From 163075a821951c25c3cb8f32dc7ed75ef5e951c5 Mon Sep 17 00:00:00 2001 From: htlcnn Date: Sun, 1 Nov 2020 20:08:48 +0700 Subject: [PATCH] black ifcblenderexport --- src/ifcblenderexport/RenameObjects.py | 50 +- src/ifcblenderexport/blenderbim/__init__.py | 7 +- .../blenderbim/bim/__init__.py | 35 +- .../blenderbim/bim/annotation.py | 57 +- src/ifcblenderexport/blenderbim/bim/bcf.py | 2 +- .../blenderbim/bim/cut_ifc.py | 424 ++- .../blenderbim/bim/export_ifc.py | 2880 ++++++++-------- src/ifcblenderexport/blenderbim/bim/helper.py | 219 +- src/ifcblenderexport/blenderbim/bim/ifc.py | 7 +- .../blenderbim/bim/import_ifc.py | 1246 +++---- .../blenderbim/bim/module/covetool/api.py | 18 +- .../bim/module/covetool/operator.py | 228 +- .../blenderbim/bim/module/covetool/prop.py | 58 +- .../blenderbim/bim/module/covetool/ui.py | 52 +- .../blenderbim/bim/module/model/door.py | 152 +- .../blenderbim/bim/module/model/grid.py | 50 +- .../blenderbim/bim/module/model/opening.py | 19 +- .../blenderbim/bim/module/model/slab.py | 23 +- .../blenderbim/bim/module/model/stair.py | 33 +- .../blenderbim/bim/module/model/wall.py | 27 +- .../blenderbim/bim/module/model/window.py | 125 +- .../blenderbim/bim/operator.py | 2928 +++++++++-------- src/ifcblenderexport/blenderbim/bim/prop.py | 1510 +++++---- src/ifcblenderexport/blenderbim/bim/qto.py | 49 +- .../blenderbim/bim/scheduler.py | 75 +- src/ifcblenderexport/blenderbim/bim/schema.py | 77 +- .../blenderbim/bim/sheeter.py | 229 +- .../blenderbim/bim/svgwriter.py | 526 +-- src/ifcblenderexport/blenderbim/bim/ui.py | 1517 ++++----- src/ifcblenderexport/docs/conf.py | 20 +- src/ifcblenderexport/dxf2ifc.py | 97 +- src/ifcblenderexport/extract.py | 92 +- src/ifcblenderexport/gbxml.py | 167 +- src/ifcblenderexport/getIfcElements.py | 163 +- src/ifcblenderexport/get_description.py | 31 +- src/ifcblenderexport/occ_utils.py | 71 +- 36 files changed, 7032 insertions(+), 6232 deletions(-) diff --git a/src/ifcblenderexport/RenameObjects.py b/src/ifcblenderexport/RenameObjects.py index 08896a95a4..f3e38daafc 100644 --- a/src/ifcblenderexport/RenameObjects.py +++ b/src/ifcblenderexport/RenameObjects.py @@ -1,29 +1,30 @@ import bpy from bpy.types import Operator + class Object_OT_RenameObjects(Operator): bl_idname = "object.renameobjects" bl_label = "Rename Object(s)" # Multi Object rename UI - BNameCB : bpy.props.BoolProperty(name = "Base Name:") - BaseName : bpy.props.StringProperty(name = "") - PreFixCB : bpy.props.BoolProperty(name = "Prefix:") - PreFix : bpy.props.StringProperty(name = "") - RemFrst : bpy.props.BoolProperty(name = "Remove First") - DgtFrst : bpy.props.IntProperty(name = "Digits") - SuffixCB : bpy.props.BoolProperty(name = "Suffix") - Suffix : bpy.props.StringProperty(name = "") - RemLast : bpy.props.BoolProperty(name = "Remove Last") - DgtLast : bpy.props.IntProperty(name = "Digits") - NumbredCB : bpy.props.BoolProperty(name = "Numbred") - BaseNum : bpy.props.IntProperty(name = "Base Number") - Step : bpy.props.IntProperty(name = "Step", default = 1) - findCB : bpy.props.BoolProperty(name = "Replace") - find : bpy.props.StringProperty(name = "") - replace : bpy.props.StringProperty(name = "") + BNameCB: bpy.props.BoolProperty(name="Base Name:") + BaseName: bpy.props.StringProperty(name="") + PreFixCB: bpy.props.BoolProperty(name="Prefix:") + PreFix: bpy.props.StringProperty(name="") + RemFrst: bpy.props.BoolProperty(name="Remove First") + DgtFrst: bpy.props.IntProperty(name="Digits") + SuffixCB: bpy.props.BoolProperty(name="Suffix") + Suffix: bpy.props.StringProperty(name="") + RemLast: bpy.props.BoolProperty(name="Remove Last") + DgtLast: bpy.props.IntProperty(name="Digits") + NumbredCB: bpy.props.BoolProperty(name="Numbred") + BaseNum: bpy.props.IntProperty(name="Base Number") + Step: bpy.props.IntProperty(name="Step", default=1) + findCB: bpy.props.BoolProperty(name="Replace") + find: bpy.props.StringProperty(name="") + replace: bpy.props.StringProperty(name="") # Single rename UI - Name : bpy.props.StringProperty(name="Name") + Name: bpy.props.StringProperty(name="Name") def draw(self, ctx): SelCount = len(bpy.context.selected_objects) @@ -70,7 +71,7 @@ class Object_OT_RenameObjects(Operator): if SelCount > 1: SelObj = bpy.context.selected_objects Index = self.BaseNum - for i in range(0,SelCount): + for i in range(0, SelCount): # Get Object Original Name # NewName = SelObj[i].name # Set the Base name # @@ -90,8 +91,8 @@ class Object_OT_RenameObjects(Operator): NewName = NewName + self.Suffix # Add Digits to end of new name # if self.NumbredCB: - NewName += str(Index) - Index += self.Step + NewName += str(Index) + Index += self.Step # Find and Replace # if self.findCB: NewName = NewName.replace(self.find, self.replace) @@ -99,17 +100,20 @@ class Object_OT_RenameObjects(Operator): SelObj[i].name = NewName elif SelCount == 1: bpy.context.selected_objects[0].name = self.Name - return {'FINISHED'} - + return {"FINISHED"} + def invoke(self, context, event): wm = context.window_manager return wm.invoke_props_dialog(self) + def register(): bpy.utils.register_class(Object_OT_RenameObjects) + def unregister(): bpy.utils.unregister_class(Object_OT_RenameObjects) + if __name__ == "__main__": - register() \ No newline at end of file + register() diff --git a/src/ifcblenderexport/blenderbim/__init__.py b/src/ifcblenderexport/blenderbim/__init__.py index e282983a11..e26493b051 100644 --- a/src/ifcblenderexport/blenderbim/__init__.py +++ b/src/ifcblenderexport/blenderbim/__init__.py @@ -1,14 +1,13 @@ bl_info = { "name": "BlenderBIM", - "description": "Author, import, and export files in the " - "Industry Foundation Classes (.ifc) file format", + "description": "Author, import, and export files in the " "Industry Foundation Classes (.ifc) file format", "author": "Dion Moult, IfcOpenShell", "blender": (2, 80, 0), "version": (0, 0, 999999), "location": "File > Export, File > Import, Scene / Object / Material / Mesh Properties", "tracker_url": "https://github.com/IfcOpenShell/IfcOpenShell/issues", - "category": "Import-Export" - } + "category": "Import-Export", +} import os import site diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index 9105f3561e..e52d3ec08b 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -1,7 +1,8 @@ # Check if we are running in Blender before loading, to allow for multiprocessing import sys import os -bpy = sys.modules.get('bpy') + +bpy = sys.modules.get("bpy") if bpy is not None: import bpy @@ -312,15 +313,13 @@ if bpy is not None: model_window.BIM_OT_add_object, model_slab.BIM_OT_add_object, model_opening.BIM_OT_add_object, - ) + ) def menu_func_export(self, context): - self.layout.operator(operator.ExportIFC.bl_idname, - text="Industry Foundation Classes (.ifc/.ifczip/.ifcjson)") + self.layout.operator(operator.ExportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcjson)") def menu_func_import(self, context): - self.layout.operator(operator.ImportIFC.bl_idname, - text="Industry Foundation Classes (.ifc/.ifczip/.ifcxml)") + self.layout.operator(operator.ImportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcxml)") def on_register(scene): prop.setDefaultProperties(scene) @@ -363,18 +362,18 @@ if bpy is not None: bpy.app.handlers.load_post.remove(prop.setDefaultProperties) bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) - del(bpy.types.Scene.BIMProperties) - del(bpy.types.Scene.BIMDebugProperties) - del(bpy.types.Scene.BCFProperties) - del(bpy.types.Scene.DocProperties) - del(bpy.types.Scene.MapConversion) - del(bpy.types.Scene.TargetCRS) - del(bpy.types.Object.BIMObjectProperties) - del(bpy.types.Collection.BIMObjectProperties) - del(bpy.types.Material.BIMMaterialProperties) - del(bpy.types.Mesh.BIMMeshProperties) - del(bpy.types.Camera.BIMCameraProperties) - del(bpy.types.TextCurve.BIMTextProperties) + del bpy.types.Scene.BIMProperties + del bpy.types.Scene.BIMDebugProperties + del bpy.types.Scene.BCFProperties + del bpy.types.Scene.DocProperties + del bpy.types.Scene.MapConversion + del bpy.types.Scene.TargetCRS + del bpy.types.Object.BIMObjectProperties + del bpy.types.Collection.BIMObjectProperties + del bpy.types.Material.BIMMaterialProperties + del bpy.types.Mesh.BIMMeshProperties + del bpy.types.Camera.BIMCameraProperties + del bpy.types.TextCurve.BIMTextProperties bpy.types.SCENE_PT_unit.remove(ui.ifc_units) bpy.types.VIEW3D_MT_mesh_add.remove(model_grid.add_object_button) bpy.types.VIEW3D_MT_mesh_add.remove(model_wall.add_object_button) diff --git a/src/ifcblenderexport/blenderbim/bim/annotation.py b/src/ifcblenderexport/blenderbim/bim/annotation.py index 8624352093..5376f37a39 100644 --- a/src/ifcblenderexport/blenderbim/bim/annotation.py +++ b/src/ifcblenderexport/blenderbim/bim/annotation.py @@ -2,24 +2,24 @@ import bpy import os from mathutils import Vector -class Annotator: +class Annotator: @staticmethod def get_svg_text_size(size): sizes = { - '1.8': '2.97', - '2.5': '4.13', - '3.5': '5.78', - '5.0': '8.25', - '7.0': '11.55', + "1.8": "2.97", + "2.5": "4.13", + "3.5": "5.78", + "5.0": "8.25", + "7.0": "11.55", } return float(sizes[str(size)]) @staticmethod def add_text(related_element=None): - curve = bpy.data.curves.new(type='FONT', name='Plan/Annotation/PLAN_VIEW/Text') - curve.body = 'TEXT' - obj = bpy.data.objects.new('IfcAnnotation/Text', curve) + curve = bpy.data.curves.new(type="FONT", name="Plan/Annotation/PLAN_VIEW/Text") + curve.body = "TEXT" + obj = bpy.data.objects.new("IfcAnnotation/Text", curve) obj.matrix_world = bpy.context.scene.camera.matrix_world if related_element is None: location, co2 = Annotator.get_placeholder_coords() @@ -28,13 +28,14 @@ class Annotator: location = related_element.location obj.location = location obj.hide_render = True - font = bpy.data.fonts.get('OpenGost TypeB TT') + font = bpy.data.fonts.get("OpenGost TypeB TT") if not font: - font = bpy.data.fonts.load(os.path.join( - bpy.context.scene.BIMProperties.data_dir, 'fonts', 'OpenGost Type B TT.ttf')) - font.name = 'OpenGost Type B TT' + font = bpy.data.fonts.load( + os.path.join(bpy.context.scene.BIMProperties.data_dir, "fonts", "OpenGost Type B TT.ttf") + ) + font.name = "OpenGost Type B TT" obj.data.font = font - obj.data.BIMTextProperties.font_size = '2.5' + obj.data.BIMTextProperties.font_size = "2.5" collection = bpy.context.scene.camera.users_collection[0] collection.objects.link(obj) Annotator.resize_text(obj) @@ -53,11 +54,11 @@ class Annotator: font_size = 1.6 / 1000 font_size *= float(text_obj.data.BIMTextProperties.font_size) - if camera.data.BIMCameraProperties.diagram_scale == 'CUSTOM': - human_scale, fraction = camera.data.BIMCameraProperties.custom_diagram_scale.split('|') + if camera.data.BIMCameraProperties.diagram_scale == "CUSTOM": + human_scale, fraction = camera.data.BIMCameraProperties.custom_diagram_scale.split("|") else: - human_scale, fraction = camera.data.BIMCameraProperties.diagram_scale.split('|') - numerator, denominator = fraction.split('/') + human_scale, fraction = camera.data.BIMCameraProperties.diagram_scale.split("|") + numerator, denominator = fraction.split("/") font_size /= float(numerator) / float(denominator) text_obj.data.size = font_size @@ -75,7 +76,7 @@ class Annotator: obj.data.edges.add(1) obj.data.edges[-1].vertices = (obj.data.vertices[-2].index, obj.data.vertices[-1].index) if isinstance(obj.data, bpy.types.Curve): - polyline = obj.data.splines.new('POLY') + polyline = obj.data.splines.new("POLY") polyline.points.add(1) polyline.points[-2].co = list(co1) + [1] polyline.points[-1].co = list(co2) + [1] @@ -87,13 +88,13 @@ class Annotator: for obj in collection.objects: if name in obj.name: return obj - if data_type == 'mesh': - data = bpy.data.meshes.new('Plan/Annotation/PLAN_VIEW/' + name) - elif data_type == 'curve': - data = bpy.data.curves.new('Plan/Annotation/PLAN_VIEW/' + name, type='CURVE') - data.dimensions = '3D' + if data_type == "mesh": + data = bpy.data.meshes.new("Plan/Annotation/PLAN_VIEW/" + name) + elif data_type == "curve": + data = bpy.data.curves.new("Plan/Annotation/PLAN_VIEW/" + name, type="CURVE") + data.dimensions = "3D" data.resolution_u = 2 - obj = bpy.data.objects.new('IfcAnnotation/' + name, data) + obj = bpy.data.objects.new("IfcAnnotation/" + name, data) collection.objects.link(obj) return obj @@ -102,7 +103,11 @@ class Annotator: camera = bpy.context.scene.camera z_offset = camera.matrix_world.to_quaternion() @ Vector((0, 0, -1)) if bpy.context.scene.render.resolution_x > bpy.context.scene.render.resolution_y: - y = camera.data.ortho_scale * (bpy.context.scene.render.resolution_y / bpy.context.scene.render.resolution_x) / 4 + y = ( + camera.data.ortho_scale + * (bpy.context.scene.render.resolution_y / bpy.context.scene.render.resolution_x) + / 4 + ) else: y = camera.data.ortho_scale / 4 y_offset = camera.matrix_world.to_quaternion() @ Vector((0, y, 0)) diff --git a/src/ifcblenderexport/blenderbim/bim/bcf.py b/src/ifcblenderexport/blenderbim/bim/bcf.py index e82f9dc4e3..7fad365aac 100644 --- a/src/ifcblenderexport/blenderbim/bim/bcf.py +++ b/src/ifcblenderexport/blenderbim/bim/bcf.py @@ -1,4 +1,4 @@ -class BcfStore(): +class BcfStore: topics = [] viewpoints = [] comments = [] diff --git a/src/ifcblenderexport/blenderbim/bim/cut_ifc.py b/src/ifcblenderexport/blenderbim/bim/cut_ifc.py index a89f08b8fa..30bd17ae8a 100644 --- a/src/ifcblenderexport/blenderbim/bim/cut_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/cut_ifc.py @@ -76,7 +76,8 @@ import ifcopenshell.util.selector import ifcopenshell.util.element cwd = os.path.dirname(os.path.realpath(__file__)) -this_file = os.path.join(cwd, 'cut_ifc.py') +this_file = os.path.join(cwd, "cut_ifc.py") + def get_booleaned_edges(shape): edges = [] @@ -86,6 +87,7 @@ def get_booleaned_edges(shape): exp.Next() return edges + def connect_edges_into_wires(unconnected_edges): edges = TopTools.TopTools_HSequenceOfShape() edges_handle = TopTools.Handle_TopTools_HSequenceOfShape(edges) @@ -98,28 +100,17 @@ def connect_edges_into_wires(unconnected_edges): ShapeAnalysis.ShapeAnalysis_FreeBounds.ConnectEdgesToWires(edges_handle, 1e-5, True, wires_handle) return wires_handle.GetObject() + def do_cut(process_data): global_id, shape, section, trsf_data = process_data axis = gp.gp_Ax2( - gp.gp_Pnt( - trsf_data['top_left_corner'][0], - trsf_data['top_left_corner'][1], - trsf_data['top_left_corner'][2]), - gp.gp_Dir( - trsf_data['projection'][0], - trsf_data['projection'][1], - trsf_data['projection'][2]), - gp.gp_Dir( - trsf_data['x_axis'][0], - trsf_data['x_axis'][1], - trsf_data['x_axis'][2]) - ) + gp.gp_Pnt(trsf_data["top_left_corner"][0], trsf_data["top_left_corner"][1], trsf_data["top_left_corner"][2]), + gp.gp_Dir(trsf_data["projection"][0], trsf_data["projection"][1], trsf_data["projection"][2]), + gp.gp_Dir(trsf_data["x_axis"][0], trsf_data["x_axis"][1], trsf_data["x_axis"][2]), + ) source = gp.gp_Ax3(axis) - destination = gp.gp_Ax3( - gp.gp_Pnt(0, 0, 0), - gp.gp_Dir(0, 0, -1), - gp.gp_Dir(1, 0, 0)) + destination = gp.gp_Ax3(gp.gp_Pnt(0, 0, 0), gp.gp_Dir(0, 0, -1), gp.gp_Dir(1, 0, 0)) transformation = gp.gp_Trsf() transformation.SetDisplacement(source, destination) @@ -130,10 +121,9 @@ def do_cut(process_data): return cut_polygons wires = connect_edges_into_wires(section_edges) for i in range(wires.Length()): - wire_shape = wires.Value(i+1) + wire_shape = wires.Value(i + 1) - transformed_wire = BRepBuilderAPI.BRepBuilderAPI_Transform( - wire_shape, transformation) + transformed_wire = BRepBuilderAPI.BRepBuilderAPI_Transform(wire_shape, transformation) wire_shape = transformed_wire.Shape() wire = topods.Wire(wire_shape) @@ -145,7 +135,7 @@ def do_cut(process_data): point = BRep.BRep_Tool.Pnt(exp.CurrentVertex()) points.append((point.X(), -point.Y())) exp.Next() - cut_polygons.append({ 'global_id': global_id, 'metadata': {}, 'points': points }) + cut_polygons.append({"global_id": global_id, "metadata": {}, "points": points}) return cut_polygons @@ -158,50 +148,50 @@ class IfcCutter: self.cut_polygons = [] self.template_variables = {} self.metadata = {} - self.data_dir = '' - self.vector_style = '' + self.data_dir = "" + self.vector_style = "" self.ifc_filenames = [] self.ifc_files = {} self.resolved_pixels = set() self.should_get_background = False - self.text_pickle_file = 'text.pickle' - self.metadata_pickle_file = 'metadata.pickle' - self.cut_pickle_file = 'cut.pickle' + self.text_pickle_file = "text.pickle" + self.metadata_pickle_file = "metadata.pickle" + self.cut_pickle_file = "cut.pickle" self.should_recut = True self.should_recut_selected = True - self.cut_objects = '' + self.cut_objects = "" self.selected_global_ids = [] self.should_extract = True self.diagram_name = None self.background_image = None self.section_box = { - 'projection': (0, 1, 0), - 'x_axis': (1, 0, 0), - 'y_axis': (0, 0, -1), - 'top_left_corner': (-2, 2, 8), - 'x': 14, - 'y': 9, - 'z': 2, - 'shape': None, - 'face': None + "projection": (0, 1, 0), + "x_axis": (1, 0, 0), + "y_axis": (0, 0, -1), + "top_left_corner": (-2, 2, 8), + "x": 14, + "y": 9, + "z": 2, + "shape": None, + "face": None, } def cut(self): - self.profile_code('Starting cut process') + self.profile_code("Starting cut process") self.load_ifc_files() - self.profile_code('Load IFC files') + self.profile_code("Load IFC files") self.get_template_variables() - self.profile_code('Get template variables') + self.profile_code("Get template variables") self.get_product_shapes() - self.profile_code('Get product shapes') + self.profile_code("Get product shapes") self.create_section_box() - self.profile_code('Create section box') + self.profile_code("Create section box") self.get_cut_polygons() - self.profile_code('Get cut polygons') + self.profile_code("Get cut polygons") self.get_annotation() - self.profile_code('Get annotation') + self.profile_code("Get annotation") self.get_cut_polygon_metadata() - self.profile_code('Get cut polygon metadata') + self.profile_code("Get cut polygon metadata") # should_get_background is False in production as this is experimental if not self.should_get_background: @@ -215,7 +205,7 @@ class IfcCutter: def profile_code(self, message): if not self.time: self.time = time.time() - print('{} :: {:.2f}'.format(message, time.time() - self.time)) + print("{} :: {:.2f}".format(message, time.time() - self.time)) self.time = time.time() def load_ifc_files(self): @@ -224,14 +214,14 @@ class IfcCutter: loaded_files = [] for filename in self.ifc_filenames: - print('Loading file {} ...'.format(filename)) + print("Loading file {} ...".format(filename)) if filename: self.ifc_files[filename] = ifcopenshell.open(filename) def get_template_variables(self): if not self.should_extract: if os.path.isfile(self.text_pickle_file): - with open(self.text_pickle_file, 'rb') as text_file: + with open(self.text_pickle_file, "rb") as text_file: self.template_variables = pickle.load(text_file) return @@ -241,7 +231,7 @@ class IfcCutter: if text_obj_data: data[text_obj.name] = text_obj_data - with open(self.text_pickle_file, 'wb') as text_file: + with open(self.text_pickle_file, "wb") as text_file: pickle.dump(data, text_file, protocol=pickle.HIGHEST_PROTOCOL) self.template_variables = data @@ -252,16 +242,16 @@ class IfcCutter: related_element = text_obj.data.BIMTextProperties.related_element if not related_element: return - global_id = related_element.BIMObjectProperties.attributes.get('GlobalId') + global_id = related_element.BIMObjectProperties.attributes.get("GlobalId") if not global_id: return element = self.get_ifc_element(global_id.string_value) for variable in text_obj.data.BIMTextProperties.variables: if element: - if '{{' in variable.prop_key: - prop_key = variable.prop_key.split('{{')[1].split('}}')[0] + if "{{" in variable.prop_key: + prop_key = variable.prop_key.split("{{")[1].split("}}")[0] prop_value = self.selector.get_element_value(element, prop_key) - variable_value = eval(variable.prop_key.replace('{{' + prop_key + '}}', str(prop_value))) + variable_value = eval(variable.prop_key.replace("{{" + prop_key + "}}", str(prop_value))) else: variable_value = self.selector.get_element_value(element, variable.prop_key) text_obj_data[variable.name] = variable_value @@ -277,24 +267,26 @@ class IfcCutter: for filename, ifc_file in self.ifc_files.items(): shape_pickle = os.path.join( - self.data_dir, 'cache', 'shapes', '{}.pickle'.format(os.path.basename(filename))) + self.data_dir, "cache", "shapes", "{}.pickle".format(os.path.basename(filename)) + ) shape_map = {} if self.should_recut_selected and os.path.isfile(shape_pickle): - with open(shape_pickle, 'rb') as shape_file: + with open(shape_pickle, "rb") as shape_file: shape_map = pickle.load(shape_file) products.extend(self.selector.parse(ifc_file, self.cut_objects)) selected_elements = [] for i, product in enumerate(products): - if product.is_a('IfcOpeningElement') \ - or product.is_a('IfcSite') \ - or product.Representation is None \ - or self.has_annotation(product): + if ( + product.is_a("IfcOpeningElement") + or product.is_a("IfcSite") + or product.Representation is None + or self.has_annotation(product) + ): continue try: - if self.should_recut_selected \ - and product.GlobalId in self.selected_global_ids: + if self.should_recut_selected and product.GlobalId in self.selected_global_ids: selected_elements.append(product) elif product.GlobalId in shape_map: shape = shape_map[product.GlobalId] @@ -302,19 +294,20 @@ class IfcCutter: else: selected_elements.append(product) except: - print('Failed to create shape for {}'.format(product)) + print("Failed to create shape for {}".format(product)) if selected_elements: total = 0 checkpoint = time.time() iterator = ifcopenshell.geom.iterator( - settings, ifc_file, multiprocessing.cpu_count(), include=selected_elements) + settings, ifc_file, multiprocessing.cpu_count(), include=selected_elements + ) valid_file = iterator.initialize() if valid_file: while True: total += 1 if total % 250 == 0: - print('{} elements processed in {:.2f}s ...'.format(total, time.time() - checkpoint)) + print("{} elements processed in {:.2f}s ...".format(total, time.time() - checkpoint)) checkpoint = time.time() shape = iterator.get() shape_map[shape.data.guid] = shape.geometry @@ -322,7 +315,7 @@ class IfcCutter: if not iterator.next(): break - with open(shape_pickle, 'wb') as shape_file: + with open(shape_pickle, "wb") as shape_file: pickle.dump(shape_map, shape_file, protocol=pickle.HIGHEST_PROTOCOL) def add_product_shape(self, product, shape): @@ -330,16 +323,18 @@ class IfcCutter: def has_annotation(self, element): for representation in element.Representation.Representations: - if representation.ContextOfItems.ContextType == 'Plan' \ - and representation.ContextOfItems.ContextIdentifier == 'Annotation': + if ( + representation.ContextOfItems.ContextType == "Plan" + and representation.ContextOfItems.ContextIdentifier == "Annotation" + ): return True return False def sort_background_elements(self, reverse=None): if reverse: - new_list = sorted(self.background_elements, key=lambda k: -k['z']) + new_list = sorted(self.background_elements, key=lambda k: -k["z"]) else: - new_list = sorted(self.background_elements, key=lambda k: k['z']) + new_list = sorted(self.background_elements, key=lambda k: k["z"]) self.background_elements = new_list def process_grid(self, face, resolution): @@ -351,13 +346,10 @@ class IfcCutter: current_x = 0 current_y = 0 is_visible = False - while current_x < self.section_box['x']: + while current_x < self.section_box["x"]: current_y = 0 - while current_y > -self.section_box['y']: - if current_x < xmin \ - or current_x > xmax \ - or current_y < ymin \ - or current_y > ymax: + while current_y > -self.section_box["y"]: + if current_x < xmin or current_x > xmax or current_y < ymin or current_y > ymax: current_y -= resolution continue if (current_x, current_y) in self.resolved_pixels: @@ -375,70 +367,65 @@ class IfcCutter: def merge_background_elements(self): background_elements = [] - resolution = 0.1 # 10cm + resolution = 0.1 # 10cm # DO CUT total_product_shapes = len(self.cut_polygons) n = 0 for element in self.cut_polygons: - #print('{}/{} background elements processed ...'.format(n, total_product_shapes), end='\r', flush=True) - print('{}/{} cut polygons processed ...'.format(n, total_product_shapes)) - print('{} resolved pixels'.format(len(self.resolved_pixels))) + # print('{}/{} background elements processed ...'.format(n, total_product_shapes), end='\r', flush=True) + print("{}/{} cut polygons processed ...".format(n, total_product_shapes)) + print("{} resolved pixels".format(len(self.resolved_pixels))) n += 1 - self.process_grid(element['geometry_face'], resolution) + self.process_grid(element["geometry_face"], resolution) # DO BACKGROUND total_product_shapes = len(self.background_elements) n = 0 for element in self.background_elements: - #print('{}/{} background elements processed ...'.format(n, total_product_shapes), end='\r', flush=True) - print('{}/{} background elements processed ...'.format(n, total_product_shapes)) - print('{} resolved pixels'.format(len(self.resolved_pixels))) + # print('{}/{} background elements processed ...'.format(n, total_product_shapes), end='\r', flush=True) + print("{}/{} background elements processed ...".format(n, total_product_shapes)) + print("{} resolved pixels".format(len(self.resolved_pixels))) n += 1 - if element['type'] != 'polygon': + if element["type"] != "polygon": background_elements.append(element) continue - is_visible = self.process_grid(element['geometry_face'], resolution) + is_visible = self.process_grid(element["geometry_face"], resolution) if is_visible: background_elements.append(element) - print('##### BEFORE it had {} and after it had {}'.format( - len(self.background_elements), len(background_elements))) + print( + "##### BEFORE it had {} and after it had {}".format(len(self.background_elements), len(background_elements)) + ) self.background_elements = background_elements return def create_section_box(self): top_left_corner = gp.gp_Pnt( - self.section_box['top_left_corner'][0], - self.section_box['top_left_corner'][1], - self.section_box['top_left_corner'][2]) + self.section_box["top_left_corner"][0], + self.section_box["top_left_corner"][1], + self.section_box["top_left_corner"][2], + ) axis = gp.gp_Ax2( top_left_corner, gp.gp_Dir( - self.section_box['projection'][0], - self.section_box['projection'][1], - self.section_box['projection'][2]), - gp.gp_Dir( - self.section_box['x_axis'][0], - self.section_box['x_axis'][1], - self.section_box['x_axis'][2]) - ) + self.section_box["projection"][0], self.section_box["projection"][1], self.section_box["projection"][2] + ), + gp.gp_Dir(self.section_box["x_axis"][0], self.section_box["x_axis"][1], self.section_box["x_axis"][2]), + ) section_box = BRepPrimAPI.BRepPrimAPI_MakeBox( - axis, self.section_box['x'], self.section_box['y'], self.section_box['z'] - ) - self.section_box['shape'] = section_box.Shape() - self.section_box['face'] = section_box.BottomFace() + axis, self.section_box["x"], self.section_box["y"], self.section_box["z"] + ) + self.section_box["shape"] = section_box.Shape() + self.section_box["face"] = section_box.BottomFace() source = gp.gp_Ax3(axis) self.transformation_data = { - 'top_left_corner': self.section_box['top_left_corner'], - 'projection': self.section_box['projection'], - 'x_axis': self.section_box['x_axis'] + "top_left_corner": self.section_box["top_left_corner"], + "projection": self.section_box["projection"], + "x_axis": self.section_box["x_axis"], } - destination = gp.gp_Ax3( - gp.gp_Pnt(0, 0, 0), - gp.gp_Dir(0, 0, -1), - gp.gp_Dir(1, 0, 0)) + destination = gp.gp_Ax3(gp.gp_Pnt(0, 0, 0), gp.gp_Dir(0, 0, -1), gp.gp_Dir(1, 0, 0)) self.transformation_dest = destination self.transformation = gp.gp_Trsf() self.transformation.SetDisplacement(source, destination) @@ -453,24 +440,21 @@ class IfcCutter: for product, shape in self.product_shapes: builder.Add(compound, shape) - print('{}/{} background elements processed ...'.format(n, total_product_shapes), end='\r', flush=True) - #print('Processing product {} '.format(product.Name)) + print("{}/{} background elements processed ...".format(n, total_product_shapes), end="\r", flush=True) + # print('Processing product {} '.format(product.Name)) n += 1 - intersection = BRepAlgoAPI.BRepAlgoAPI_Common(self.section_box['shape'], shape).Shape() + intersection = BRepAlgoAPI.BRepAlgoAPI_Common(self.section_box["shape"], shape).Shape() intersection_edges = self.get_booleaned_edges(intersection) if len(intersection_edges) <= 0: continue intersections.append(intersection) - transformed_intersection = BRepBuilderAPI.BRepBuilderAPI_Transform( - intersection, self.transformation) + transformed_intersection = BRepBuilderAPI.BRepBuilderAPI_Transform(intersection, self.transformation) intersection = transformed_intersection.Shape() edge_face_map = TopTools.TopTools_IndexedDataMapOfShapeListOfShape() - TopExp.topexp.MapShapesAndAncestors( - intersection, TopAbs.TopAbs_EDGE, - TopAbs.TopAbs_FACE, edge_face_map) + TopExp.topexp.MapShapesAndAncestors(intersection, TopAbs.TopAbs_EDGE, TopAbs.TopAbs_FACE, edge_face_map) exp = TopExp.TopExp_Explorer(intersection, TopAbs.TopAbs_FACE) while exp.More(): @@ -486,31 +470,29 @@ class IfcCutter: exp.Next() def get_raycast_hits(self, shape): - resolution = 0.1 # 5cm + resolution = 0.1 # 5cm hits = [] current_x = 0 current_y = 0 - while current_x < self.section_box['x'] /2: + while current_x < self.section_box["x"] / 2: current_y = 0 - while current_y < self.section_box['y']/4: - point = numpy.array(self.section_box['top_left_corner']) - point = numpy.add(point, current_x * numpy.array(self.section_box['x_axis'])) - point = numpy.add(point, current_y * numpy.array(self.section_box['y_axis'])) + while current_y < self.section_box["y"] / 4: + point = numpy.array(self.section_box["top_left_corner"]) + point = numpy.add(point, current_x * numpy.array(self.section_box["x_axis"])) + point = numpy.add(point, current_y * numpy.array(self.section_box["y_axis"])) hit = self.raycast(shape, point) if hit: hits.append(hit) current_y += resolution current_x += resolution - print('row down') + print("row down") return hits def raycast(self, shape, point): raycast = IntCurvesFace.IntCurvesFace_ShapeIntersector() raycast.Load(shape, 0.01) - line = gp.gp_Lin( - gp.gp_Pnt(float(point[0]), float(point[1]), float(point[2])), - gp.gp_Dir( 0, 0, -1)) - raycast.Perform(line, 0, self.section_box['z']) + line = gp.gp_Lin(gp.gp_Pnt(float(point[0]), float(point[1]), float(point[2])), gp.gp_Dir(0, 0, -1)) + raycast.Perform(line, 0, self.section_box["z"]) return raycast.NbPnt() != 0 def raycast_at_projection_dir(self, shape, point): @@ -519,14 +501,14 @@ class IfcCutter: line = gp.gp_Lin( gp.gp_Pnt(float(point[0]), float(point[1]), float(point[2])), gp.gp_Dir( - self.section_box['projection'][0], - self.section_box['projection'][1], - self.section_box['projection'][2])) - raycast.Perform(line, 0, self.section_box['z']) + self.section_box["projection"][0], self.section_box["projection"][1], self.section_box["projection"][2] + ), + ) + raycast.Perform(line, 0, self.section_box["z"]) if raycast.NbPnt() != 0: # The smaller WParameter is the closer z-index # Should be the first - return { 'face': raycast.Face(1), 'z': raycast.WParameter(1) } + return {"face": raycast.Face(1), "z": raycast.WParameter(1)} def get_bbox(self, shape): bbox = Bnd.Bnd_Box() @@ -536,7 +518,7 @@ class IfcCutter: def calculate_face_zpos(self, face): bbox = self.get_bbox(face) xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get() - zpos = zmin + ((zmax - zmin)/2) + zpos = zmin + ((zmax - zmin) / 2) return zpos, zmax def get_split_edges(self, edge_face_map, face, zmax, product): @@ -553,24 +535,21 @@ class IfcCutter: # because it does, sometimes. edge_angle = 0 if edge_angle > 30 and edge_angle < 160: - newedge = self.build_new_edge(edge, zmax+0.01) + newedge = self.build_new_edge(edge, zmax + 0.01) if newedge: - self.background_elements.append({ - 'raw': product, - 'geometry': newedge, - 'type': 'line', - 'z': zmax+0.01 - }) + self.background_elements.append( + {"raw": product, "geometry": newedge, "type": "line", "z": zmax + 0.01} + ) exp2.Next() def get_angle_between_faces(self, f1, f2): return self.convert_dot_product_to_angle( - self.get_dot_product_of_normals( - self.get_normal(f1), self.get_normal(f2))) + self.get_dot_product_of_normals(self.get_normal(f1), self.get_normal(f2)) + ) def get_normal(self, face): surface = Geom.Handle_Geom_Surface(BRep.BRep_Tool.Surface(face)) - props = GeomLProp.GeomLProp_SLProps(surface, 0, 0, 1, .001) + props = GeomLProp.GeomLProp_SLProps(surface, 0, 0, 1, 0.001) return props.Normal() def get_dot_product_of_normals(self, n1, n2): @@ -580,9 +559,7 @@ class IfcCutter: return math.acos(dp) def is_same_point(self, p1, p2): - return p1.X() == p2.X() \ - and p1.Y() == p2.Y() \ - and p1.Z() == p2.Z() + return p1.X() == p2.X() and p1.Y() == p2.Y() and p1.Z() == p2.Z() def build_new_edge(self, edge, zpos): exp = TopExp.TopExp_Explorer(edge, TopAbs.TopAbs_VERTEX) @@ -594,9 +571,7 @@ class IfcCutter: new_vertices.append(BRepBuilderAPI.BRepBuilderAPI_MakeVertex(current_point).Vertex()) exp.Next() try: - return BRepBuilderAPI.BRepBuilderAPI_MakeEdge( - new_vertices[0], new_vertices[1] - ).Edge() + return BRepBuilderAPI.BRepBuilderAPI_MakeEdge(new_vertices[0], new_vertices[1]).Edge() except: return None @@ -619,10 +594,9 @@ class IfcCutter: previous_vertex = current_vertex else: try: - new_wire_builder.Add(topods.Edge( - BRepBuilderAPI.BRepBuilderAPI_MakeEdge( - previous_vertex, current_vertex - ).Edge())) + new_wire_builder.Add( + topods.Edge(BRepBuilderAPI.BRepBuilderAPI_MakeEdge(previous_vertex, current_vertex).Edge()) + ) previous_vertex = current_vertex except: pass @@ -631,24 +605,19 @@ class IfcCutter: # make last edge if not wireexp.More(): try: - new_wire_builder.Add(topods.Edge( - BRepBuilderAPI.BRepBuilderAPI_MakeEdge( - current_vertex, first_vertex - ).Edge())) + new_wire_builder.Add( + topods.Edge(BRepBuilderAPI.BRepBuilderAPI_MakeEdge(current_vertex, first_vertex).Edge()) + ) except: pass try: new_wire = new_wire_builder.Wire() new_face = BRepBuilderAPI.BRepBuilderAPI_MakeFace(new_wire).Face() - self.background_elements.append({ - 'raw': product, - 'geometry': new_wire, - 'geometry_face': new_face, - 'type': 'polygon', - 'z': zpos - }) + self.background_elements.append( + {"raw": product, "geometry": new_wire, "geometry_face": new_face, "type": "polygon", "z": zpos} + ) except: - #print('Could not build face') + # print('Could not build face') pass exp.Next() @@ -674,21 +643,26 @@ class IfcCutter: def get_annotation(self): import mathutils + self.annotation_objs = [] settings_2d = ifcopenshell.geom.settings() settings_2d.set(settings_2d.INCLUDE_CURVES, True) settings_py = ifcopenshell.geom.settings() settings_py.set(settings_py.USE_PYTHON_OPENCASCADE, True) for ifc_file in self.ifc_files.values(): - for element in ifc_file.by_type('IfcElement'): + for element in ifc_file.by_type("IfcElement"): annotation_representation = None box_representation = None for representation in element.Representation.Representations: - if representation.ContextOfItems.ContextType == 'Plan' \ - and representation.ContextOfItems.ContextIdentifier == 'Annotation': + if ( + representation.ContextOfItems.ContextType == "Plan" + and representation.ContextOfItems.ContextIdentifier == "Annotation" + ): annotation_representation = representation - elif representation.ContextOfItems.ContextType == 'Model' \ - and representation.ContextOfItems.ContextIdentifier == 'Box': + elif ( + representation.ContextOfItems.ContextType == "Model" + and representation.ContextOfItems.ContextIdentifier == "Box" + ): box_representation = representation if not annotation_representation or not box_representation: continue @@ -698,19 +672,19 @@ class IfcCutter: # plane, then we should "continue" and not process the 2D # wireframe. This approach works but is not very smart. for subelement in ifc_file.traverse(box_representation): - if subelement.is_a('IfcBoundingBox'): + if subelement.is_a("IfcBoundingBox"): block = ifc_file.createIfcBlock( ifc_file.createIfcAxis2Placement3D(subelement.Corner, None, None), subelement.XDim, subelement.YDim, - subelement.ZDim + subelement.ZDim, ) for inverse in ifc_file.get_inverse(subelement): ifcopenshell.util.element.replace_attribute(inverse, subelement, block) element.Representation.Representations = [box_representation] shape = ifcopenshell.geom.create_shape(settings_py, element) - section = BRepAlgoAPI.BRepAlgoAPI_Section(self.section_box['face'], shape.geometry).Shape() + section = BRepAlgoAPI.BRepAlgoAPI_Section(self.section_box["face"], shape.geometry).Shape() section_edges = get_booleaned_edges(section) if len(section_edges) <= 0: @@ -722,64 +696,66 @@ class IfcCutter: # Monkey patch - see bug #771. element.Representation.Representations = [annotation_representation] shape = ifcopenshell.geom.create_shape(settings_2d, element) - if hasattr(shape, 'geometry'): + if hasattr(shape, "geometry"): geometry = shape.geometry else: geometry = shape e = geometry.edges v = geometry.verts m = shape.transformation.matrix.data - mat = mathutils.Matrix(([m[0], m[1], m[2], 0], - [m[3], m[4], m[5], 0], - [m[6], m[7], m[8], 0], - [m[9], m[10], m[11], 1])) + mat = mathutils.Matrix( + ([m[0], m[1], m[2], 0], [m[3], m[4], m[5], 0], [m[6], m[7], m[8], 0], [m[9], m[10], m[11], 1]) + ) mat.transpose() - self.annotation_objs.append({ - 'raw': element, - 'classes': self.get_classes(element, 'annotation'), - 'edges': [[e[i], e[i + 1]] for i in range(0, len(e), 2)], - 'vertices': [mat @ mathutils.Vector((v[i], v[i + 1], v[i + 2])) for i in range(0, len(v), 3)] - }) + self.annotation_objs.append( + { + "raw": element, + "classes": self.get_classes(element, "annotation"), + "edges": [[e[i], e[i + 1]] for i in range(0, len(e), 2)], + "vertices": [mat @ mathutils.Vector((v[i], v[i + 1], v[i + 2])) for i in range(0, len(v), 3)], + } + ) def get_cut_polygon_metadata(self): if not self.should_extract: if os.path.isfile(self.metadata_pickle_file): - with open(self.metadata_pickle_file, 'rb') as metadata_file: + with open(self.metadata_pickle_file, "rb") as metadata_file: self.metadata = pickle.load(metadata_file) for polygon in self.cut_polygons: - if polygon['global_id'] in self.metadata: - polygon['metadata'] = self.metadata[polygon['global_id']] + if polygon["global_id"] in self.metadata: + polygon["metadata"] = self.metadata[polygon["global_id"]] return for polygon in self.cut_polygons: - metadata = { 'classes': self.get_classes(self.get_ifc_element(polygon['global_id']), 'cut') } - self.metadata[polygon['global_id']] = metadata - polygon['metadata'] = metadata + metadata = {"classes": self.get_classes(self.get_ifc_element(polygon["global_id"]), "cut")} + self.metadata[polygon["global_id"]] = metadata + polygon["metadata"] = metadata - with open(self.metadata_pickle_file, 'wb') as metadata_file: + with open(self.metadata_pickle_file, "wb") as metadata_file: pickle.dump(self.metadata, metadata_file, protocol=pickle.HIGHEST_PROTOCOL) def pickle_cut_polygons(self): - with open(self.cut_pickle_file, 'wb') as pickle_file: + with open(self.cut_pickle_file, "wb") as pickle_file: pickle.dump(self.cut_polygons, pickle_file, protocol=pickle.HIGHEST_PROTOCOL) def get_fresh_cut_polygons(self): - process_data = [(p.GlobalId, s, self.section_box['face'], self.transformation_data) for p, s in self.product_shapes] + process_data = [ + (p.GlobalId, s, self.section_box["face"], self.transformation_data) for p, s in self.product_shapes + ] import bpy + multiprocessing.set_executable(bpy.app.binary_path_python) with multiprocessing.Pool(9) as p: results = p.map(do_cut, process_data) for result in results: - polygons = [p for p in result if p['points']] + polygons = [p for p in result if p["points"]] self.cut_polygons.extend(polygons) def get_polygon_metadata(self, polygon, position): - polygon['metadata'] = { - 'classes': self.get_classes(self.get_ifc_element(polygon['global_id']), position) - } + polygon["metadata"] = {"classes": self.get_classes(self.get_ifc_element(polygon["global_id"]), position)} return polygon def get_ifc_element(self, global_id): @@ -795,28 +771,29 @@ class IfcCutter: def get_classes(self, element, position): classes = [position, element.is_a()] for association in element.HasAssociations: - if association.is_a('IfcRelAssociatesMaterial'): - classes.append('material-{}'.format( - re.sub('[^0-9a-zA-Z]+', '', self.get_material_name(association.RelatingMaterial)) - )) - classes.append('globalid-{}'.format(element.GlobalId)) + if association.is_a("IfcRelAssociatesMaterial"): + classes.append( + "material-{}".format( + re.sub("[^0-9a-zA-Z]+", "", self.get_material_name(association.RelatingMaterial)) + ) + ) + classes.append("globalid-{}".format(element.GlobalId)) for attribute in self.attributes: result = self.selector.get_element_value(element, attribute) if result: - classes.append('{}-{}'.format( - re.sub('[^0-9a-zA-Z]+', '', attribute), - re.sub('[^0-9a-zA-Z]+', '', result) - )) + classes.append( + "{}-{}".format(re.sub("[^0-9a-zA-Z]+", "", attribute), re.sub("[^0-9a-zA-Z]+", "", result)) + ) return classes def get_material_name(self, element): - if hasattr(element, 'Name') and element.Name: + if hasattr(element, "Name") and element.Name: return element.Name return element.id() def get_pickled_cut_polygons(self): if os.path.isfile(self.cut_pickle_file): - with open(self.cut_pickle_file, 'rb') as pickle_file: + with open(self.cut_pickle_file, "rb") as pickle_file: self.cut_polygons = pickle.load(pickle_file) @@ -838,36 +815,35 @@ class IfcCutterDebug(IfcCutter): self.display_background_elements() def display_everything_with_section_plane(self): - section_face_display = ifcopenshell.geom.utils.display_shape(self.section_box['face']) + section_face_display = ifcopenshell.geom.utils.display_shape(self.section_box["face"]) ifcopenshell.geom.utils.set_shape_transparency(section_face_display, 0.8) - section_box_display = ifcopenshell.geom.utils.display_shape(self.section_box['shape']) + section_box_display = ifcopenshell.geom.utils.display_shape(self.section_box["shape"]) ifcopenshell.geom.utils.set_shape_transparency(section_box_display, 0.5) - transformed_box = BRepBuilderAPI.BRepBuilderAPI_Transform( - self.section_box['shape'], self.transformation) + transformed_box = BRepBuilderAPI.BRepBuilderAPI_Transform(self.section_box["shape"], self.transformation) box_display = ifcopenshell.geom.utils.display_shape(transformed_box.Shape()) ifcopenshell.geom.utils.set_shape_transparency(box_display, 0.2) for shape in self.product_shapes: ifcopenshell.geom.utils.display_shape(shape[1]) - input('Debug: showing everything with section plane.') + input("Debug: showing everything with section plane.") def display_cut_polygons(self): self.occ_display.EraseAll() for polygon in self.cut_polygons: - ifcopenshell.geom.utils.display_shape(polygon['geometry'], clr='BLACK') - face = BRepBuilderAPI.BRepBuilderAPI_MakeFace(polygon['geometry']).Face() + ifcopenshell.geom.utils.display_shape(polygon["geometry"], clr="BLACK") + face = BRepBuilderAPI.BRepBuilderAPI_MakeFace(polygon["geometry"]).Face() face_display = ifcopenshell.geom.utils.display_shape(face) ifcopenshell.geom.utils.set_shape_transparency(face_display, 0.5) - input('Debug: showing cut polygons.') + input("Debug: showing cut polygons.") def display_background_elements(self): self.occ_display.EraseAll() for element in self.background_elements: - if element['type'] == 'line': - ifcopenshell.geom.utils.display_shape(element['geometry'], clr='PURPLE') - elif element['type'] == 'polyline': - ifcopenshell.geom.utils.display_shape(element['geometry_face'], clr='RED') - elif element['type'] == 'polygon': - ifcopenshell.geom.utils.display_shape(element['geometry_face']) - input('Debug: showing background elements.') + if element["type"] == "line": + ifcopenshell.geom.utils.display_shape(element["geometry"], clr="PURPLE") + elif element["type"] == "polyline": + ifcopenshell.geom.utils.display_shape(element["geometry_face"], clr="RED") + elif element["type"] == "polygon": + ifcopenshell.geom.utils.display_shape(element["geometry_face"]) + input("Debug: showing background elements.") diff --git a/src/ifcblenderexport/blenderbim/bim/export_ifc.py b/src/ifcblenderexport/blenderbim/bim/export_ifc.py index 2af4040765..3e8f1edf23 100644 --- a/src/ifcblenderexport/blenderbim/bim/export_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/export_ifc.py @@ -14,12 +14,13 @@ from . import ifc import ifcopenshell import addon_utils + class ArrayModifier: count: int offset: Vector -class IfcParser(): +class IfcParser: def __init__(self, ifc_export_settings, qto_calculator): self.data_dir = ifc_export_settings.data_dir self.qto_calculator = qto_calculator @@ -89,7 +90,7 @@ class IfcParser(): self.projects = self.get_projects() self.project = self.projects[0] if not selected_objects: - selected_objects = self.get_all_objects_in_project(self.project['raw']) + selected_objects = self.get_all_objects_in_project(self.project["raw"]) self.units = self.get_units() self.unit_scale = self.get_unit_scale() self.people = self.get_people() @@ -127,56 +128,58 @@ class IfcParser(): def get_units(self): units = { - 'length': { - 'ifc': None, - 'is_metric': bpy.context.scene.unit_settings.system != 'IMPERIAL', - 'raw': bpy.context.scene.unit_settings.length_unit + "length": { + "ifc": None, + "is_metric": bpy.context.scene.unit_settings.system != "IMPERIAL", + "raw": bpy.context.scene.unit_settings.length_unit, }, - 'area': { - 'ifc': None, - 'is_metric': bpy.context.scene.unit_settings.system != 'IMPERIAL', - 'raw': bpy.context.scene.unit_settings.length_unit + "area": { + "ifc": None, + "is_metric": bpy.context.scene.unit_settings.system != "IMPERIAL", + "raw": bpy.context.scene.unit_settings.length_unit, }, - 'volume': { - 'ifc': None, - 'is_metric': bpy.context.scene.unit_settings.system != 'IMPERIAL', - 'raw': bpy.context.scene.unit_settings.length_unit - }} + "volume": { + "ifc": None, + "is_metric": bpy.context.scene.unit_settings.system != "IMPERIAL", + "raw": bpy.context.scene.unit_settings.length_unit, + }, + } for data in units.values(): - if data['raw'] == 'ADAPTIVE': - if data['is_metric']: - data['raw'] = 'METERS' + if data["raw"] == "ADAPTIVE": + if data["is_metric"]: + data["raw"] = "METERS" else: - data['raw'] = 'FEET' + data["raw"] = "FEET" return units def get_unit_scale(self): conversions = { - 'KILOMETERS': 1e3, - 'CENTIMETERS': 1e-2, - 'MILLIMETERS': 1e-3, - 'MICROMETERS': 1e-6, - 'FEET': 0.3048, - 'INCHES': 0.0254} - if bpy.context.scene.unit_settings.system in {'METRIC', 'IMPERIAL'}: + "KILOMETERS": 1e3, + "CENTIMETERS": 1e-2, + "MILLIMETERS": 1e-3, + "MICROMETERS": 1e-6, + "FEET": 0.3048, + "INCHES": 0.0254, + } + if bpy.context.scene.unit_settings.system in {"METRIC", "IMPERIAL"}: scale = bpy.context.scene.unit_settings.scale_length else: scale = 1 - if self.units['length']['raw'] in conversions.keys(): - scale *= conversions[self.units['length']['raw']] + if self.units["length"]["raw"] in conversions.keys(): + scale *= conversions[self.units["length"]["raw"]] return scale def get_object_attributes(self, obj): - attributes = {'Name': self.get_ifc_name(obj.name)} - global_id_index = obj.BIMObjectProperties.attributes.find('GlobalId') + attributes = {"Name": self.get_ifc_name(obj.name)} + global_id_index = obj.BIMObjectProperties.attributes.find("GlobalId") if global_id_index == -1: global_id = obj.BIMObjectProperties.attributes.add() - global_id.name = 'GlobalId' + global_id.name = "GlobalId" global_id.string_value = ifcopenshell.guid.new() elif obj.BIMObjectProperties.attributes[global_id_index].string_value in self.global_ids: obj.BIMObjectProperties.attributes[global_id_index].string_value = ifcopenshell.guid.new() attributes.update({a.name: a.string_value for a in obj.BIMObjectProperties.attributes}) - self.global_ids.append(attributes['GlobalId']) + self.global_ids.append(attributes["GlobalId"]) return attributes def get_products(self): @@ -185,42 +188,34 @@ class IfcParser(): self.resolve_modifiers(product) def resolve_modifiers(self, product): - obj = product['raw'] - if obj.data \ - and hasattr(obj.data, 'BIMMeshProperties') \ - and not obj.data.BIMMeshProperties.is_parametric: + obj = product["raw"] + if obj.data and hasattr(obj.data, "BIMMeshProperties") and not obj.data.BIMMeshProperties.is_parametric: return - instance_objects = [(obj, { - 'location': obj.matrix_world.translation, - 'array_offset': Vector((0, 0, 0)), - 'scale': obj.scale - })] + instance_objects = [ + (obj, {"location": obj.matrix_world.translation, "array_offset": Vector((0, 0, 0)), "scale": obj.scale}) + ] for modifier in obj.modifiers: created_instances = [] - if modifier.type == 'ARRAY': - instance_objects.extend( - self.resolve_array_modifier(product, modifier, instance_objects) - ) - elif modifier.type == 'MIRROR': - instance_objects.extend( - self.resolve_mirror_modifier(product, modifier, instance_objects) - ) + if modifier.type == "ARRAY": + instance_objects.extend(self.resolve_array_modifier(product, modifier, instance_objects)) + elif modifier.type == "MIRROR": + instance_objects.extend(self.resolve_mirror_modifier(product, modifier, instance_objects)) def get_array_modifier(self, product, modifier): - obj = product['raw'] + obj = product["raw"] array = ArrayModifier() world_rotation = obj.matrix_world.decompose()[1] array.offset = world_rotation @ Vector( ( modifier.constant_offset_displace[0], modifier.constant_offset_displace[1], - modifier.constant_offset_displace[2] + modifier.constant_offset_displace[2], ) ) - if modifier.fit_type == 'FIXED_COUNT': + if modifier.fit_type == "FIXED_COUNT": array.count = modifier.count - elif modifier.fit_type == 'FIT_LENGTH': + elif modifier.fit_type == "FIT_LENGTH": array.count = int(modifier.fit_length / array.offset.length) return array @@ -230,19 +225,19 @@ class IfcParser(): for obj in instance_objects: for n in range(modifier.count - 1): override = obj[1].copy() - override['array_offset'] = ((n + 1) * modifier.offset) - override['location'] = obj[1]['location'].copy() - location = override['location'] + ((n + 1) * modifier.offset) - override['location'] = location + override["array_offset"] = (n + 1) * modifier.offset + override["location"] = obj[1]["location"].copy() + location = override["location"] + ((n + 1) * modifier.offset) + override["location"] = location self.add_product( self.get_product( - {'raw': obj[0], 'metadata': product['metadata']}, + {"raw": obj[0], "metadata": product["metadata"]}, metadata_override=override, - attribute_override={'GlobalId': self.get_parametric_global_id( - product['raw'], - len(instance_objects)+len(created_instances)-1 + attribute_override={ + "GlobalId": self.get_parametric_global_id( + product["raw"], len(instance_objects) + len(created_instances) - 1 ) - } + }, ) ) created_instances.append((obj[0], override)) @@ -258,26 +253,26 @@ class IfcParser(): axis_instances = [] for obj in instance_objects: override = obj[1].copy() - override['has_scale'] = True - override['has_mirror'] = True - override['scale'] = obj[1]['scale'].copy() - override['scale'][mirror] *= -1 + override["has_scale"] = True + override["has_mirror"] = True + override["scale"] = obj[1]["scale"].copy() + override["scale"][mirror] *= -1 mirror_axis = Vector((0, 0, 0)) mirror_axis[mirror] = 1 world_rotation = obj[0].matrix_world.decompose()[1].to_matrix().to_4x4() - unrotated_offset = world_rotation.inverted() @ override['array_offset'] + unrotated_offset = world_rotation.inverted() @ override["array_offset"] mirrored_offset = unrotated_offset @ Matrix.Scale(-1, 4, mirror_axis) rotated_offset = world_rotation @ mirrored_offset - override['location'] = override['location'] - override['array_offset'] + rotated_offset + override["location"] = override["location"] - override["array_offset"] + rotated_offset self.add_product( self.get_product( - {'raw': obj[0], 'metadata': product['metadata']}, + {"raw": obj[0], "metadata": product["metadata"]}, metadata_override=override, - attribute_override={'GlobalId': self.get_parametric_global_id( - product['raw'], - len(instance_objects)+len(created_instances)-1 + attribute_override={ + "GlobalId": self.get_parametric_global_id( + product["raw"], len(instance_objects) + len(created_instances) - 1 ) - } + }, ) ) created_instances.append((obj[0], override)) @@ -287,7 +282,7 @@ class IfcParser(): def resolve_product_relationships(self): for i, product in enumerate(self.products): - obj = product['raw'] + obj = product["raw"] self.resolve_voids_and_fills(i, obj) self.resolve_structural_connections(i, obj) @@ -295,25 +290,24 @@ class IfcParser(): if not obj.BIMObjectProperties.structural_member_connection: return self.rel_connects_structural_member[i] = self.get_product_index_from_raw_name( - obj.BIMObjectProperties.structural_member_connection.name) + obj.BIMObjectProperties.structural_member_connection.name + ) def resolve_voids_and_fills(self, i, obj): for m in obj.modifiers: - if m.type != 'BOOLEAN' or m.object is None: + if m.type != "BOOLEAN" or m.object is None: continue void_or_projection = self.get_product_index_from_raw_name(m.object.name) if void_or_projection is None: continue - if m.operation == 'DIFFERENCE' \ - and self.get_ifc_class(m.object.name) == 'IfcOpeningElement': + if m.operation == "DIFFERENCE" and self.get_ifc_class(m.object.name) == "IfcOpeningElement": self.rel_voids_elements.setdefault(i, []).append(void_or_projection) if not m.object.parent: continue fill = self.get_product_index_from_raw_name(m.object.parent.name) if fill: self.rel_fills_elements.setdefault(void_or_projection, []).append(fill) - elif m.operation == 'UNION' \ - and self.get_ifc_class(m.object.name) == 'IfcProjectionElement': + elif m.operation == "UNION" and self.get_ifc_class(m.object.name) == "IfcProjectionElement": self.rel_projects_elements.setdefault(i, []).append(void_or_projection) def get_axis(self, matrix, axis): @@ -330,86 +324,90 @@ class IfcParser(): def add_product(self, product): self.products.append(product) - self.product_name_index_map[product['raw'].name] = self.product_index + self.product_name_index_map[product["raw"].name] = self.product_index self.product_index += 1 def get_product_index_from_raw_name(self, name): for index, product in enumerate(self.products): - if product['raw'].name == name: + if product["raw"].name == name: return index def append_product_attributes(self, product, obj): - product.update({ - 'location': obj.matrix_world.translation, - 'up_axis': self.get_axis(obj.matrix_world, 2), - 'forward_axis': self.get_axis(obj.matrix_world, 0), - 'right_axis': self.get_axis(obj.matrix_world, 1), - 'has_scale': (obj.scale - Vector((1, 1, 1))).length > 0.01, - 'has_mirror': False, - 'array_offset': Vector((0, 0, 0)), - 'scale': obj.scale, - 'representations': self.get_object_representation_names(obj) - }) + product.update( + { + "location": obj.matrix_world.translation, + "up_axis": self.get_axis(obj.matrix_world, 2), + "forward_axis": self.get_axis(obj.matrix_world, 0), + "right_axis": self.get_axis(obj.matrix_world, 1), + "has_scale": (obj.scale - Vector((1, 1, 1))).length > 0.01, + "has_mirror": False, + "array_offset": Vector((0, 0, 0)), + "scale": obj.scale, + "representations": self.get_object_representation_names(obj), + } + ) def get_product(self, selected_product, metadata_override={}, attribute_override={}): - obj = selected_product['raw'] + obj = selected_product["raw"] product = { - 'ifc': None, - 'raw': obj, - 'class': self.get_ifc_class(obj.name), - 'attributes': self.get_object_attributes(obj), - 'relating_structure': None, - 'relating_host': None, - 'relating_qtos_key': None, - 'has_boundary_condition': obj.BIMObjectProperties.has_boundary_condition, - 'boundary_condition_class': None, - 'boundary_condition_attributes': {}, - 'structural_member_connection': None + "ifc": None, + "raw": obj, + "class": self.get_ifc_class(obj.name), + "attributes": self.get_object_attributes(obj), + "relating_structure": None, + "relating_host": None, + "relating_qtos_key": None, + "has_boundary_condition": obj.BIMObjectProperties.has_boundary_condition, + "boundary_condition_class": None, + "boundary_condition_attributes": {}, + "structural_member_connection": None, } self.append_product_attributes(product, obj) - product['attributes'].update(attribute_override) + product["attributes"].update(attribute_override) product.update(metadata_override) type_product = obj.BIMObjectProperties.relating_type - if type_product \ - and self.is_a_type(self.get_ifc_class(type_product.name)): + if type_product and self.is_a_type(self.get_ifc_class(type_product.name)): reference = self.get_type_product_reference(type_product.name) self.rel_defines_by_type.setdefault(reference, []).append(self.product_index) - if product['has_boundary_condition']: - product['boundary_condition_class'] = obj.BIMObjectProperties.boundary_condition.name - product['boundary_condition_attributes'] = {a.name: a.string_value - for a in obj.BIMObjectProperties.boundary_condition.attributes} + if product["has_boundary_condition"]: + product["boundary_condition_class"] = obj.BIMObjectProperties.boundary_condition.name + product["boundary_condition_attributes"] = { + a.name: a.string_value for a in obj.BIMObjectProperties.boundary_condition.attributes + } self.get_product_relating_structure(product, obj) - if 'IfcRelNests' in obj.constraints: + if "IfcRelNests" in obj.constraints: # TODO: I think get_product_index_from_raw_name should not be used - parent_product_index = self.get_product_index_from_raw_name( - obj.constraints['IfcRelNests'].target.name) + parent_product_index = self.get_product_index_from_raw_name(obj.constraints["IfcRelNests"].target.name) self.rel_nests.setdefault(parent_product_index, []).append(product) - product['relating_host'] = parent_product_index + product["relating_host"] = parent_product_index for name, constraint in obj.constraints.items(): - if 'IfcRelSpaceBoundary' not in name: + if "IfcRelSpaceBoundary" not in name: continue - self.rel_space_boundaries.setdefault(self.product_index, []).append({ - 'ifc': None, - 'class': self.get_ifc_class(name), - 'related_building_element_raw_name': constraint.target.name, - 'connection_geometry_face_index': name.split('/')[1], - 'attributes': { - 'PhysicalOrVirtualBoundary': name.split('/')[2], - 'InternalOrExternalBoundary': name.split('/')[3] + self.rel_space_boundaries.setdefault(self.product_index, []).append( + { + "ifc": None, + "class": self.get_ifc_class(name), + "related_building_element_raw_name": constraint.target.name, + "connection_geometry_face_index": name.split("/")[1], + "attributes": { + "PhysicalOrVirtualBoundary": name.split("/")[2], + "InternalOrExternalBoundary": name.split("/")[3], + }, } - }) + ) - if obj.instance_type == 'COLLECTION' \ - and self.is_a_rel_aggregates(self.get_ifc_class(obj.instance_collection.name)): + if obj.instance_type == "COLLECTION" and self.is_a_rel_aggregates( + self.get_ifc_class(obj.instance_collection.name) + ): self.rel_aggregates[self.product_index] = obj.name - if 'rel_aggregates_relating_object' in selected_product['metadata']: - relating_object = selected_product['metadata']['rel_aggregates_relating_object'] + if "rel_aggregates_relating_object" in selected_product["metadata"]: + relating_object = selected_product["metadata"]["rel_aggregates_relating_object"] self.aggregates.setdefault(relating_object.name, []).append(self.product_index) if obj.name in self.qtos: @@ -419,29 +417,25 @@ class IfcParser(): self.get_product_psets_qtos(product, obj, is_qto=True) for reference in obj.BIMObjectProperties.document_references: - self.rel_associates_document_object.setdefault( - reference.name, []).append(product) + self.rel_associates_document_object.setdefault(reference.name, []).append(product) for classification in obj.BIMObjectProperties.classifications: - self.rel_associates_classification_object.setdefault( - classification.name, []).append(product) + self.rel_associates_classification_object.setdefault(classification.name, []).append(product) for constraint in obj.BIMObjectProperties.constraints: - self.rel_associates_constraint_object.setdefault( - constraint.name, []).append(product) + self.rel_associates_constraint_object.setdefault(constraint.name, []).append(product) for slot in obj.material_slots: - if slot.material is None or slot.link == 'OBJECT': + if slot.material is None or slot.link == "OBJECT": continue - if obj.BIMObjectProperties.material_type == 'IfcMaterialLayerSet': - self.rel_associates_material_layer_set.setdefault(self.product_index, []).append( - slot.material.name) - elif obj.BIMObjectProperties.material_type == 'IfcMaterialConstituentSet': + if obj.BIMObjectProperties.material_type == "IfcMaterialLayerSet": + self.rel_associates_material_layer_set.setdefault(self.product_index, []).append(slot.material.name) + elif obj.BIMObjectProperties.material_type == "IfcMaterialConstituentSet": self.rel_associates_material_constituent_set.setdefault(self.product_index, []).append( - slot.material.name) - elif obj.BIMObjectProperties.material_type == 'IfcMaterialProfileSet': - self.rel_associates_material_profile_set.setdefault(self.product_index, []).append( - slot.material.name) + slot.material.name + ) + elif obj.BIMObjectProperties.material_type == "IfcMaterialProfileSet": + self.rel_associates_material_profile_set.setdefault(self.product_index, []).append(slot.material.name) else: self.rel_associates_material.setdefault(slot.material.name, []).append(product) @@ -455,20 +449,16 @@ class IfcParser(): if is_qto: psets_qtos = obj.BIMObjectProperties.qtos if not psets_qtos and self.ifc_export_settings.should_guess_quantities: - self.add_automatic_qtos(product['class'], obj) + self.add_automatic_qtos(product["class"], obj) psets_qtos = obj.BIMObjectProperties.qtos results = self.qtos relationships = self.rel_defines_by_qto for item in psets_qtos: - item_key = '{}/{}'.format(item.name, obj.name) + item_key = "{}/{}".format(item.name, obj.name) raw = {p.name: p.string_value for p in item.properties if p.string_value} if not raw: continue - results[item_key] = { - 'ifc': None, - 'raw': raw, - 'attributes': { 'Name': item.name } - } + results[item_key] = {"ifc": None, "raw": raw, "attributes": {"Name": item.name}} relationships.setdefault(item_key, []).append(product) def add_automatic_qtos(self, ifc_class, obj): @@ -479,7 +469,7 @@ class IfcParser(): if name not in schema.ifc.qtos: continue has_automatic_value = False - props = schema.ifc.qtos[name]['HasPropertyTemplates'].keys() + props = schema.ifc.qtos[name]["HasPropertyTemplates"].keys() guessed_values = {} for prop_name in props: value = self.qto_calculator.guess_quantity(prop_name, props, obj) @@ -509,9 +499,9 @@ class IfcParser(): if relating_structure: reference = self.get_spatial_structure_element_reference(relating_structure.name) self.rel_contained_in_spatial_structure.setdefault(reference, []).append(self.product_index) - product['relating_structure'] = reference + product["relating_structure"] = reference return - for collection in product['raw'].users_collection: + for collection in product["raw"].users_collection: self.parse_product_collection(product, collection) def parse_product_collection(self, product, collection): @@ -521,7 +511,7 @@ class IfcParser(): if self.is_a_spatial_structure_element(class_name): reference = self.get_spatial_structure_element_reference(collection.name) self.rel_contained_in_spatial_structure.setdefault(reference, []).append(self.product_index) - product['relating_structure'] = reference + product["relating_structure"] = reference elif self.is_a_group(class_name): reference = self.get_group_reference(collection.name) self.rel_assigns_to_group.setdefault(reference, []).append(self.product_index) @@ -565,7 +555,7 @@ class IfcParser(): for obj in selected_objects: if obj.BIMObjectProperties.relating_type: added_objs.append(obj.BIMObjectProperties.relating_type) - if obj.instance_type == 'COLLECTION': + if obj.instance_type == "COLLECTION": for obj2 in obj.instance_collection.objects: if obj2.BIMObjectProperties.relating_type: added_objs.append(obj2.BIMObjectProperties.relating_type) @@ -576,73 +566,73 @@ class IfcParser(): if not metadata: metadata = {} for obj in objects_to_sort: - if obj.name[0:3] != 'Ifc': + if obj.name[0:3] != "Ifc": continue elif self.is_a_grid_axis(self.get_ifc_class(obj.name)): - self.selected_grid_axes.append({'raw': obj, 'metadata': metadata}) + self.selected_grid_axes.append({"raw": obj, "metadata": metadata}) elif self.is_a_spatial_structure_element(self.get_ifc_class(obj.name)): - self.selected_spatial_structure_elements.append({'raw': obj, 'metadata': metadata}) + self.selected_spatial_structure_elements.append({"raw": obj, "metadata": metadata}) elif self.is_a_type(self.get_ifc_class(obj.name)): - self.selected_types.append({'raw': obj, 'metadata': metadata}) + self.selected_types.append({"raw": obj, "metadata": metadata}) elif self.is_a_group(self.get_ifc_class(obj.name)): - self.selected_groups.append({'raw': obj, 'metadata': metadata}) - elif obj.instance_type == 'COLLECTION': + self.selected_groups.append({"raw": obj, "metadata": metadata}) + elif obj.instance_type == "COLLECTION": self.categorise_selected_objects( - obj.instance_collection.objects, - {'rel_aggregates_relating_object': obj} + obj.instance_collection.objects, {"rel_aggregates_relating_object": obj} ) - self.selected_products.append({'raw': obj, 'metadata': metadata}) - elif self.is_a_project(self.get_ifc_class(obj.name)) \ - or self.is_a_library(self.get_ifc_class(obj.name)): + self.selected_products.append({"raw": obj, "metadata": metadata}) + elif self.is_a_project(self.get_ifc_class(obj.name)) or self.is_a_library(self.get_ifc_class(obj.name)): pass elif not self.is_a_library(self.get_ifc_class(obj.users_collection[0].name)): - self.selected_products.append({'raw': obj, 'metadata': metadata}) + self.selected_products.append({"raw": obj, "metadata": metadata}) def get_material_psets(self): psets = {} - for filename in Path(self.data_dir + 'material/').glob('**/*.csv'): - with open(filename, 'r') as f: + for filename in Path(self.data_dir + "material/").glob("**/*.csv"): + with open(filename, "r") as f: description = filename.parts[-2] name = filename.stem if description not in psets: psets[description] = {} psets[description][name] = { - 'ifc': None, - 'raw': {x[0]: x[1] for x in list(csv.reader(f))}, - 'attributes': { - 'Name': name, - 'Description': description} + "ifc": None, + "raw": {x[0]: x[1] for x in list(csv.reader(f))}, + "attributes": {"Name": name, "Description": description}, } return psets def get_door_attributes(self): - return self.get_predefined_attributes('door') + return self.get_predefined_attributes("door") def get_window_attributes(self): - return self.get_predefined_attributes('window') + return self.get_predefined_attributes("window") def get_predefined_attributes(self, attr): results = {} - for filename in Path(self.data_dir + attr + '/').glob('**/*.csv'): - with open(filename, 'r') as f: + for filename in Path(self.data_dir + attr + "/").glob("**/*.csv"): + with open(filename, "r") as f: type_name = filename.parts[-2] pset_name = filename.stem - results.setdefault(type_name, []).append({ - 'ifc': None, - 'raw': {x[0]: x[1] for x in list(csv.reader(f))}, - 'pset_name': pset_name.split('.')[0] - }) + results.setdefault(type_name, []).append( + { + "ifc": None, + "raw": {x[0]: x[1] for x in list(csv.reader(f))}, + "pset_name": pset_name.split(".")[0], + } + ) return results def get_classifications(self): results = {} for classification in bpy.context.scene.BIMProperties.classifications: if classification.name not in schema.ifc.classification_files: - schema.ifc.classification_files[classification.name] = ifcopenshell.file.from_string(classification.data) + schema.ifc.classification_files[classification.name] = ifcopenshell.file.from_string( + classification.data + ) results[classification.name] = { - 'ifc': None, - 'raw': classification, - 'raw_element': schema.ifc.classification_files[classification.name].by_type('IfcClassification')[0] + "ifc": None, + "raw": classification, + "raw_element": schema.ifc.classification_files[classification.name].by_type("IfcClassification")[0], } return results @@ -650,64 +640,56 @@ class IfcParser(): results = {} for name, classification in self.classifications.items(): ifc_file = schema.ifc.classification_files[name] - if ifc_file.schema == 'IFC2X3': - results[name] = { e.ItemReference: e for e in ifc_file.by_type('IfcClassificationReference')} + if ifc_file.schema == "IFC2X3": + results[name] = {e.ItemReference: e for e in ifc_file.by_type("IfcClassificationReference")} else: - results[name] = { e.Identification: e for e in ifc_file.by_type('IfcClassificationReference')} + results[name] = {e.Identification: e for e in ifc_file.by_type("IfcClassificationReference")} return results def get_classification_references(self): results = {} - for product in self.selected_products \ - + self.selected_types \ - + self.selected_spatial_structure_elements: - for reference in product['raw'].BIMObjectProperties.classifications: + for product in self.selected_products + self.selected_types + self.selected_spatial_structure_elements: + for reference in product["raw"].BIMObjectProperties.classifications: results[reference.name] = { - 'ifc': None, - 'raw': reference, - 'raw_element': self.classification_reference_maps[reference.referenced_source][reference.name] - + "ifc": None, + "raw": reference, + "raw_element": self.classification_reference_maps[reference.referenced_source][reference.name], } return results def get_constraints(self): results = {} data_map = { - 'name': 'Name', - 'description': 'Description', - 'constraint_grade': 'ConstraintGrade', - 'constraint_source': 'ConstraintSource', - 'user_defined_grade': 'UserDefinedGrade', - 'objective_qualifier': 'ObjectiveQualifier', - 'user_defined_qualifier': 'UserDefinedQualifier', + "name": "Name", + "description": "Description", + "constraint_grade": "ConstraintGrade", + "constraint_source": "ConstraintSource", + "user_defined_grade": "UserDefinedGrade", + "objective_qualifier": "ObjectiveQualifier", + "user_defined_qualifier": "UserDefinedQualifier", } for constraint in bpy.context.scene.BIMProperties.constraints: attributes = {} for key, value in data_map.items(): if getattr(constraint, key): attributes[value] = getattr(constraint, key) - results[constraint.name] = { - 'ifc': None, - 'raw': constraint, - 'attributes': attributes - } + results[constraint.name] = {"ifc": None, "raw": constraint, "attributes": attributes} return results def get_people(self): data_map = { - 'name': 'Identification', - 'family_name': 'FamilyName', - 'given_name': 'GivenName', + "name": "Identification", + "family_name": "FamilyName", + "given_name": "GivenName", } list_data_map = { - 'middle_names': 'MiddleNames', - 'prefix_titles': 'PrefixTitles', - 'suffix_titles': 'SuffixTitles', + "middle_names": "MiddleNames", + "prefix_titles": "PrefixTitles", + "suffix_titles": "SuffixTitles", } results = [] - if self.ifc_export_settings.schema == 'IFC2X3' \ - and not bpy.context.scene.BIMProperties.people: + if self.ifc_export_settings.schema == "IFC2X3" and not bpy.context.scene.BIMProperties.people: bpy.ops.bim.add_person() for person in bpy.context.scene.BIMProperties.people: @@ -717,25 +699,26 @@ class IfcParser(): attributes[value] = getattr(person, key) for key, value in list_data_map.items(): if getattr(person, key): - attributes[value] = getattr(person, key).split(',') - results.append({ - 'ifc': None, - 'raw': person, - 'attributes': attributes, - 'roles': self.get_roles(person.roles), - 'addresses': self.get_addresses(person.addresses) - }) + attributes[value] = getattr(person, key).split(",") + results.append( + { + "ifc": None, + "raw": person, + "attributes": attributes, + "roles": self.get_roles(person.roles), + "addresses": self.get_addresses(person.addresses), + } + ) return results def get_organisations(self): data_map = { - 'name': 'Name', - 'description': 'Description', + "name": "Name", + "description": "Description", } results = [] - if self.ifc_export_settings.schema == 'IFC2X3' \ - and not bpy.context.scene.BIMProperties.organisations: + if self.ifc_export_settings.schema == "IFC2X3" and not bpy.context.scene.BIMProperties.organisations: bpy.ops.bim.add_organisation() for organisation in bpy.context.scene.BIMProperties.organisations: @@ -743,20 +726,22 @@ class IfcParser(): for key, value in data_map.items(): if getattr(organisation, key): attributes[value] = getattr(organisation, key) - results.append({ - 'ifc': None, - 'raw': organisation, - 'attributes': attributes, - 'roles': self.get_roles(organisation.roles), - 'addresses': self.get_addresses(organisation.addresses) - }) + results.append( + { + "ifc": None, + "raw": organisation, + "attributes": attributes, + "roles": self.get_roles(organisation.roles), + "addresses": self.get_addresses(organisation.addresses), + } + ) return results def get_roles(self, roles): data_map = { - 'name': 'Role', - 'user_defined_role': 'UserDefinedRole', - 'description': 'Description', + "name": "Role", + "user_defined_role": "UserDefinedRole", + "description": "Description", } results = [] for role in roles: @@ -764,11 +749,7 @@ class IfcParser(): for key, value in data_map.items(): if getattr(role, key): attributes[value] = getattr(role, key) - results.append({ - 'ifc': None, - 'raw': role, - 'attributes': attributes - }) + results.append({"ifc": None, "raw": role, "attributes": attributes}) return results def get_addresses(self, addresses): @@ -779,67 +760,67 @@ class IfcParser(): def get_address(self, address): address_data_map = { - 'purpose': 'Purpose', - 'description': 'Description', - 'user_defined_purpose': 'UserDefinedPurpose', + "purpose": "Purpose", + "description": "Description", + "user_defined_purpose": "UserDefinedPurpose", } postal_data_map = { - 'internal_location': 'InternalLocation', - 'postal_box': 'PostalBox', - 'town': 'Town', - 'region': 'Region', - 'postal_code': 'PostalCode', - 'country': 'Country', + "internal_location": "InternalLocation", + "postal_box": "PostalBox", + "town": "Town", + "region": "Region", + "postal_code": "PostalCode", + "country": "Country", } telecom_data_map = { - 'pager_number': 'PagerNumber', - 'www_home_page_url': 'WWWHomePageURL', + "pager_number": "PagerNumber", + "www_home_page_url": "WWWHomePageURL", } telecom_list_data_map = { - 'telephone_numbers': 'TelephoneNumbers', - 'fascimile_numbers': 'FascimileNumbers', - 'electronic_mail_addresses': 'ElectronicMailAddresses', - 'messaging_ids': 'MessagingIDs', + "telephone_numbers": "TelephoneNumbers", + "fascimile_numbers": "FascimileNumbers", + "electronic_mail_addresses": "ElectronicMailAddresses", + "messaging_ids": "MessagingIDs", } attributes = {} - if 'IfcPostalAddress' in address.name: + if "IfcPostalAddress" in address.name: merged_data_map = {**address_data_map, **postal_data_map} if address.address_lines: - attributes['AddressLines'] = address.address_lines.split('/') - elif 'IfcTelecomAddress' in address.name: + attributes["AddressLines"] = address.address_lines.split("/") + elif "IfcTelecomAddress" in address.name: merged_data_map = {**address_data_map, **telecom_data_map} for key, value in telecom_list_data_map.items(): if getattr(address, key): - attributes[value] = getattr(address, key).split(',') + attributes[value] = getattr(address, key).split(",") for key, value in merged_data_map.items(): if getattr(address, key): attributes[value] = getattr(address, key) return { - 'ifc': None, - 'raw': address, - 'is_postal': 'IfcPostalAddress' in address.name, - 'is_telecom': 'IfcTelecomAddress' in address.name, - 'attributes': attributes + "ifc": None, + "raw": address, + "is_postal": "IfcPostalAddress" in address.name, + "is_telecom": "IfcTelecomAddress" in address.name, + "attributes": attributes, } def get_document_references(self): results = {} for reference in bpy.context.scene.BIMProperties.document_references: data_map = { - 'name': 'Identification', - 'human_name': 'Name', - 'description': 'Description', - 'location': 'Location' + "name": "Identification", + "human_name": "Name", + "description": "Description", + "location": "Location", } attributes = {} for key, value in data_map.items(): if getattr(reference, key): attributes[value] = getattr(reference, key) results[reference.name] = { - 'ifc': None, - 'raw': reference, - 'referenced_document': reference.referenced_document, - 'attributes': attributes + "ifc": None, + "raw": reference, + "referenced_document": reference.referenced_document, + "attributes": attributes, } return results @@ -847,31 +828,27 @@ class IfcParser(): results = {} for information in bpy.context.scene.BIMProperties.document_information: data_map = { - 'name': 'Identification', - 'human_name': 'Name', - 'description': 'Description', - 'location': 'Location', - 'purpose': 'Purpose', - 'intended_use': 'IntendedUse', - 'scope': 'Scope', - 'revision': 'Revision', - 'creation_time': 'CreationTime', - 'last_revision_time': 'LastRevisionTime', - 'electronic_format': 'ElectronicFormat', - 'valid_from': 'ValidFrom', - 'valid_until': 'ValidUntil', - 'confidentiality': 'Confidentiality', - 'status': 'Status' + "name": "Identification", + "human_name": "Name", + "description": "Description", + "location": "Location", + "purpose": "Purpose", + "intended_use": "IntendedUse", + "scope": "Scope", + "revision": "Revision", + "creation_time": "CreationTime", + "last_revision_time": "LastRevisionTime", + "electronic_format": "ElectronicFormat", + "valid_from": "ValidFrom", + "valid_until": "ValidUntil", + "confidentiality": "Confidentiality", + "status": "Status", } attributes = {} for key, value in data_map.items(): if getattr(information, key): attributes[value] = getattr(information, key) - results[information.name] = { - 'ifc': None, - 'raw': information, - 'attributes': attributes - } + results[information.name] = {"ifc": None, "raw": information, "attributes": attributes} return results def get_projects(self): @@ -879,12 +856,14 @@ class IfcParser(): for collection in bpy.data.collections: if self.is_a_project(self.get_ifc_class(collection.name)): obj = bpy.data.objects.get(collection.name) - results.append({ - 'ifc': None, - 'raw': collection, - 'class': self.get_ifc_class(collection.name), - 'attributes': self.get_object_attributes(obj) - }) + results.append( + { + "ifc": None, + "raw": collection, + "class": self.get_ifc_class(collection.name), + "attributes": self.get_object_attributes(obj), + } + ) return results def get_all_objects_in_project(self, collection): @@ -897,29 +876,29 @@ class IfcParser(): def setup_project(self): bpy.ops.bim.quick_project_setup() for collection in bpy.data.collections: - if collection.name == 'IfcBuildingStorey/Ground Floor': + if collection.name == "IfcBuildingStorey/Ground Floor": break for obj in bpy.context.selected_objects: - if hasattr(obj, 'data') \ - and isinstance(obj.data, bpy.types.Mesh) \ - and '/' not in obj.name: - obj.name = 'IfcBuildingElementProxy/{}'.format(obj.name) + if hasattr(obj, "data") and isinstance(obj.data, bpy.types.Mesh) and "/" not in obj.name: + obj.name = "IfcBuildingElementProxy/{}".format(obj.name) for user_collection in obj.users_collection: user_collection.objects.unlink(obj) collection.objects.link(obj) def get_libraries(self): results = [] - for collection in self.project['raw'].children: + for collection in self.project["raw"].children: if not self.is_a_library(self.get_ifc_class(collection.name)): continue - results.append({ - 'ifc': None, - 'raw': collection, - 'class': self.get_ifc_class(collection.name), - 'rel_declares_type_products': [], - 'attributes': self.get_object_attributes(collection) - }) + results.append( + { + "ifc": None, + "raw": collection, + "class": self.get_ifc_class(collection.name), + "rel_declares_type_products": [], + "attributes": self.get_object_attributes(collection), + } + ) return results def get_map_conversion(self): @@ -927,15 +906,15 @@ class IfcParser(): if not scene.BIMProperties.has_georeferencing: return {} return { - 'ifc': None, - 'attributes': { - 'Eastings': float(scene.MapConversion.eastings), - 'Northings': float(scene.MapConversion.northings), - 'OrthogonalHeight': float(scene.MapConversion.orthogonal_height), - 'XAxisAbscissa': float(scene.MapConversion.x_axis_abscissa), - 'XAxisOrdinate': float(scene.MapConversion.x_axis_ordinate), - 'Scale': float(scene.MapConversion.scale) - } + "ifc": None, + "attributes": { + "Eastings": float(scene.MapConversion.eastings), + "Northings": float(scene.MapConversion.northings), + "OrthogonalHeight": float(scene.MapConversion.orthogonal_height), + "XAxisAbscissa": float(scene.MapConversion.x_axis_abscissa), + "XAxisOrdinate": float(scene.MapConversion.x_axis_ordinate), + "Scale": float(scene.MapConversion.scale), + }, } def get_target_crs(self): @@ -943,16 +922,16 @@ class IfcParser(): if not scene.BIMProperties.has_georeferencing: return {} return { - 'ifc': None, - 'attributes': { - 'Name': scene.TargetCRS.name, - 'Description': scene.TargetCRS.description, - 'GeodeticDatum': scene.TargetCRS.geodetic_datum, - 'VerticalDatum': scene.TargetCRS.vertical_datum, - 'MapProjection': scene.TargetCRS.map_projection, - 'MapZone': str(scene.TargetCRS.map_zone), - 'MapUnit': scene.TargetCRS.map_unit - } + "ifc": None, + "attributes": { + "Name": scene.TargetCRS.name, + "Description": scene.TargetCRS.description, + "GeodeticDatum": scene.TargetCRS.geodetic_datum, + "VerticalDatum": scene.TargetCRS.vertical_datum, + "MapProjection": scene.TargetCRS.map_projection, + "MapZone": str(scene.TargetCRS.map_zone), + "MapUnit": scene.TargetCRS.map_unit, + }, } def get_library_information(self): @@ -960,26 +939,26 @@ class IfcParser(): if not scene.BIMProperties.has_library: return {} return { - 'ifc': None, - 'attributes': { - 'Name': scene.BIMLibrary.name, - 'Version': scene.BIMLibrary.version, - 'VersionDate': scene.BIMLibrary.version_date, - 'Location': scene.BIMLibrary.location, - 'Description': scene.BIMLibrary.description - } + "ifc": None, + "attributes": { + "Name": scene.BIMLibrary.name, + "Version": scene.BIMLibrary.version, + "VersionDate": scene.BIMLibrary.version_date, + "Location": scene.BIMLibrary.location, + "Description": scene.BIMLibrary.description, + }, } def get_spatial_structure_elements(self): elements = [] for selected_element in self.selected_spatial_structure_elements: - obj = selected_element['raw'] + obj = selected_element["raw"] element = { - 'ifc': None, - 'raw': obj, - 'class': self.get_ifc_class(obj.name), - 'attributes': self.get_object_attributes(obj), - 'address': self.get_address(obj.BIMObjectProperties.address) + "ifc": None, + "raw": obj, + "class": self.get_ifc_class(obj.name), + "attributes": self.get_object_attributes(obj), + "address": self.get_address(obj.BIMObjectProperties.address), } self.append_product_attributes(element, obj) self.get_product_psets_qtos(element, obj, is_pset=True) @@ -990,46 +969,47 @@ class IfcParser(): def get_groups(self): elements = [] for selected_element in self.selected_groups: - obj = selected_element['raw'] - elements.append({ - 'ifc': None, - 'raw': obj, - 'class': self.get_ifc_class(obj.name), - 'attributes': self.get_object_attributes(obj) - }) + obj = selected_element["raw"] + elements.append( + { + "ifc": None, + "raw": obj, + "class": self.get_ifc_class(obj.name), + "attributes": self.get_object_attributes(obj), + } + ) return elements def load_presentation_layer_assignments(self): for representation in self.representations.values(): - if representation['presentation_layer']: - self.presentation_layer_assignments.setdefault( - representation['presentation_layer'], []).append(representation) + if representation["presentation_layer"]: + self.presentation_layer_assignments.setdefault(representation["presentation_layer"], []).append( + representation + ) def load_representations(self): if not self.ifc_export_settings.has_representations: return self.generated_subcontexts = [] for context in self.ifc_export_settings.context_tree: - for subcontext in context['subcontexts']: - for target_view in subcontext['target_views']: - if context['name'] == 'Model' \ - and subcontext['name'] == 'Box' \ - and target_view == 'MODEL_VIEW': - self.generated_subcontexts = '/'.join([context['name'], subcontext['name'], target_view]) - for product in self.selected_products \ - + self.selected_types \ - + self.selected_spatial_structure_elements: + for subcontext in context["subcontexts"]: + for target_view in subcontext["target_views"]: + if context["name"] == "Model" and subcontext["name"] == "Box" and target_view == "MODEL_VIEW": + self.generated_subcontexts = "/".join([context["name"], subcontext["name"], target_view]) + for product in self.selected_products + self.selected_types + self.selected_spatial_structure_elements: self.prevent_data_name_duplicates(product) self.load_product_representations(product) def prevent_data_name_duplicates(self, product): - if product['raw'].data \ - and bpy.data.meshes.get(product['raw'].data.name) \ - and bpy.data.curves.get(product['raw'].data.name): - product['raw'].data.name += '~' + if ( + product["raw"].data + and bpy.data.meshes.get(product["raw"].data.name) + and bpy.data.curves.get(product["raw"].data.name) + ): + product["raw"].data.name += "~" def load_product_representations(self, product): - obj = product['raw'] + obj = product["raw"] if obj.data and obj.data.name in self.representations: return if isinstance(obj.data, bpy.types.Camera): @@ -1037,37 +1017,41 @@ class IfcParser(): self.append_representation_per_context(obj) def is_point_cloud(self, obj): - return hasattr(obj, 'point_cloud_visualizer') \ - and obj.point_cloud_visualizer.uuid + return hasattr(obj, "point_cloud_visualizer") and obj.point_cloud_visualizer.uuid def is_structural(self, obj): - return 'IfcStructural' in obj.name + return "IfcStructural" in obj.name def append_default_representation(self, obj): - self.representations['Model/Body/MODEL_VIEW/{}'.format(obj.data.name)] = self.get_representation( - obj.data, obj, 'Model', 'Body', 'MODEL_VIEW') - if 'Model/Box/MODEL_VIEW' in self.generated_subcontexts: - if self.ifc_export_settings.should_roundtrip_native \ - and obj.data.BIMMeshProperties.ifc_definition_id: + self.representations["Model/Body/MODEL_VIEW/{}".format(obj.data.name)] = self.get_representation( + obj.data, obj, "Model", "Body", "MODEL_VIEW" + ) + if "Model/Box/MODEL_VIEW" in self.generated_subcontexts: + if self.ifc_export_settings.should_roundtrip_native and obj.data.BIMMeshProperties.ifc_definition_id: return - self.representations['Model/Box/MODEL_VIEW/{}'.format(obj.data.name)] = self.get_representation( - obj.data, obj, 'Model', 'Box', 'MODEL_VIEW') + self.representations["Model/Box/MODEL_VIEW/{}".format(obj.data.name)] = self.get_representation( + obj.data, obj, "Model", "Box", "MODEL_VIEW" + ) def append_point_cloud_representation(self, obj): - self.representations['Model/Body/MODEL_VIEW/{}'.format(obj.name)] = self.get_representation( - obj.point_cloud_visualizer, obj, 'Model', 'Body', 'MODEL_VIEW') + self.representations["Model/Body/MODEL_VIEW/{}".format(obj.name)] = self.get_representation( + obj.point_cloud_visualizer, obj, "Model", "Body", "MODEL_VIEW" + ) def append_curve_axis_representation(self, obj): - self.representations['Model/Axis/GRAPH_VIEW/{}'.format(obj.data.name)] = self.get_representation( - obj.data, obj, 'Model', 'Axis', 'GRAPH_VIEW') + self.representations["Model/Axis/GRAPH_VIEW/{}".format(obj.data.name)] = self.get_representation( + obj.data, obj, "Model", "Axis", "GRAPH_VIEW" + ) def append_structural_reference_representation(self, obj): - if obj.type == 'EMPTY': - self.representations['Model/Reference/GRAPH_VIEW/{}'.format(obj.name)] = self.get_representation( - obj, obj, 'Model', 'Reference', 'GRAPH_VIEW') + if obj.type == "EMPTY": + self.representations["Model/Reference/GRAPH_VIEW/{}".format(obj.name)] = self.get_representation( + obj, obj, "Model", "Reference", "GRAPH_VIEW" + ) else: - self.representations['Model/Reference/GRAPH_VIEW/{}'.format(obj.data.name)] = self.get_representation( - obj.data, obj, 'Model', 'Reference', 'GRAPH_VIEW') + self.representations["Model/Reference/GRAPH_VIEW/{}".format(obj.data.name)] = self.get_representation( + obj.data, obj, "Model", "Reference", "GRAPH_VIEW" + ) def append_representation_per_context(self, obj): if obj.data: @@ -1075,37 +1059,32 @@ class IfcParser(): else: name = obj.name for context in self.ifc_export_settings.context_tree: - for subcontext in context['subcontexts']: - for target_view in subcontext['target_views']: - self.append_representation_in_context(obj, context['name'], subcontext['name'], target_view, name) + for subcontext in context["subcontexts"]: + for target_view in subcontext["target_views"]: + self.append_representation_in_context(obj, context["name"], subcontext["name"], target_view, name) def append_representation_in_context(self, obj, context, subcontext, target_view, name): - context_prefix = '/'.join([context, subcontext, target_view]) - mesh_name = '/'.join([context_prefix, name]) + context_prefix = "/".join([context, subcontext, target_view]) + mesh_name = "/".join([context_prefix, name]) mesh = self.search_for_mesh_or_curve_data(mesh_name) if mesh: - self.representations[mesh_name] = self.get_representation( - mesh, obj, context, subcontext, target_view) - if 'Model/Box/MODEL_VIEW' in self.generated_subcontexts \ - and context_prefix == 'Model/Body/MODEL_VIEW': - if self.ifc_export_settings.should_roundtrip_native \ - and obj.data.BIMMeshProperties.ifc_definition_id: + self.representations[mesh_name] = self.get_representation(mesh, obj, context, subcontext, target_view) + if "Model/Box/MODEL_VIEW" in self.generated_subcontexts and context_prefix == "Model/Body/MODEL_VIEW": + if self.ifc_export_settings.should_roundtrip_native and obj.data.BIMMeshProperties.ifc_definition_id: pass else: - self.representations['Model/Box/MODEL_VIEW/{}'.format(mesh_name.split('/')[3])] = self.get_representation( - obj.data, obj, 'Model', 'Box', 'MODEL_VIEW') - elif context_prefix == 'Model/Body/MODEL_VIEW' \ - and obj.data \ - and not self.is_mesh_context_sensitive(obj.data.name): + self.representations[ + "Model/Box/MODEL_VIEW/{}".format(mesh_name.split("/")[3]) + ] = self.get_representation(obj.data, obj, "Model", "Box", "MODEL_VIEW") + elif ( + context_prefix == "Model/Body/MODEL_VIEW" and obj.data and not self.is_mesh_context_sensitive(obj.data.name) + ): self.append_default_representation(obj) - elif context_prefix == 'Model/Body/MODEL_VIEW' \ - and self.is_point_cloud(obj): + elif context_prefix == "Model/Body/MODEL_VIEW" and self.is_point_cloud(obj): self.append_point_cloud_representation(obj) - elif context_prefix == 'Model/Reference/GRAPH_VIEW' \ - and self.is_structural(obj): + elif context_prefix == "Model/Reference/GRAPH_VIEW" and self.is_structural(obj): self.append_structural_reference_representation(obj) - elif context_prefix == 'Model/Axis/GRAPH_VIEW' \ - and obj.type == 'CURVE': + elif context_prefix == "Model/Axis/GRAPH_VIEW" and obj.type == "CURVE": self.append_curve_axis_representation(obj) def search_for_mesh_or_curve_data(self, name): @@ -1116,48 +1095,50 @@ class IfcParser(): def get_representation(self, mesh, obj, context, subcontext, target_view): return { - 'ifc': None, - 'raw': mesh, - 'raw_object': obj, - 'context': context, - 'subcontext': subcontext, - 'target_view': target_view, - 'has_ifc_definition': False if not hasattr(mesh, 'BIMMeshProperties') else (mesh.BIMMeshProperties.ifc_definition or mesh.BIMMeshProperties.ifc_definition_id), - 'ifc_definition': mesh.BIMMeshProperties.ifc_definition if hasattr(mesh, 'BIMMeshProperties') else None, - 'ifc_definition_id': mesh.BIMMeshProperties.ifc_definition_id if hasattr(mesh, 'BIMMeshProperties') else None, - 'is_parametric': mesh.BIMMeshProperties.is_parametric if hasattr(mesh, 'BIMMeshProperties') else False, - 'is_curve': isinstance(mesh, bpy.types.Curve), - 'is_point_cloud': self.is_point_cloud(obj), - 'is_structural': self.is_structural(obj), - 'is_text': isinstance(mesh, bpy.types.TextCurve), - 'is_wireframe': self.is_wireframe_mesh(mesh, obj), - 'is_native': mesh.BIMMeshProperties.is_native if hasattr(mesh, 'BIMMeshProperties') else False, - 'is_swept_solid': mesh.BIMMeshProperties.is_swept_solid if hasattr(mesh, 'BIMMeshProperties') else False, - 'is_generated': False, - 'presentation_layer': mesh.BIMMeshProperties.presentation_layer if hasattr(mesh, 'BIMMeshProperties') else None, - 'attributes': {'Name': mesh.name} + "ifc": None, + "raw": mesh, + "raw_object": obj, + "context": context, + "subcontext": subcontext, + "target_view": target_view, + "has_ifc_definition": False + if not hasattr(mesh, "BIMMeshProperties") + else (mesh.BIMMeshProperties.ifc_definition or mesh.BIMMeshProperties.ifc_definition_id), + "ifc_definition": mesh.BIMMeshProperties.ifc_definition if hasattr(mesh, "BIMMeshProperties") else None, + "ifc_definition_id": mesh.BIMMeshProperties.ifc_definition_id + if hasattr(mesh, "BIMMeshProperties") + else None, + "is_parametric": mesh.BIMMeshProperties.is_parametric if hasattr(mesh, "BIMMeshProperties") else False, + "is_curve": isinstance(mesh, bpy.types.Curve), + "is_point_cloud": self.is_point_cloud(obj), + "is_structural": self.is_structural(obj), + "is_text": isinstance(mesh, bpy.types.TextCurve), + "is_wireframe": self.is_wireframe_mesh(mesh, obj), + "is_native": mesh.BIMMeshProperties.is_native if hasattr(mesh, "BIMMeshProperties") else False, + "is_swept_solid": mesh.BIMMeshProperties.is_swept_solid if hasattr(mesh, "BIMMeshProperties") else False, + "is_generated": False, + "presentation_layer": mesh.BIMMeshProperties.presentation_layer + if hasattr(mesh, "BIMMeshProperties") + else None, + "attributes": {"Name": mesh.name}, } def is_wireframe_mesh(self, mesh, obj): if isinstance(mesh, bpy.types.Mesh) and not mesh.polygons: modifiers = [m.type for m in obj.modifiers] # SCREW and SKIN can create faces, so it is not a wireframe mesh - if 'SCREW' not in modifiers and 'SKIN' not in modifiers: + if "SCREW" not in modifiers and "SKIN" not in modifiers: return True if isinstance(mesh, bpy.types.Curve) and not mesh.bevel_object and not mesh.bevel_depth: return True return False def is_mesh_context_sensitive(self, name): - return '/' in name \ - and ( \ - name[0:6] == 'Model/' \ - or name[0:5] == 'Plan/' \ - ) + return "/" in name and (name[0:6] == "Model/" or name[0:5] == "Plan/") def get_ifc_representation_name(self, name): if self.is_mesh_context_sensitive(name): - return name.split('/')[3] + return name.split("/")[3] return name def get_materials(self): @@ -1165,25 +1146,25 @@ class IfcParser(): if not self.ifc_export_settings.has_representations: return results for product in self.selected_products + self.type_products: - obj = product['raw'] + obj = product["raw"] if obj.data is None: continue for slot in obj.material_slots: if slot.material is None: continue - if slot.material.name in results or slot.link == 'OBJECT': + if slot.material.name in results or slot.link == "OBJECT": continue results[slot.material.name] = { - 'ifc': None, - 'part_ifc': None, - 'raw': slot.material, - 'material_type': obj.BIMObjectProperties.material_type, - 'attributes': self.get_material_attributes(slot.material) + "ifc": None, + "part_ifc": None, + "raw": slot.material, + "material_type": obj.BIMObjectProperties.material_type, + "attributes": self.get_material_attributes(slot.material), } return results def get_material_attributes(self, material): - attributes = {'Name': material.name} + attributes = {"Name": material.name} attributes.update({a.name: a.string_value for a in material.BIMMaterialProperties.attributes}) return attributes @@ -1193,45 +1174,47 @@ class IfcParser(): return results parsed_data_names = [] for product in self.selected_products + self.type_products: - obj = product['raw'] - if obj.data is None \ - or obj.data.name in parsed_data_names: + obj = product["raw"] + if obj.data is None or obj.data.name in parsed_data_names: continue - if hasattr(obj.data, 'BIMMeshProperties') \ - and obj.data.BIMMeshProperties.ifc_definition_id: + if hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.ifc_definition_id: continue parsed_data_names.append(obj.data.name) for slot in obj.material_slots: if slot.material is None: continue - results.append({ - 'ifc': None, - 'raw': slot.material, - 'related_product_name': product['raw'].name, - 'attributes': {'Name': slot.material.name}, - }) + results.append( + { + "ifc": None, + "raw": slot.material, + "related_product_name": product["raw"].name, + "attributes": {"Name": slot.material.name}, + } + ) return results def get_grid_axes(self): results = {} for selected_axis in self.selected_grid_axes: - obj = selected_axis['raw'] + obj = selected_axis["raw"] grid_raw = bpy.data.objects.get(self.get_parent_collection(obj.users_collection[0]).name) if grid_raw.name not in results: - results[grid_raw.name] = {'UAxes': [], 'VAxes': [], 'WAxes': []} - if 'UAxes' in obj.users_collection[0].name: - axis_type = 'UAxes' - elif 'VAxes' in obj.users_collection[0].name: - axis_type = 'VAxes' + results[grid_raw.name] = {"UAxes": [], "VAxes": [], "WAxes": []} + if "UAxes" in obj.users_collection[0].name: + axis_type = "UAxes" + elif "VAxes" in obj.users_collection[0].name: + axis_type = "VAxes" else: - axis_type = 'WAxes' - results[grid_raw.name][axis_type].append ({ - 'ifc': None, - 'raw': obj, - 'grid_raw': grid_raw, - 'class': 'IfcGridAxis', - 'attributes': {a.name: a.string_value for a in obj.BIMObjectProperties.attributes} - }) + axis_type = "WAxes" + results[grid_raw.name][axis_type].append( + { + "ifc": None, + "raw": obj, + "grid_raw": grid_raw, + "class": "IfcGridAxis", + "attributes": {a.name: a.string_value for a in obj.BIMObjectProperties.attributes}, + } + ) return results def get_type_products(self): @@ -1243,106 +1226,101 @@ class IfcParser(): def get_object_representation_names(self, obj): names = [] if self.is_point_cloud(obj): - names.append('Model/Body/MODEL_VIEW/{}'.format(obj.name)) + names.append("Model/Body/MODEL_VIEW/{}".format(obj.name)) return names - elif self.is_structural(obj) and obj.type == 'EMPTY': - names.append('Model/Reference/GRAPH_VIEW/{}'.format(obj.name)) + elif self.is_structural(obj) and obj.type == "EMPTY": + names.append("Model/Reference/GRAPH_VIEW/{}".format(obj.name)) return names if not obj.data: return names name = self.get_ifc_representation_name(obj.data.name) for context in self.ifc_export_settings.context_tree: - for subcontext in context['subcontexts']: - for target_view in subcontext['target_views']: - mesh_name = '/'.join([context['name'], subcontext['name'], target_view, name]) + for subcontext in context["subcontexts"]: + for target_view in subcontext["target_views"]: + mesh_name = "/".join([context["name"], subcontext["name"], target_view, name]) if mesh_name in self.representations: names.append(mesh_name) return names def get_spatial_structure_elements_tree(self, parent): children = [] - if parent['raw'].name not in bpy.data.collections: + if parent["raw"].name not in bpy.data.collections: return children for reference, element in enumerate(self.spatial_structure_elements): - if ( \ - # A convention is established that spatial elements may be - # an object placed in a collection of the same name - element['raw'].name == element['raw'].users_collection[0].name \ - and element['raw'].users_collection[0].name in [c.name \ - for c in bpy.data.collections[parent['raw'].name].children] \ - ) or ( \ - # We allow finer grain spatial elements such as IfcSpace to - # break the convention to prevent collection overload in Blender - element['raw'].name != element['raw'].users_collection[0].name \ - and element['raw'].users_collection[0].name in [o.name \ - for o in bpy.data.collections[parent['raw'].name].objects] \ - ): - children.append({ - 'reference': reference, - 'children': self.get_spatial_structure_elements_tree(element) - }) + if ( # A convention is established that spatial elements may be + # an object placed in a collection of the same name + element["raw"].name == element["raw"].users_collection[0].name + and element["raw"].users_collection[0].name + in [c.name for c in bpy.data.collections[parent["raw"].name].children] + ) or ( # We allow finer grain spatial elements such as IfcSpace to + # break the convention to prevent collection overload in Blender + element["raw"].name != element["raw"].users_collection[0].name + and element["raw"].users_collection[0].name + in [o.name for o in bpy.data.collections[parent["raw"].name].objects] + ): + children.append({"reference": reference, "children": self.get_spatial_structure_elements_tree(element)}) return children def get_spatial_structure_element_reference(self, name): - return [e['raw'].name for e in self.spatial_structure_elements].index(name) + return [e["raw"].name for e in self.spatial_structure_elements].index(name) def get_group_reference(self, name): - return ['{}/{}'.format(e['class'], e['attributes']['Name']) - for e in self.groups].index(name) + return ["{}/{}".format(e["class"], e["attributes"]["Name"]) for e in self.groups].index(name) def get_type_product_reference(self, name): - return [p['raw'].name - for p in self.type_products].index(name) + return [p["raw"].name for p in self.type_products].index(name) def get_ifc_class(self, name): - return name.split('/')[0] + return name.split("/")[0] def get_ifc_name(self, name): try: - return name.split('/')[1] + return name.split("/")[1] except IndexError: self.ifc_export_settings.logger.error( - 'Name "{}" does not follow the format of "IfcClass/Name"'.format(name)) + 'Name "{}" does not follow the format of "IfcClass/Name"'.format(name) + ) def get_name_attribute(self, obj): - name = obj.BIMObjectProperties.attributes.get('Name') + name = obj.BIMObjectProperties.attributes.get("Name") if name: return name.string_value return self.get_ifc_name(obj.name) def is_a_grid_axis(self, class_name): - return class_name == 'IfcGridAxis' + return class_name == "IfcGridAxis" def is_a_spatial_structure_element(self, class_name): return class_name in [ - 'IfcBuilding', - 'IfcBuildingStorey', - 'IfcExternalSpatialElement', - 'IfcSite', - 'IfcSpace', - 'IfcSpatialZone' - ] + "IfcBuilding", + "IfcBuildingStorey", + "IfcExternalSpatialElement", + "IfcSite", + "IfcSpace", + "IfcSpatialZone", + ] def is_a_rel_aggregates(self, class_name): - return class_name == 'IfcRelAggregates' + return class_name == "IfcRelAggregates" def is_a_project(self, class_name): - return class_name == 'IfcProject' + return class_name == "IfcProject" def is_a_library(self, class_name): - return class_name == 'IfcProjectLibrary' + return class_name == "IfcProjectLibrary" def is_a_group(self, class_name): return class_name in [g for g in schema.ifc.IfcGroup.keys()] def is_a_type(self, class_name): - return (class_name[0:3] == 'Ifc' and class_name[-4:] == 'Type') \ - or (class_name[0:3] == 'Ifc' and class_name[-5:] == 'Style') + return (class_name[0:3] == "Ifc" and class_name[-4:] == "Type") or ( + class_name[0:3] == "Ifc" and class_name[-5:] == "Style" + ) -class IfcExporter(): +class IfcExporter: def __init__(self, ifc_export_settings, ifc_parser): - self.template_file = '{}template.ifc'.format(ifc_export_settings.schema_dir) + self.template_file = "{}template.ifc".format(ifc_export_settings.schema_dir) self.ifc_export_settings = ifc_export_settings self.ifc_parser = ifc_parser @@ -1389,7 +1367,7 @@ class IfcExporter(): self.relate_opening_elements_to_fillings() self.relate_objects_to_projection_elements() self.relate_objects_to_materials() - for set_type in ['constituent', 'layer', 'profile']: + for set_type in ["constituent", "layer", "profile"]: self.relate_objects_to_material_sets(set_type) self.relate_spaces_to_boundary_elements() self.relate_to_documents(self.ifc_parser.rel_associates_document_object) @@ -1403,175 +1381,204 @@ class IfcExporter(): def create_origin(self): self.origin = self.file.createIfcAxis2Placement3D( - self.file.createIfcCartesianPoint((0., 0., 0.)), - self.file.createIfcDirection((0., 0., 1.)), - self.file.createIfcDirection((1., 0., 0.))) + self.file.createIfcCartesianPoint((0.0, 0.0, 0.0)), + self.file.createIfcDirection((0.0, 0.0, 1.0)), + self.file.createIfcDirection((1.0, 0.0, 0.0)), + ) def set_header(self): # TODO: add all metadata, pending bug #747 self.file.wrapped_data.header.file_name.name = os.path.basename(self.ifc_export_settings.output_file) - self.file.wrapped_data.header.file_name.time_stamp = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).astimezone().replace(microsecond=0).isoformat() - self.file.wrapped_data.header.file_name.preprocessor_version = 'IfcOpenShell {}'.format(ifcopenshell.version) - self.file.wrapped_data.header.file_name.originating_system = '{} {}'.format( - self.get_application_name(), self.get_application_version()) + self.file.wrapped_data.header.file_name.time_stamp = ( + datetime.datetime.utcnow() + .replace(tzinfo=datetime.timezone.utc) + .astimezone() + .replace(microsecond=0) + .isoformat() + ) + self.file.wrapped_data.header.file_name.preprocessor_version = "IfcOpenShell {}".format(ifcopenshell.version) + self.file.wrapped_data.header.file_name.originating_system = "{} {}".format( + self.get_application_name(), self.get_application_version() + ) if self.owner_history: - if self.schema_version == 'IFC2X3': + if self.schema_version == "IFC2X3": self.file.wrapped_data.header.file_name.authorization = self.owner_history.OwningUser.ThePerson.Id else: - self.file.wrapped_data.header.file_name.authorization = self.owner_history.OwningUser.ThePerson.Identification + self.file.wrapped_data.header.file_name.authorization = ( + self.owner_history.OwningUser.ThePerson.Identification + ) else: - self.file.wrapped_data.header.file_name.authorization = 'Nobody' + self.file.wrapped_data.header.file_name.authorization = "Nobody" def get_application_name(self): - return 'BlenderBIM' + return "BlenderBIM" def get_application_version(self): - return '.'.join([str(x) for x in [addon.bl_info.get('version', (-1,-1,-1)) for addon in addon_utils.modules() if addon.bl_info['name'] == 'BlenderBIM'][0]]) + return ".".join( + [ + str(x) + for x in [ + addon.bl_info.get("version", (-1, -1, -1)) + for addon in addon_utils.modules() + if addon.bl_info["name"] == "BlenderBIM" + ][0] + ] + ) def get_application_organisation(self): - self.application_organisation = self.file.create_entity('IfcOrganization', **{ - "Name": "IfcOpenShell", - "Description": "IfcOpenShell is an open source (LGPL) software library that helps users and software developers to work with the IFC file format.", - "Roles": [self.file.create_entity('IfcActorRole', **{ - "Role": "USERDEFINED", - "UserDefinedRole": "CONTRIBUTOR" - })], - "Addresses": [ - self.file.create_entity('IfcTelecomAddress', **{ - "Purpose": "USERDEFINED", - "UserDefinedPurpose": "WEBPAGE", - "Description": "The main webpage of the software collection.", - "WWWHomePageURL": "https://ifcopenshell.org" - }), - self.file.create_entity('IfcTelecomAddress', **{ - "Purpose": "USERDEFINED", - "UserDefinedPurpose": "WEBPAGE", - "Description": "The BlenderBIM Add-on webpage of the software collection.", - "WWWHomePageURL": "https://blenderbim.org" - }), - self.file.create_entity('IfcTelecomAddress', **{ - "Purpose": "USERDEFINED", - "UserDefinedPurpose": "REPOSITORY", - "Description": "The source code repository of the software collection.", - "WWWHomePageURL": "https://github.com/IfcOpenShell/IfcOpenShell.git" - }) - ] - }) + self.application_organisation = self.file.create_entity( + "IfcOrganization", + **{ + "Name": "IfcOpenShell", + "Description": "IfcOpenShell is an open source (LGPL) software library that helps users and software developers to work with the IFC file format.", + "Roles": [ + self.file.create_entity("IfcActorRole", **{"Role": "USERDEFINED", "UserDefinedRole": "CONTRIBUTOR"}) + ], + "Addresses": [ + self.file.create_entity( + "IfcTelecomAddress", + **{ + "Purpose": "USERDEFINED", + "UserDefinedPurpose": "WEBPAGE", + "Description": "The main webpage of the software collection.", + "WWWHomePageURL": "https://ifcopenshell.org", + }, + ), + self.file.create_entity( + "IfcTelecomAddress", + **{ + "Purpose": "USERDEFINED", + "UserDefinedPurpose": "WEBPAGE", + "Description": "The BlenderBIM Add-on webpage of the software collection.", + "WWWHomePageURL": "https://blenderbim.org", + }, + ), + self.file.create_entity( + "IfcTelecomAddress", + **{ + "Purpose": "USERDEFINED", + "UserDefinedPurpose": "REPOSITORY", + "Description": "The source code repository of the software collection.", + "WWWHomePageURL": "https://github.com/IfcOpenShell/IfcOpenShell.git", + }, + ), + ], + }, + ) return self.application_organisation def create_owner_history(self): person = None organisation = None for person in self.ifc_parser.people: - if self.schema_version == 'IFC2X3' and person['ifc'].Id == bpy.context.scene.BIMProperties.person: + if self.schema_version == "IFC2X3" and person["ifc"].Id == bpy.context.scene.BIMProperties.person: break - elif person['ifc'].Identification == bpy.context.scene.BIMProperties.person: + elif person["ifc"].Identification == bpy.context.scene.BIMProperties.person: break for organisation in self.ifc_parser.organisations: - if organisation['ifc'].Name == bpy.context.scene.BIMProperties.organisation: + if organisation["ifc"].Name == bpy.context.scene.BIMProperties.organisation: break if not person or not organisation: self.owner_history = None return - person_and_organisation = self.file.create_entity('IfcPersonAndOrganization', **{ - 'ThePerson': person['ifc'], - 'TheOrganization': organisation['ifc'], - 'Roles': None # TODO - }) + person_and_organisation = self.file.create_entity( + "IfcPersonAndOrganization", + **{"ThePerson": person["ifc"], "TheOrganization": organisation["ifc"], "Roles": None}, # TODO + ) developer_organisation = self.get_application_organisation() - application = self.file.create_entity('IfcApplication', **{ - 'ApplicationDeveloper': developer_organisation, - 'Version': self.get_application_version(), - 'ApplicationFullName': self.get_application_name(), - 'ApplicationIdentifier': self.get_application_name() - }) - self.owner_history = self.file.create_entity('IfcOwnerHistory', **{ - 'OwningUser': person_and_organisation, - 'OwningApplication': application, - 'State': 'READWRITE', - 'ChangeAction': 'NOCHANGE', - 'LastModifiedDate': int(time.time()), - 'LastModifyingUser': person_and_organisation, - 'LastModifyingApplication': application, - 'CreationDate': int(time.time()) # illegal, but better than nothing ... - }) + application = self.file.create_entity( + "IfcApplication", + **{ + "ApplicationDeveloper": developer_organisation, + "Version": self.get_application_version(), + "ApplicationFullName": self.get_application_name(), + "ApplicationIdentifier": self.get_application_name(), + }, + ) + self.owner_history = self.file.create_entity( + "IfcOwnerHistory", + **{ + "OwningUser": person_and_organisation, + "OwningApplication": application, + "State": "READWRITE", + "ChangeAction": "NOCHANGE", + "LastModifiedDate": int(time.time()), + "LastModifyingUser": person_and_organisation, + "LastModifyingApplication": application, + "CreationDate": int(time.time()), # illegal, but better than nothing ... + }, + ) def create_units(self): for unit_type, data in self.ifc_parser.units.items(): - if data['is_metric']: - data['ifc'] = self.create_metric_unit(unit_type, data) + if data["is_metric"]: + data["ifc"] = self.create_metric_unit(unit_type, data) else: - data['ifc'] = self.create_imperial_unit(unit_type, data) - self.file.createIfcUnitAssignment([u['ifc'] for u in self.ifc_parser.units.values()]) + data["ifc"] = self.create_imperial_unit(unit_type, data) + self.file.createIfcUnitAssignment([u["ifc"] for u in self.ifc_parser.units.values()]) def create_metric_unit(self, unit_type, data): - type_prefix = '' - if unit_type == 'area': - type_prefix = 'SQUARE_' - elif unit_type == 'volume': - type_prefix = 'CUBIC_' + type_prefix = "" + if unit_type == "area": + type_prefix = "SQUARE_" + elif unit_type == "volume": + type_prefix = "CUBIC_" return self.file.createIfcSIUnit( None, - '{}UNIT'.format(unit_type.upper()), - SIUnitHelper.get_prefix(data['raw']), - type_prefix + SIUnitHelper.get_unit_name(data['raw']) + "{}UNIT".format(unit_type.upper()), + SIUnitHelper.get_prefix(data["raw"]), + type_prefix + SIUnitHelper.get_unit_name(data["raw"]), ) def create_imperial_unit(self, unit_type, data): - if unit_type == 'length': + if unit_type == "length": dimensional_exponents = self.file.createIfcDimensionalExponents(1, 0, 0, 0, 0, 0, 0) - name_prefix = '' - elif unit_type == 'area': + name_prefix = "" + elif unit_type == "area": dimensional_exponents = self.file.createIfcDimensionalExponents(2, 0, 0, 0, 0, 0, 0) - name_prefix = 'square' - elif unit_type == 'volume': + name_prefix = "square" + elif unit_type == "volume": dimensional_exponents = self.file.createIfcDimensionalExponents(3, 0, 0, 0, 0, 0, 0) - name_prefix = 'cubic' + name_prefix = "cubic" si_unit = self.file.createIfcSIUnit( None, - '{}UNIT'.format(unit_type.upper()), + "{}UNIT".format(unit_type.upper()), None, - '{}METRE'.format(name_prefix.upper() + '_' if name_prefix else '') - ) - if data['raw'] == 'INCHES': - name = '{}inch'.format(name_prefix + ' ' if name_prefix else '') - elif data['raw'] == 'FEET': - name = '{}foot'.format(name_prefix + ' ' if name_prefix else '') - value_component = self.file.create_entity( - 'IfcReal', - **{'wrappedValue': SIUnitHelper.si_conversions[name]} + "{}METRE".format(name_prefix.upper() + "_" if name_prefix else ""), ) + if data["raw"] == "INCHES": + name = "{}inch".format(name_prefix + " " if name_prefix else "") + elif data["raw"] == "FEET": + name = "{}foot".format(name_prefix + " " if name_prefix else "") + value_component = self.file.create_entity("IfcReal", **{"wrappedValue": SIUnitHelper.si_conversions[name]}) conversion_factor = self.file.createIfcMeasureWithUnit(value_component, si_unit) return self.file.createIfcConversionBasedUnit( - dimensional_exponents, - '{}UNIT'.format(unit_type.upper()), - name, - conversion_factor + dimensional_exponents, "{}UNIT".format(unit_type.upper()), name, conversion_factor ) def create_people(self): for person in self.ifc_parser.people: - if person['roles']: - person['attributes']['Roles'] = self.create_roles(person['roles']) - if person['addresses']: - person['attributes']['Addresses'] = self.create_addresses(person['addresses']) - if self.schema_version == 'IFC2X3' and 'Identification' in person['attributes']: - person['attributes']['Id'] = person['attributes']['Identification'] - del person['attributes']['Identification'] - person['ifc'] = self.file.create_entity('IfcPerson', **person['attributes']) + if person["roles"]: + person["attributes"]["Roles"] = self.create_roles(person["roles"]) + if person["addresses"]: + person["attributes"]["Addresses"] = self.create_addresses(person["addresses"]) + if self.schema_version == "IFC2X3" and "Identification" in person["attributes"]: + person["attributes"]["Id"] = person["attributes"]["Identification"] + del person["attributes"]["Identification"] + person["ifc"] = self.file.create_entity("IfcPerson", **person["attributes"]) def create_organisations(self): for organisation in self.ifc_parser.organisations: - if organisation['roles']: - organisation['attributes']['Roles'] = self.create_roles(organisation['roles']) - if organisation['addresses']: - organisation['attributes']['Addresses'] = self.create_addresses(organisation['addresses']) - organisation['ifc'] = self.file.create_entity('IfcOrganization', **organisation['attributes']) + if organisation["roles"]: + organisation["attributes"]["Roles"] = self.create_roles(organisation["roles"]) + if organisation["addresses"]: + organisation["attributes"]["Addresses"] = self.create_addresses(organisation["addresses"]) + organisation["ifc"] = self.file.create_entity("IfcOrganization", **organisation["attributes"]) def create_roles(self, roles): results = [] for role in roles: - results.append(self.file.create_entity('IfcActorRole', **role['attributes'])) + results.append(self.file.create_entity("IfcActorRole", **role["attributes"])) return results def create_addresses(self, addresses): @@ -1581,337 +1588,360 @@ class IfcExporter(): return results def create_address(self, address): - if self.schema_version == 'IFC2X3' and 'MessagingIDs' in address['attributes']: - del address['attributes']['MessagingIDs'] - return self.file.create_entity('IfcPostalAddress' if - address['is_postal'] else 'IfcTelecomAddress', **address['attributes']) + if self.schema_version == "IFC2X3" and "MessagingIDs" in address["attributes"]: + del address["attributes"]["MessagingIDs"] + return self.file.create_entity( + "IfcPostalAddress" if address["is_postal"] else "IfcTelecomAddress", **address["attributes"] + ) def create_library_information(self): information = self.ifc_parser.library_information if not information: return - information['attributes']['Publisher'] = self.owner_history.OwningUser - information['ifc'] = self.file.create_entity('IfcLibraryInformation', - **information['attributes']) + information["attributes"]["Publisher"] = self.owner_history.OwningUser + information["ifc"] = self.file.create_entity("IfcLibraryInformation", **information["attributes"]) self.file.createIfcRelAssociatesLibrary( - ifcopenshell.guid.new(), - self.owner_history, - information['attributes']['Name'], - information['attributes']['Description'], - [self.ifc_parser.project['ifc']], - information['ifc']) + ifcopenshell.guid.new(), + self.owner_history, + information["attributes"]["Name"], + information["attributes"]["Description"], + [self.ifc_parser.project["ifc"]], + information["ifc"], + ) def create_document_information(self): for information in self.ifc_parser.document_information.values(): - information['ifc'] = self.file.create_entity( - 'IfcDocumentInformation', **information['attributes']) + information["ifc"] = self.file.create_entity("IfcDocumentInformation", **information["attributes"]) def create_document_references(self): for reference in self.ifc_parser.document_references.values(): - if reference['referenced_document'] \ - and reference['referenced_document'] in self.ifc_parser.document_information: - reference['attributes']['ReferencedDocument'] = self.ifc_parser.document_information[reference['referenced_document']]['ifc'] - reference['ifc'] = self.file.create_entity( - 'IfcDocumentReference', **reference['attributes']) + if ( + reference["referenced_document"] + and reference["referenced_document"] in self.ifc_parser.document_information + ): + reference["attributes"]["ReferencedDocument"] = self.ifc_parser.document_information[ + reference["referenced_document"] + ]["ifc"] + reference["ifc"] = self.file.create_entity("IfcDocumentReference", **reference["attributes"]) self.file.createIfcRelAssociatesDocument( - ifcopenshell.guid.new(), None, None, None, - [self.ifc_parser.project['ifc']], reference['ifc']) + ifcopenshell.guid.new(), None, None, None, [self.ifc_parser.project["ifc"]], reference["ifc"] + ) def create_classifications(self): for classification in self.ifc_parser.classifications.values(): - classification['ifc'] = self.file.add(classification['raw_element']) + classification["ifc"] = self.file.add(classification["raw_element"]) self.file.createIfcRelAssociatesClassification( - ifcopenshell.guid.new(), None, None, None, - [self.ifc_parser.project['ifc']], classification['ifc']) + ifcopenshell.guid.new(), None, None, None, [self.ifc_parser.project["ifc"]], classification["ifc"] + ) def create_classification_references(self): for reference in self.ifc_parser.classification_references.values(): - reference['ifc'] = self.file.add(reference['raw_element']) + reference["ifc"] = self.file.add(reference["raw_element"]) def create_constraints(self): for constraint in self.ifc_parser.constraints.values(): - constraint['ifc'] = self.file.create_entity( - 'IfcObjective', **constraint['attributes']) + constraint["ifc"] = self.file.create_entity("IfcObjective", **constraint["attributes"]) def create_psets(self): for pset in self.ifc_parser.psets.values(): properties = self.create_pset_properties(pset) if not properties: continue - pset['attributes'].update({ - 'GlobalId': ifcopenshell.guid.new(), - 'OwnerHistory': self.owner_history, - 'HasProperties': properties - }) - pset['ifc'] = self.file.create_entity('IfcPropertySet', **pset['attributes']) + pset["attributes"].update( + {"GlobalId": ifcopenshell.guid.new(), "OwnerHistory": self.owner_history, "HasProperties": properties} + ) + pset["ifc"] = self.file.create_entity("IfcPropertySet", **pset["attributes"]) def create_material_psets(self, material): - for pset_dir in material['raw'].BIMMaterialProperties.psets: + for pset_dir in material["raw"].BIMMaterialProperties.psets: for name, properties in self.ifc_parser.material_psets[pset_dir.name].items(): - self.file.create_entity('IfcMaterialProperties', **{ - 'Name': name, - 'Description': pset_dir.name, - 'Properties': self.create_pset_properties(properties), - 'Material': material['ifc'] - }) + self.file.create_entity( + "IfcMaterialProperties", + **{ + "Name": name, + "Description": pset_dir.name, + "Properties": self.create_pset_properties(properties), + "Material": material["ifc"], + }, + ) def create_qto_properties(self, qto): - if qto['attributes']['Name'] in schema.ifc.qtos: + if qto["attributes"]["Name"] in schema.ifc.qtos: return self.create_templated_qto_properties(qto) return self.create_custom_qto_properties(qto) def create_pset_properties(self, pset): - if pset['attributes']['Name'] in schema.ifc.psets: + if pset["attributes"]["Name"] in schema.ifc.psets: return self.create_templated_pset_properties(pset) return self.create_custom_pset_properties(pset) def create_custom_pset_properties(self, pset): properties = [] - for key, value in pset['raw'].items(): + for key, value in pset["raw"].items(): properties.append( - self.file.create_entity('IfcPropertySingleValue', **{ - 'Name': key, - 'NominalValue': self.file.create_entity('IfcLabel', value) - })) + self.file.create_entity( + "IfcPropertySingleValue", + **{"Name": key, "NominalValue": self.file.create_entity("IfcLabel", value)}, + ) + ) return properties def create_custom_qto_properties(self, qto): properties = [] - for key, value in qto['raw'].items(): - if 'Area' in key: - quantity_type = 'Area' - elif 'Volume' in key: - quantity_type = 'Volume' + for key, value in qto["raw"].items(): + if "Area" in key: + quantity_type = "Area" + elif "Volume" in key: + quantity_type = "Volume" else: - quantity_type = 'Length' + quantity_type = "Length" properties.append( - self.file.create_entity(f'IfcQuantity{quantity_type}', **{ - 'Name': key, - f'{quantity_type}Value': float(value) - })) + self.file.create_entity( + f"IfcQuantity{quantity_type}", **{"Name": key, f"{quantity_type}Value": float(value)} + ) + ) return properties def create_templated_pset_properties(self, pset): properties = [] - templates = schema.ifc.psets[pset['attributes']['Name']]['HasPropertyTemplates'] + templates = schema.ifc.psets[pset["attributes"]["Name"]]["HasPropertyTemplates"] for name, data in templates.items(): - if name not in pset['raw']: + if name not in pset["raw"]: continue - if data.TemplateType == 'P_SINGLEVALUE' \ - or data.TemplateType == 'P_ENUMERATEDVALUE': + if data.TemplateType == "P_SINGLEVALUE" or data.TemplateType == "P_ENUMERATEDVALUE": if data.PrimaryMeasureType: value_type = data.PrimaryMeasureType else: # The IFC spec is missing some, so we provide a fallback - value_type = 'IfcLabel' + value_type = "IfcLabel" nominal_value = self.file.create_entity( - value_type, - self.cast_to_base_type(value_type, pset['raw'][name])) + value_type, self.cast_to_base_type(value_type, pset["raw"][name]) + ) properties.append( - self.file.create_entity('IfcPropertySingleValue', **{ - 'Name': name, - 'NominalValue': nominal_value - })) - invalid_pset_keys = [k for k in pset['raw'].keys() if k not in templates.keys()] + self.file.create_entity("IfcPropertySingleValue", **{"Name": name, "NominalValue": nominal_value}) + ) + invalid_pset_keys = [k for k in pset["raw"].keys() if k not in templates.keys()] if invalid_pset_keys: self.ifc_export_settings.logger.error( - 'One or more properties were invalid in the pset {}: {}'.format( - pset['attributes']['Name'], - invalid_pset_keys)) + "One or more properties were invalid in the pset {}: {}".format( + pset["attributes"]["Name"], invalid_pset_keys + ) + ) return properties def create_templated_qto_properties(self, qto): properties = [] - templates = schema.ifc.qtos[qto['attributes']['Name']]['HasPropertyTemplates'] + templates = schema.ifc.qtos[qto["attributes"]["Name"]]["HasPropertyTemplates"] for name, data in templates.items(): - if name not in qto['raw']: + if name not in qto["raw"]: continue - if data.TemplateType[0:2] == 'Q_': + if data.TemplateType[0:2] == "Q_": value_basename = data.TemplateType[2:].title() - value_name = f'{value_basename}Value' - class_name = f'IfcQuantity{value_basename}' + value_name = f"{value_basename}Value" + class_name = f"IfcQuantity{value_basename}" properties.append( - self.file.create_entity(class_name, **{ - 'Name': name, - value_name: float(qto['raw'][name]) - })) - invalid_qto_keys = [k for k in qto['raw'].keys() if k not in templates.keys()] + self.file.create_entity(class_name, **{"Name": name, value_name: float(qto["raw"][name])}) + ) + invalid_qto_keys = [k for k in qto["raw"].keys() if k not in templates.keys()] if invalid_qto_keys: self.ifc_export_settings.logger.error( - 'One or more properties were invalid in the qto {}/{}: {}'.format( - qto['attributes']['Name'], - qto['attributes']['Description'], - invalid_qto_keys)) + "One or more properties were invalid in the qto {}/{}: {}".format( + qto["attributes"]["Name"], qto["attributes"]["Description"], invalid_qto_keys + ) + ) return properties def cast_to_base_type(self, var_type, value): if var_type not in schema.ifc.type_map: return value - elif schema.ifc.type_map[var_type] == 'float': + elif schema.ifc.type_map[var_type] == "float": return float(value) - elif schema.ifc.type_map[var_type] == 'integer': + elif schema.ifc.type_map[var_type] == "integer": return int(value) - elif schema.ifc.type_map[var_type] == 'bool': - return True if value.lower() in ['1', 't', 'true', 'yes', 'y', 'uh-huh'] else False + elif schema.ifc.type_map[var_type] == "bool": + return True if value.lower() in ["1", "t", "true", "yes", "y", "uh-huh"] else False return str(value) def create_rep_context(self): self.ifc_rep_context = {} for context in self.ifc_export_settings.context_tree: - if context['name'] == 'Model': - self.ifc_rep_context['Model'] = { - 'ifc': self.file.createIfcGeometricRepresentationContext( - None, 'Model', 3, 1.0E-05, self.origin)} - elif context['name'] == 'Plan': - self.ifc_rep_context['Plan'] = { - 'ifc': self.file.createIfcGeometricRepresentationContext( - None, 'Plan', 2, 1.0E-05, self.origin)} - for subcontext in context['subcontexts']: - self.ifc_rep_context[context['name']][subcontext['name']] = {} - for target_view in subcontext['target_views']: - self.ifc_rep_context[context['name']][subcontext['name']][target_view] = { - 'ifc': self.file.createIfcGeometricRepresentationSubContext( - subcontext['name'], context['name'], None, None, None, None, - self.ifc_rep_context[context['name']]['ifc'], None, target_view, None)} + if context["name"] == "Model": + self.ifc_rep_context["Model"] = { + "ifc": self.file.createIfcGeometricRepresentationContext(None, "Model", 3, 1.0e-05, self.origin) + } + elif context["name"] == "Plan": + self.ifc_rep_context["Plan"] = { + "ifc": self.file.createIfcGeometricRepresentationContext(None, "Plan", 2, 1.0e-05, self.origin) + } + for subcontext in context["subcontexts"]: + self.ifc_rep_context[context["name"]][subcontext["name"]] = {} + for target_view in subcontext["target_views"]: + self.ifc_rep_context[context["name"]][subcontext["name"]][target_view] = { + "ifc": self.file.createIfcGeometricRepresentationSubContext( + subcontext["name"], + context["name"], + None, + None, + None, + None, + self.ifc_rep_context[context["name"]]["ifc"], + None, + target_view, + None, + ) + } def create_project(self): - self.ifc_parser.project['attributes'].update({ - 'RepresentationContexts': [c['ifc'] for c in self.ifc_rep_context.values()], - 'UnitsInContext': self.file.by_type("IfcUnitAssignment")[0] - }) - self.ifc_parser.project['ifc'] = self.file.create_entity( - self.ifc_parser.project['class'], **self.ifc_parser.project['attributes']) + self.ifc_parser.project["attributes"].update( + { + "RepresentationContexts": [c["ifc"] for c in self.ifc_rep_context.values()], + "UnitsInContext": self.file.by_type("IfcUnitAssignment")[0], + } + ) + self.ifc_parser.project["ifc"] = self.file.create_entity( + self.ifc_parser.project["class"], **self.ifc_parser.project["attributes"] + ) def create_libraries(self): for library in self.ifc_parser.libraries: - library['ifc'] = self.file.create_entity(library['class'], **library['attributes']) - libraries = [l['ifc'] for l in self.ifc_parser.libraries] + library["ifc"] = self.file.create_entity(library["class"], **library["attributes"]) + libraries = [l["ifc"] for l in self.ifc_parser.libraries] if libraries: self.file.createIfcRelDeclares( - ifcopenshell.guid.new(), self.owner_history, - None, None, - self.ifc_parser.project['ifc'], libraries) + ifcopenshell.guid.new(), self.owner_history, None, None, self.ifc_parser.project["ifc"], libraries + ) def create_map_conversion(self): if not self.ifc_parser.map_conversion: return self.create_target_crs() # TODO should this be hardcoded? - self.ifc_parser.map_conversion['attributes']['SourceCRS'] = self.ifc_rep_context['Model']['ifc'] - self.ifc_parser.map_conversion['attributes']['TargetCRS'] = self.ifc_parser.target_crs['ifc'] - self.ifc_parser.map_conversion['ifc'] = self.file.create_entity( - 'IfcMapConversion', - **self.ifc_parser.map_conversion['attributes'] + self.ifc_parser.map_conversion["attributes"]["SourceCRS"] = self.ifc_rep_context["Model"]["ifc"] + self.ifc_parser.map_conversion["attributes"]["TargetCRS"] = self.ifc_parser.target_crs["ifc"] + self.ifc_parser.map_conversion["ifc"] = self.file.create_entity( + "IfcMapConversion", **self.ifc_parser.map_conversion["attributes"] ) def create_target_crs(self): - for key, value in self.ifc_parser.target_crs['attributes'].items(): - if not self.ifc_parser.target_crs['attributes'][key]: - self.ifc_parser.target_crs['attributes'][key] = None - if self.ifc_parser.target_crs['attributes']['MapUnit']: - self.ifc_parser.target_crs['attributes']['MapUnit'] = self.file.createIfcSIUnit( + for key, value in self.ifc_parser.target_crs["attributes"].items(): + if not self.ifc_parser.target_crs["attributes"][key]: + self.ifc_parser.target_crs["attributes"][key] = None + if self.ifc_parser.target_crs["attributes"]["MapUnit"]: + self.ifc_parser.target_crs["attributes"]["MapUnit"] = self.file.createIfcSIUnit( None, - 'LENGTHUNIT', - SIUnitHelper.get_prefix(self.ifc_parser.target_crs['attributes']['MapUnit']), - SIUnitHelper.get_unit_name(self.ifc_parser.target_crs['attributes']['MapUnit']) + "LENGTHUNIT", + SIUnitHelper.get_prefix(self.ifc_parser.target_crs["attributes"]["MapUnit"]), + SIUnitHelper.get_unit_name(self.ifc_parser.target_crs["attributes"]["MapUnit"]), ) - self.ifc_parser.target_crs['ifc'] = self.file.create_entity( - 'IfcProjectedCRS', - **self.ifc_parser.target_crs['attributes'] + self.ifc_parser.target_crs["ifc"] = self.file.create_entity( + "IfcProjectedCRS", **self.ifc_parser.target_crs["attributes"] ) def create_type_products(self): for product in self.ifc_parser.type_products: - self.cast_attributes(product['class'], product['attributes']) + self.cast_attributes(product["class"], product["attributes"]) - product['attributes'].update({ - 'OwnerHistory': self.owner_history, # TODO: unhardcode - 'RepresentationMaps': self.get_product_shape(product) - }) + product["attributes"].update( + { + "OwnerHistory": self.owner_history, # TODO: unhardcode + "RepresentationMaps": self.get_product_shape(product), + } + ) # TODO: re-implement psets, relationships, door/window properties try: - product['ifc'] = self.file.create_entity(product['class'], **product['attributes']) + product["ifc"] = self.file.create_entity(product["class"], **product["attributes"]) except RuntimeError as e: self.ifc_export_settings.logger.error( 'The type product "{}/{}" could not be created: {}'.format( - product['class'], - product['attributes']['Name'], - e.args) + product["class"], product["attributes"]["Name"], e.args + ) ) def add_predefined_attributes_to_type_product(self, product, attributes): self.create_predefined_attributes(attributes) - product['attributes'].setdefault('HasPropertySets', []) + product["attributes"].setdefault("HasPropertySets", []) for attribute in attributes: - product['attributes']['HasPropertySets'].append(attribute['ifc']) + product["attributes"]["HasPropertySets"].append(attribute["ifc"]) def create_predefined_attributes(self, attributes): for attribute in attributes: - attribute['ifc'] = self.file.create_entity( - attribute['pset_name'], - **{k: float(v) if v.replace('.', '', 1).isdigit() else v - for k, v in attribute['raw'].items()} + attribute["ifc"] = self.file.create_entity( + attribute["pset_name"], + **{k: float(v) if v.replace(".", "", 1).isdigit() else v for k, v in attribute["raw"].items()}, ) def relate_definitions_to_contexts(self): for library in self.ifc_parser.libraries: self.file.createIfcRelDeclares( - ifcopenshell.guid.new(), self.owner_history, None, None, - library['ifc'], - [self.ifc_parser.type_products[t]['ifc'] for t in library['rel_declares_type_products']]) + ifcopenshell.guid.new(), + self.owner_history, + None, + None, + library["ifc"], + [self.ifc_parser.type_products[t]["ifc"] for t in library["rel_declares_type_products"]], + ) def relate_objects_to_objects(self): for relating_object, related_objects_reference in self.ifc_parser.rel_aggregates.items(): relating_object = self.ifc_parser.products[relating_object] if related_objects_reference not in self.ifc_parser.aggregates: continue - related_objects = [self.ifc_parser.products[o]['ifc'] - for o in self.ifc_parser.aggregates[related_objects_reference]] + related_objects = [ + self.ifc_parser.products[o]["ifc"] for o in self.ifc_parser.aggregates[related_objects_reference] + ] self.file.createIfcRelAggregates( - ifcopenshell.guid.new(), self.owner_history, relating_object['attributes']['Name'], None, - relating_object['ifc'], related_objects) + ifcopenshell.guid.new(), + self.owner_history, + relating_object["attributes"]["Name"], + None, + relating_object["ifc"], + related_objects, + ) for obj in related_objects: - obj.ObjectPlacement.PlacementRelTo = relating_object['ifc'].ObjectPlacement + obj.ObjectPlacement.PlacementRelTo = relating_object["ifc"].ObjectPlacement def create_spatial_structure_elements(self, element_tree, relating_object=None): if relating_object == None: - relating_object = self.ifc_parser.project['ifc'] + relating_object = self.ifc_parser.project["ifc"] placement_rel_to = None else: placement_rel_to = relating_object.ObjectPlacement related_objects = [] for node in element_tree: - element = self.ifc_parser.spatial_structure_elements[node['reference']] + element = self.ifc_parser.spatial_structure_elements[node["reference"]] - if element['has_scale']: + if element["has_scale"]: # Omission of the relative placement here is not as per implementer agreements placement = self.file.createIfcLocalPlacement(None, self.origin) else: placement = self.file.createIfcLocalPlacement( - placement_rel_to, self.get_relative_placement(element, placement_rel_to)) + placement_rel_to, self.get_relative_placement(element, placement_rel_to) + ) - self.cast_attributes(element['class'], element['attributes']) - element['attributes'].update({ - 'OwnerHistory': self.owner_history, # TODO: unhardcode - 'ObjectPlacement': placement, - 'Representation': self.get_product_shape(element) - }) + self.cast_attributes(element["class"], element["attributes"]) + element["attributes"].update( + { + "OwnerHistory": self.owner_history, # TODO: unhardcode + "ObjectPlacement": placement, + "Representation": self.get_product_shape(element), + } + ) - if element['class'] == 'IfcSite': - element['attributes'].update({'SiteAddress': self.create_address(element['address'])}) - elif element['class'] == 'IfcBuilding': - element['attributes'].update({'BuildingAddress': self.create_address(element['address'])}) + if element["class"] == "IfcSite": + element["attributes"].update({"SiteAddress": self.create_address(element["address"])}) + elif element["class"] == "IfcBuilding": + element["attributes"].update({"BuildingAddress": self.create_address(element["address"])}) - element['ifc'] = self.file.create_entity(element['class'], **element['attributes']) - related_objects.append(element['ifc']) - self.create_spatial_structure_elements(node['children'], element['ifc']) + element["ifc"] = self.file.create_entity(element["class"], **element["attributes"]) + related_objects.append(element["ifc"]) + self.create_spatial_structure_elements(node["children"], element["ifc"]) if related_objects: self.file.createIfcRelAggregates( - ifcopenshell.guid.new(), - self.owner_history, None, None, relating_object, related_objects) + ifcopenshell.guid.new(), self.owner_history, None, None, relating_object, related_objects + ) def get_relative_placement(self, element, placement_rel_to): if placement_rel_to: @@ -1921,15 +1951,16 @@ class IfcExporter(): relating_object_matrix[2][3] = self.convert_unit_to_si(relating_object_matrix[2][3]) else: relating_object_matrix = Matrix() - z = Vector(element['up_axis']) - x = Vector(element['forward_axis']) - o = Vector(element['location']) + z = Vector(element["up_axis"]) + x = Vector(element["forward_axis"]) + o = Vector(element["location"]) object_matrix = self.a2p(o, z, x) relative_placement_matrix = relating_object_matrix.inverted() @ object_matrix return self.create_ifc_axis_2_placement_3d( relative_placement_matrix.translation, self.get_axis(relative_placement_matrix, 2), - self.get_axis(relative_placement_matrix, 0)) + self.get_axis(relative_placement_matrix, 0), + ) def get_axis(self, matrix, axis): return matrix.col[axis].to_3d().normalized() @@ -1949,77 +1980,83 @@ class IfcExporter(): return r def get_axis2placement(self, plc): - z = Vector(plc.Axis.DirectionRatios if plc.Axis else (0,0,1)) - x = Vector(plc.RefDirection.DirectionRatios if plc.RefDirection else (1,0,0)) + z = Vector(plc.Axis.DirectionRatios if plc.Axis else (0, 0, 1)) + x = Vector(plc.RefDirection.DirectionRatios if plc.RefDirection else (1, 0, 0)) o = plc.Location.Coordinates - return self.a2p(o,z,x) + return self.a2p(o, z, x) def create_groups(self): for group in self.ifc_parser.groups: - group['ifc'] = self.file.create_entity(group['class'], **group['attributes']) - self.file.createIfcRelDeclares(ifcopenshell.guid.new(), - self.owner_history, None, None, self.ifc_parser.project['ifc'], [group['ifc']]) + group["ifc"] = self.file.create_entity(group["class"], **group["attributes"]) + self.file.createIfcRelDeclares( + ifcopenshell.guid.new(), self.owner_history, None, None, self.ifc_parser.project["ifc"], [group["ifc"]] + ) def create_styled_items(self): for styled_item in self.ifc_parser.styled_items: product = self.ifc_parser.products[ - self.ifc_parser.get_product_index_from_raw_name( - styled_item['related_product_name'])] + self.ifc_parser.get_product_index_from_raw_name(styled_item["related_product_name"]) + ] material_slots = {} - if product['ifc'].Representation: - for representation in product['ifc'].Representation.Representations: + if product["ifc"].Representation: + for representation in product["ifc"].Representation.Representations: for mapped_item in representation.Items: items = mapped_item[0].MappedRepresentation.Items for i, item in enumerate(items): - if i >= len(product['raw'].material_slots): + if i >= len(product["raw"].material_slots): i = 0 - material_slots[product['raw'].material_slots[i].name] = item + material_slots[product["raw"].material_slots[i].name] = item for styled_item_name, representation_item in material_slots.items(): - if styled_item_name == styled_item['attributes']['Name']: - styled_item['ifc'] = self.create_styled_item(styled_item, representation_item) + if styled_item_name == styled_item["attributes"]["Name"]: + styled_item["ifc"] = self.create_styled_item(styled_item, representation_item) def create_styled_item(self, item, representation_item=None): styles = [] styles.append(self.create_surface_style_rendering(item)) - if item['raw'].BIMMaterialProperties.is_external: - styles.append(self.file.create_entity('IfcExternallyDefinedSurfaceStyle', - **self.get_material_external_definition(item['raw']))) + if item["raw"].BIMMaterialProperties.is_external: + styles.append( + self.file.create_entity( + "IfcExternallyDefinedSurfaceStyle", **self.get_material_external_definition(item["raw"]) + ) + ) # Name is filled out because Revit treats this incorrectly as the material name - surface_style = self.file.createIfcSurfaceStyle(item['attributes']['Name'], 'BOTH', styles) - if self.schema_version == 'IFC2X3' or self.ifc_export_settings.should_use_presentation_style_assignment: + surface_style = self.file.createIfcSurfaceStyle(item["attributes"]["Name"], "BOTH", styles) + if self.schema_version == "IFC2X3" or self.ifc_export_settings.should_use_presentation_style_assignment: surface_style = self.file.createIfcPresentationStyleAssignment([surface_style]) - return self.file.createIfcStyledItem(representation_item, [surface_style], item['attributes']['Name']) + return self.file.createIfcStyledItem(representation_item, [surface_style], item["attributes"]["Name"]) def create_presentation_layer_assignments(self): for name, assigned_items in self.ifc_parser.presentation_layer_assignments.items(): self.file.createIfcPresentationLayerAssignment( - name, None, [i['ifc'].MappedRepresentation for i in assigned_items], None) + name, None, [i["ifc"].MappedRepresentation for i in assigned_items], None + ) def create_materials(self): for material in self.ifc_parser.materials.values(): styled_item = self.create_styled_item(material) styled_representation = self.file.createIfcStyledRepresentation( - self.ifc_rep_context['Model']['Body']['MODEL_VIEW']['ifc'], None, None, [styled_item]) - if self.schema_version == 'IFC2X3': - material['ifc'] = self.file.createIfcMaterial(material['raw'].name) + self.ifc_rep_context["Model"]["Body"]["MODEL_VIEW"]["ifc"], None, None, [styled_item] + ) + if self.schema_version == "IFC2X3": + material["ifc"] = self.file.createIfcMaterial(material["raw"].name) else: - material['ifc'] = self.file.createIfcMaterial(material['raw'].name, None, None) + material["ifc"] = self.file.createIfcMaterial(material["raw"].name, None, None) self.create_material_psets(material) self.file.createIfcMaterialDefinitionRepresentation( - material['raw'].name, None, [styled_representation], material['ifc']) - if material['material_type'] == 'IfcMaterial': + material["raw"].name, None, [styled_representation], material["ifc"] + ) + if material["material_type"] == "IfcMaterial": continue - material_type = material['material_type'][0:-3] - self.cast_attributes(material_type, material['attributes']) - material['attributes']['Material'] = material['ifc'] - if material_type == 'IfcMaterialProfile': - material['attributes']['Profile'] = self.create_material_profile(material) - material['part_ifc'] = self.file.create_entity(material_type, - **material['attributes']) + material_type = material["material_type"][0:-3] + self.cast_attributes(material_type, material["attributes"]) + material["attributes"]["Material"] = material["ifc"] + if material_type == "IfcMaterialProfile": + material["attributes"]["Profile"] = self.create_material_profile(material) + material["part_ifc"] = self.file.create_entity(material_type, **material["attributes"]) def create_material_profile(self, material): - ifc_class = material['raw'].BIMMaterialProperties.profile_def - attributes = {a.name: a.string_value for a in material['raw'].BIMMaterialProperties.profile_attributes} + ifc_class = material["raw"].BIMMaterialProperties.profile_def + attributes = {a.name: a.string_value for a in material["raw"].BIMMaterialProperties.profile_attributes} self.cast_attributes(ifc_class, attributes) return self.file.create_entity(ifc_class, **attributes) @@ -2041,7 +2078,7 @@ class IfcExporter(): attributes[key] = self.cast_to_base_type(var_type, value) def cast_edge_case(self, ifc_class, key, value): - if key == 'RefLatitude' or key == 'RefLongitude': + if key == "RefLatitude" or key == "RefLongitude": return self.dd2dms(value) # TODO: migrate to ifcopenshell.util @@ -2049,38 +2086,42 @@ class IfcExporter(): dd = float(dd) sign = 1 if dd >= 0 else -1 dd = abs(dd) - minutes, seconds = divmod(dd*3600, 60) + minutes, seconds = divmod(dd * 3600, 60) degrees, minutes = divmod(minutes, 60) if dd < 0: degrees = -degrees return (int(degrees) * sign, int(minutes) * sign, int(seconds) * sign) def create_surface_style_rendering(self, styled_item): - surface_colour = self.create_colour_rgb(styled_item['raw'].diffuse_color) + surface_colour = self.create_colour_rgb(styled_item["raw"].diffuse_color) rendering_attributes = { - 'SurfaceColour': surface_colour, - 'Transparency': (styled_item['raw'].diffuse_color[3] - 1) * -1, - 'ReflectanceMethod': 'NOTDEFINED' + "SurfaceColour": surface_colour, + "Transparency": (styled_item["raw"].diffuse_color[3] - 1) * -1, + "ReflectanceMethod": "NOTDEFINED", } - rendering_attributes.update(self.get_rendering_attributes(styled_item['raw'])) - return self.file.create_entity('IfcSurfaceStyleRendering', **rendering_attributes) + rendering_attributes.update(self.get_rendering_attributes(styled_item["raw"])) + return self.file.create_entity("IfcSurfaceStyleRendering", **rendering_attributes) def get_rendering_attributes(self, material): - if not material.use_nodes \ - or not hasattr(material.node_tree, 'nodes') \ - or 'Principled BSDF' not in material.node_tree.nodes: + if ( + not material.use_nodes + or not hasattr(material.node_tree, "nodes") + or "Principled BSDF" not in material.node_tree.nodes + ): return {} - bsdf = material.node_tree.nodes['Principled BSDF'] + bsdf = material.node_tree.nodes["Principled BSDF"] return { - 'Transparency': (bsdf.inputs['Alpha'].default_value - 1) * -1, - 'DiffuseColour': self.create_colour_rgb(bsdf.inputs['Base Color'].default_value) + "Transparency": (bsdf.inputs["Alpha"].default_value - 1) * -1, + "DiffuseColour": self.create_colour_rgb(bsdf.inputs["Base Color"].default_value), } def get_material_external_definition(self, material): return { - 'Location': material.BIMMaterialProperties.location, - 'Identification': material.BIMMaterialProperties.identification if material.BIMMaterialProperties.identification else material.name, - 'Name': material.BIMMaterialProperties.name if material.BIMMaterialProperties.name else material.name + "Location": material.BIMMaterialProperties.location, + "Identification": material.BIMMaterialProperties.identification + if material.BIMMaterialProperties.identification + else material.name, + "Name": material.BIMMaterialProperties.name if material.BIMMaterialProperties.name else material.name, } def create_colour_rgb(self, colour): @@ -2088,7 +2129,7 @@ class IfcExporter(): def create_representations(self): for representation in self.ifc_parser.representations.values(): - representation['ifc'] = self.create_representation(representation) + representation["ifc"] = self.create_representation(representation) def create_grid_axes(self): for uvw in self.ifc_parser.grid_axes.values(): @@ -2097,12 +2138,18 @@ class IfcExporter(): self.create_grid_axis(axis) def create_grid_axis(self, axis): - points = [axis['grid_raw'].matrix_world.inverted() @ (axis['raw'].matrix_world @ v.co) for v in axis['raw'].data.vertices[0:2]] - self.cast_attributes('IfcGridAxis', axis['attributes']) - axis['attributes']['AxisCurve'] = self.file.createIfcPolyline([ - self.create_cartesian_point(points[0][0], points[0][1], points[0][2]), - self.create_cartesian_point(points[1][0], points[1][1], points[1][2])]) - axis['ifc'] = self.file.create_entity('IfcGridAxis', **axis['attributes']) + points = [ + axis["grid_raw"].matrix_world.inverted() @ (axis["raw"].matrix_world @ v.co) + for v in axis["raw"].data.vertices[0:2] + ] + self.cast_attributes("IfcGridAxis", axis["attributes"]) + axis["attributes"]["AxisCurve"] = self.file.createIfcPolyline( + [ + self.create_cartesian_point(points[0][0], points[0][1], points[0][2]), + self.create_cartesian_point(points[1][0], points[1][1], points[1][2]), + ] + ) + axis["ifc"] = self.file.create_entity("IfcGridAxis", **axis["attributes"]) def create_products(self): for product in self.ifc_parser.products: @@ -2114,91 +2161,94 @@ class IfcExporter(): properties = self.create_qto_properties(qto) if not properties: continue - qto['attributes'].update({ - 'GlobalId': ifcopenshell.guid.new(), - 'OwnerHistory': self.owner_history, - 'Quantities': properties - }) - qto['ifc'] = self.file.create_entity('IfcElementQuantity', **qto['attributes']) + qto["attributes"].update( + {"GlobalId": ifcopenshell.guid.new(), "OwnerHistory": self.owner_history, "Quantities": properties} + ) + qto["ifc"] = self.file.create_entity("IfcElementQuantity", **qto["attributes"]) def create_product(self, product): - if self.schema.declaration_by_name(product['class']).is_abstract(): + if self.schema.declaration_by_name(product["class"]).is_abstract(): self.ifc_export_settings.logger.error( 'The product "{}/{}" class is abstract and could not be created'.format( - product['class'], product['attributes']['Name'])) + product["class"], product["attributes"]["Name"] + ) + ) return - if product['relating_structure'] is not None: - placement_rel_to = self.ifc_parser.spatial_structure_elements[product['relating_structure']][ - 'ifc'].ObjectPlacement - elif product['relating_host'] is not None: + if product["relating_structure"] is not None: + placement_rel_to = self.ifc_parser.spatial_structure_elements[product["relating_structure"]][ + "ifc" + ].ObjectPlacement + elif product["relating_host"] is not None: # TODO: this could be unsafe if the host is not yet created, so we # should consider migrating it such that the placement rel to is set # as the relationship creation stage, like how IfcRelAggregates for # object aggregates work. - placement_rel_to = self.ifc_parser.products[product['relating_host']]['ifc'].ObjectPlacement + placement_rel_to = self.ifc_parser.products[product["relating_host"]]["ifc"].ObjectPlacement else: placement_rel_to = None - if product['has_scale']: + if product["has_scale"]: # Omission of the relative placement here is not as per implementer agreements placement = self.file.createIfcLocalPlacement(None, self.origin) else: - placement = self.file.createIfcLocalPlacement(placement_rel_to, - self.get_relative_placement(product, placement_rel_to)) + placement = self.file.createIfcLocalPlacement( + placement_rel_to, self.get_relative_placement(product, placement_rel_to) + ) - self.cast_attributes(product['class'], product['attributes']) + self.cast_attributes(product["class"], product["attributes"]) - product['attributes'].update({ - 'OwnerHistory': self.owner_history, # TODO: unhardcode - 'ObjectPlacement': placement, - 'Representation': self.get_product_shape(product) - }) + product["attributes"].update( + { + "OwnerHistory": self.owner_history, # TODO: unhardcode + "ObjectPlacement": placement, + "Representation": self.get_product_shape(product), + } + ) - if product['has_boundary_condition']: - ifc_class = product['boundary_condition_class'] - attributes = product['boundary_condition_attributes'] + if product["has_boundary_condition"]: + ifc_class = product["boundary_condition_class"] + attributes = product["boundary_condition_attributes"] for key, value in attributes.items(): - if value == 'True' or value == 'False': + if value == "True" or value == "False": attributes[key] = bool(value) else: attributes[key] = float(value) self.cast_attributes(ifc_class, attributes) boundary_condition = self.file.create_entity(ifc_class, **attributes) - product['attributes']['AppliedCondition'] = boundary_condition + product["attributes"]["AppliedCondition"] = boundary_condition - if product['class'] == 'IfcGrid': - name = 'IfcGrid/' + product['attributes']['Name'] - product['attributes']['UAxes'] = [a['ifc'] for a in self.ifc_parser.grid_axes[name]['UAxes']] - product['attributes']['VAxes'] = [a['ifc'] for a in self.ifc_parser.grid_axes[name]['VAxes']] - if self.ifc_parser.grid_axes[name]['WAxes']: - product['attributes']['WAxes'] = [a['ifc'] for a in self.ifc_parser.grid_axes[name]['WAxes']] + if product["class"] == "IfcGrid": + name = "IfcGrid/" + product["attributes"]["Name"] + product["attributes"]["UAxes"] = [a["ifc"] for a in self.ifc_parser.grid_axes[name]["UAxes"]] + product["attributes"]["VAxes"] = [a["ifc"] for a in self.ifc_parser.grid_axes[name]["VAxes"]] + if self.ifc_parser.grid_axes[name]["WAxes"]: + product["attributes"]["WAxes"] = [a["ifc"] for a in self.ifc_parser.grid_axes[name]["WAxes"]] try: - product['ifc'] = self.file.create_entity(product['class'], **product['attributes']) + product["ifc"] = self.file.create_entity(product["class"], **product["attributes"]) except RuntimeError as e: self.ifc_export_settings.logger.error( 'The product "{}/{}" could not be created: {}'.format( - product['class'], - product['attributes']['Name'], - e.args) + product["class"], product["attributes"]["Name"], e.args + ) ) def get_product_attribute_type(self, product_class, attribute_name): element_schema = schema.ifc.elements[product_class] - for a in element_schema['attributes']: - if a['name'] == attribute_name: - return a['type'] - if element_schema['parent'] in schema.ifc.elements: - return self.get_product_attribute_type(element_schema['parent'], attribute_name) + for a in element_schema["attributes"]: + if a["name"] == attribute_name: + return a["type"] + if element_schema["parent"] in schema.ifc.elements: + return self.get_product_attribute_type(element_schema["parent"], attribute_name) def cast_complex_attribute(self, product_class, attribute_name, attribute_value): element_schema = schema.ifc.elements[product_class] - for a in element_schema['complex_attributes']: - if a['name'] == attribute_name: - if not a['is_select']: - return a['type'] - for select_type in a['select_types']: + for a in element_schema["complex_attributes"]: + if a["name"] == attribute_name: + if not a["is_select"]: + return a["type"] + for select_type in a["select_types"]: try: return self.file.create_entity(select_type, attribute_value) except: @@ -2215,236 +2265,249 @@ class IfcExporter(): def get_product_shape_representations(self, product): results = [] - for representation_name in product['representations']: + for representation_name in product["representations"]: representation = self.ifc_parser.representations[representation_name] - if self.ifc_export_settings.should_roundtrip_native and representation['has_ifc_definition']: - results.append(representation['ifc']) + if self.ifc_export_settings.should_roundtrip_native and representation["has_ifc_definition"]: + results.append(representation["ifc"]) else: results.append(self.get_product_mapped_geometry(product, representation)) return results def get_product_mapped_geometry(self, product, representation): - mapping_source = representation['ifc'] + mapping_source = representation["ifc"] shape_representation = mapping_source.MappedRepresentation - if product['has_scale']: - if not product['has_mirror']: - product['scale'] = Vector(( - abs(product['scale'].x), - abs(product['scale'].y), - abs(product['scale'].z) - )) + if product["has_scale"]: + if not product["has_mirror"]: + product["scale"] = Vector((abs(product["scale"].x), abs(product["scale"].y), abs(product["scale"].z))) mapping_target = self.file.createIfcCartesianTransformationOperator3DnonUniform( - self.create_direction(product['forward_axis']), - self.create_direction(product['right_axis']), - self.create_cartesian_point( - product['location'].x, - product['location'].y, - product['location'].z - ), - product['scale'].x, - self.create_direction(product['up_axis']), - product['scale'].y, - product['scale'].z) + self.create_direction(product["forward_axis"]), + self.create_direction(product["right_axis"]), + self.create_cartesian_point(product["location"].x, product["location"].y, product["location"].z), + product["scale"].x, + self.create_direction(product["up_axis"]), + product["scale"].y, + product["scale"].z, + ) else: mapping_target = self.file.createIfcCartesianTransformationOperator3D( - self.create_direction(Vector((1, 0, 0))), - self.create_direction(Vector((0, 1, 0))), - self.create_cartesian_point(0, 0, 0), - 1, self.create_direction(Vector((0, 0, 1)))) + self.create_direction(Vector((1, 0, 0))), + self.create_direction(Vector((0, 1, 0))), + self.create_cartesian_point(0, 0, 0), + 1, + self.create_direction(Vector((0, 0, 1))), + ) mapped_item = self.file.createIfcMappedItem(mapping_source, mapping_target) return self.file.createIfcShapeRepresentation( - shape_representation.ContextOfItems, - shape_representation.RepresentationIdentifier, - 'MappedRepresentation', - [mapped_item]) + shape_representation.ContextOfItems, + shape_representation.RepresentationIdentifier, + "MappedRepresentation", + [mapped_item], + ) def create_ifc_axis_2_placement_2d(self, point, forward): return self.file.createIfcAxis2Placement2D( - self.create_cartesian_point(point.x, point.y), - self.file.createIfcDirection((forward.x, forward.y))) + self.create_cartesian_point(point.x, point.y), self.file.createIfcDirection((forward.x, forward.y)) + ) def create_ifc_axis_2_placement_3d(self, point, up, forward): return self.file.createIfcAxis2Placement3D( self.create_cartesian_point(point.x, point.y, point.z), self.file.createIfcDirection((up.x, up.y, up.z)), - self.file.createIfcDirection((forward.x, forward.y, forward.z))) + self.file.createIfcDirection((forward.x, forward.y, forward.z)), + ) def create_representation(self, representation): - if self.ifc_export_settings.should_roundtrip_native and representation['has_ifc_definition']: + if self.ifc_export_settings.should_roundtrip_native and representation["has_ifc_definition"]: return self.create_representation_from_definition(representation) self.ifc_vertices = [] self.ifc_edges = [] - if representation['context'] == 'Model': + if representation["context"] == "Model": return self.create_model_representation(representation) - elif representation['context'] == 'Plan': + elif representation["context"] == "Plan": return self.create_plan_representation(representation) - elif representation['context'] == 'NotDefined': + elif representation["context"] == "NotDefined": return self.create_variable_representation(representation) def create_representation_from_definition(self, representation): - if representation['ifc_definition']: - print('Authoring an IFC definition directly is not yet implemented') + if representation["ifc_definition"]: + print("Authoring an IFC definition directly is not yet implemented") return - elif representation['ifc_definition_id']: - entry = self.file.add(ifc.IfcStore.get_file().by_id(representation['ifc_definition_id'])) + elif representation["ifc_definition_id"]: + entry = self.file.add(ifc.IfcStore.get_file().by_id(representation["ifc_definition_id"])) substitutions = [] - for element in get_representation_elements( - ifc.IfcStore.get_file(), representation['ifc_definition_id']): + for element in get_representation_elements(ifc.IfcStore.get_file(), representation["ifc_definition_id"]): added_element = self.file.add(element) - if added_element.is_a('IfcGeometricRepresentationContext'): + if added_element.is_a("IfcGeometricRepresentationContext"): substitutions.append(added_element) for element in substitutions: - if element.is_a() == 'IfcGeometricRepresentationContext': - new_element = [e for e in - self.file.by_type('IfcGeometricRepresentationContext') - if e.ContextType == element.ContextType][0] - elif element.is_a() == 'IfcGeometricRepresentationSubContext': - new_element = [e for e in - self.file.by_type('IfcGeometricRepresentationContext') - if e.ContextType == element.ContextType and - e.ContextIdentifier == element.ContextIdentifier][0] + if element.is_a() == "IfcGeometricRepresentationContext": + new_element = [ + e + for e in self.file.by_type("IfcGeometricRepresentationContext") + if e.ContextType == element.ContextType + ][0] + elif element.is_a() == "IfcGeometricRepresentationSubContext": + new_element = [ + e + for e in self.file.by_type("IfcGeometricRepresentationContext") + if e.ContextType == element.ContextType and e.ContextIdentifier == element.ContextIdentifier + ][0] for inverse in self.file.get_inverse(element): ifcopenshell.util.element.replace_attribute(inverse, element, new_element) # TODO: Work out how and when to purge this - #self.file.remove(element) + # self.file.remove(element) return entry def create_model_representation(self, representation): - if representation['subcontext'] == 'Annotation': - return self.file.createIfcRepresentationMap(self.origin, - self.create_geometric_set_representation(representation)) - elif representation['subcontext'] == 'Axis': + if representation["subcontext"] == "Annotation": return self.file.createIfcRepresentationMap( - self.origin, self.create_curve3d_representation(representation)) - elif representation['subcontext'] == 'Body': + self.origin, self.create_geometric_set_representation(representation) + ) + elif representation["subcontext"] == "Axis": + return self.file.createIfcRepresentationMap(self.origin, self.create_curve3d_representation(representation)) + elif representation["subcontext"] == "Body": return self.create_variable_representation(representation) - elif representation['subcontext'] == 'Box': - return self.file.createIfcRepresentationMap(self.origin, - self.create_box_representation(representation)) - elif representation['subcontext'] == 'Clearance': + elif representation["subcontext"] == "Box": + return self.file.createIfcRepresentationMap(self.origin, self.create_box_representation(representation)) + elif representation["subcontext"] == "Clearance": return self.create_variable_representation(representation) - elif representation['subcontext'] == 'CoG': - return self.file.createIfcRepresentationMap(self.origin, - self.create_cog_representation(representation)) - elif representation['subcontext'] == 'FootPrint': + elif representation["subcontext"] == "CoG": + return self.file.createIfcRepresentationMap(self.origin, self.create_cog_representation(representation)) + elif representation["subcontext"] == "FootPrint": return self.create_variable_representation(representation) - elif representation['subcontext'] == 'Reference': - if representation['target_view'] == 'GRAPH_VIEW': + elif representation["subcontext"] == "Reference": + if representation["target_view"] == "GRAPH_VIEW": return self.file.createIfcRepresentationMap( - self.origin, self.create_structural_reference_representation(representation)) - elif representation['subcontext'] == 'Profile': + self.origin, self.create_structural_reference_representation(representation) + ) + elif representation["subcontext"] == "Profile": + return self.file.createIfcRepresentationMap(self.origin, self.create_curve3d_representation(representation)) + elif representation["subcontext"] == "SurveyPoints": return self.file.createIfcRepresentationMap( - self.origin, self.create_curve3d_representation(representation)) - elif representation['subcontext'] == 'SurveyPoints': - return self.file.createIfcRepresentationMap(self.origin, - self.create_geometric_curve_set_representation(representation)) + self.origin, self.create_geometric_curve_set_representation(representation) + ) def create_plan_representation(self, representation): - if representation['subcontext'] == 'Annotation': - if representation['is_text']: + if representation["subcontext"] == "Annotation": + if representation["is_text"]: shape_representation = self.create_text_representation(representation) else: shape_representation = self.create_geometric_curve_set_representation(representation, is_2d=True) - shape_representation.RepresentationType = 'Annotation2D' + shape_representation.RepresentationType = "Annotation2D" return self.file.createIfcRepresentationMap(self.origin, shape_representation) - elif representation['subcontext'] == 'Axis': - return self.file.createIfcRepresentationMap( - self.origin, self.create_curve2d_representation(representation)) - elif representation['subcontext'] == 'Body': + elif representation["subcontext"] == "Axis": + return self.file.createIfcRepresentationMap(self.origin, self.create_curve2d_representation(representation)) + elif representation["subcontext"] == "Body": pass - elif representation['subcontext'] == 'Box': + elif representation["subcontext"] == "Box": pass - elif representation['subcontext'] == 'Clearance': + elif representation["subcontext"] == "Clearance": pass - elif representation['subcontext'] == 'CoG': + elif representation["subcontext"] == "CoG": pass - elif representation['subcontext'] == 'FootPrint': - if representation['target_view'] in ['PLAN_VIEW', 'REFLECTED_PLAN_VIEW']: - return self.file.createIfcRepresentationMap(self.origin, - self.create_geometric_curve_set_representation(representation, is_2d=True)) - elif representation['subcontext'] == 'Reference': + elif representation["subcontext"] == "FootPrint": + if representation["target_view"] in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]: + return self.file.createIfcRepresentationMap( + self.origin, self.create_geometric_curve_set_representation(representation, is_2d=True) + ) + elif representation["subcontext"] == "Reference": pass - elif representation['subcontext'] == 'Profile': + elif representation["subcontext"] == "Profile": pass - elif representation['subcontext'] == 'SurveyPoints': + elif representation["subcontext"] == "SurveyPoints": pass def create_variable_representation(self, representation): - if representation['is_wireframe']: - return self.file.createIfcRepresentationMap(self.origin, - self.create_wireframe_representation(representation)) - elif representation['is_curve']: - return self.file.createIfcRepresentationMap(self.origin, - self.create_curve_representation(representation)) - elif representation['is_native']: - return self.file.createIfcRepresentationMap(self.origin, - self.create_native_representation(representation)) - elif representation['is_swept_solid']: - return self.file.createIfcRepresentationMap(self.origin, - self.create_swept_solid_representation(representation)) - elif representation['is_point_cloud']: - return self.file.createIfcRepresentationMap(self.origin, - self.create_point_cloud_representation(representation)) - return self.file.createIfcRepresentationMap(self.origin, - self.create_solid_representation(representation)) + if representation["is_wireframe"]: + return self.file.createIfcRepresentationMap( + self.origin, self.create_wireframe_representation(representation) + ) + elif representation["is_curve"]: + return self.file.createIfcRepresentationMap(self.origin, self.create_curve_representation(representation)) + elif representation["is_native"]: + return self.file.createIfcRepresentationMap(self.origin, self.create_native_representation(representation)) + elif representation["is_swept_solid"]: + return self.file.createIfcRepresentationMap( + self.origin, self.create_swept_solid_representation(representation) + ) + elif representation["is_point_cloud"]: + return self.file.createIfcRepresentationMap( + self.origin, self.create_point_cloud_representation(representation) + ) + return self.file.createIfcRepresentationMap(self.origin, self.create_solid_representation(representation)) def create_box_representation(self, representation): - obj = representation['raw_object'] + obj = representation["raw_object"] bounding_box = self.file.createIfcBoundingBox( - self.create_cartesian_point( - obj.bound_box[0][0], - obj.bound_box[0][1], - obj.bound_box[0][2] - ), + self.create_cartesian_point(obj.bound_box[0][0], obj.bound_box[0][1], obj.bound_box[0][2]), self.convert_si_to_unit(obj.dimensions[0]), self.convert_si_to_unit(obj.dimensions[1]), - self.convert_si_to_unit(obj.dimensions[2]) + self.convert_si_to_unit(obj.dimensions[2]), ) return self.file.createIfcShapeRepresentation( - self.ifc_rep_context[representation['context']][representation['subcontext']][ - representation['target_view']]['ifc'], - representation['subcontext'], 'BoundingBox', [bounding_box]) + self.ifc_rep_context[representation["context"]][representation["subcontext"]][ + representation["target_view"] + ]["ifc"], + representation["subcontext"], + "BoundingBox", + [bounding_box], + ) def create_cog_representation(self, representation): - mesh = representation['raw'] - cog = self.create_cartesian_point( - mesh.vertices[0].co.x, mesh.vertices[0].co.y, mesh.vertices[0].co.z) + mesh = representation["raw"] + cog = self.create_cartesian_point(mesh.vertices[0].co.x, mesh.vertices[0].co.y, mesh.vertices[0].co.z) return self.file.createIfcShapeRepresentation( - self.ifc_rep_context[representation['context']][representation['subcontext']][ - representation['target_view']]['ifc'], - representation['subcontext'], - 'BoundingBox', - [cog]) + self.ifc_rep_context[representation["context"]][representation["subcontext"]][ + representation["target_view"] + ]["ifc"], + representation["subcontext"], + "BoundingBox", + [cog], + ) def create_text_representation(self, representation): return self.file.createIfcShapeRepresentation( - self.ifc_rep_context[representation['context']][representation['subcontext']][ - representation['target_view']]['ifc'], - representation['subcontext'], - 'Annotation2D', - [self.create_text(representation['raw'])]) + self.ifc_rep_context[representation["context"]][representation["subcontext"]][ + representation["target_view"] + ]["ifc"], + representation["subcontext"], + "Annotation2D", + [self.create_text(representation["raw"])], + ) def create_wireframe_representation(self, representation): return self.file.createIfcShapeRepresentation( - self.ifc_rep_context[representation['context']][representation['subcontext']][ - representation['target_view']]['ifc'], - representation['subcontext'], - 'Curve', - self.create_curves(representation['raw'])) + self.ifc_rep_context[representation["context"]][representation["subcontext"]][ + representation["target_view"] + ]["ifc"], + representation["subcontext"], + "Curve", + self.create_curves(representation["raw"]), + ) def create_geometric_set_representation(self, representation, is_2d=False): - geometric_curve_set = self.file.createIfcGeometricSet(self.create_curves(representation['raw'], is_2d=is_2d)) + geometric_curve_set = self.file.createIfcGeometricSet(self.create_curves(representation["raw"], is_2d=is_2d)) return self.file.createIfcShapeRepresentation( - self.ifc_rep_context[representation['context']][representation['subcontext']][ - representation['target_view']]['ifc'], - representation['subcontext'], 'GeometricSet', [geometric_curve_set]) + self.ifc_rep_context[representation["context"]][representation["subcontext"]][ + representation["target_view"] + ]["ifc"], + representation["subcontext"], + "GeometricSet", + [geometric_curve_set], + ) def create_geometric_curve_set_representation(self, representation, is_2d=False): - geometric_curve_set = self.file.createIfcGeometricCurveSet(self.create_curves(representation['raw'], is_2d=is_2d)) + geometric_curve_set = self.file.createIfcGeometricCurveSet( + self.create_curves(representation["raw"], is_2d=is_2d) + ) return self.file.createIfcShapeRepresentation( - self.ifc_rep_context[representation['context']][representation['subcontext']][ - representation['target_view']]['ifc'], - representation['subcontext'], 'GeometricCurveSet', [geometric_curve_set]) + self.ifc_rep_context[representation["context"]][representation["subcontext"]][ + representation["target_view"] + ]["ifc"], + representation["subcontext"], + "GeometricCurveSet", + [geometric_curve_set], + ) # https://medium.com/@behreajj/scripting-curves-in-blender-with-python-c487097efd13 # https://blender.stackexchange.com/questions/30597/python-up-vector-math-for-curve @@ -2466,61 +2529,76 @@ class IfcExporter(): def create_curve3d_representation(self, representation): return self.file.createIfcShapeRepresentation( - self.ifc_rep_context[representation['context']][representation['subcontext']][ - representation['target_view']]['ifc'], - representation['subcontext'], 'Curve3D', - self.create_curves(representation['raw'])) + self.ifc_rep_context[representation["context"]][representation["subcontext"]][ + representation["target_view"] + ]["ifc"], + representation["subcontext"], + "Curve3D", + self.create_curves(representation["raw"]), + ) def create_curve2d_representation(self, representation): return self.file.createIfcShapeRepresentation( - self.ifc_rep_context[representation['context']][representation['subcontext']][ - representation['target_view']]['ifc'], - representation['subcontext'], 'Curve2D', - self.create_curves(representation['raw'], is_2d=True)) + self.ifc_rep_context[representation["context"]][representation["subcontext"]][ + representation["target_view"] + ]["ifc"], + representation["subcontext"], + "Curve2D", + self.create_curves(representation["raw"], is_2d=True), + ) def create_structural_reference_representation(self, representation): - if representation['raw_object'].type == 'EMPTY': + if representation["raw_object"].type == "EMPTY": return self.file.createIfcTopologyRepresentation( - self.ifc_rep_context[representation['context']][representation['subcontext']][ - representation['target_view']]['ifc'], - representation['subcontext'], 'Vertex', - [self.create_vertex_point(Vector((0, 0, 0)))]) + self.ifc_rep_context[representation["context"]][representation["subcontext"]][ + representation["target_view"] + ]["ifc"], + representation["subcontext"], + "Vertex", + [self.create_vertex_point(Vector((0, 0, 0)))], + ) return self.file.createIfcTopologyRepresentation( - self.ifc_rep_context[representation['context']][representation['subcontext']][ - representation['target_view']]['ifc'], - representation['subcontext'], 'Edge', - [self.create_edge(representation['raw'])]) + self.ifc_rep_context[representation["context"]][representation["subcontext"]][ + representation["target_view"] + ]["ifc"], + representation["subcontext"], + "Edge", + [self.create_edge(representation["raw"])], + ) def create_curve_representation(self, representation): - if representation['raw'].bevel_object: + if representation["raw"].bevel_object: swept_area_solids = self.create_extruded_area_solids(representation) else: swept_area_solids = self.create_swept_disk_solids(representation) return self.file.createIfcShapeRepresentation( - self.ifc_rep_context[representation['context']][representation['subcontext']][ - representation['target_view']]['ifc'], - representation['subcontext'], 'AdvancedSweptSolid', - swept_area_solids) + self.ifc_rep_context[representation["context"]][representation["subcontext"]][ + representation["target_view"] + ]["ifc"], + representation["subcontext"], + "AdvancedSweptSolid", + swept_area_solids, + ) def create_swept_disk_solids(self, representation): results = [] - radius = self.convert_si_to_unit(representation['raw'].bevel_depth) - start_param = representation['raw'].bevel_factor_start - end_param = representation['raw'].bevel_factor_end - directrixes = self.create_curves(representation['raw']) + radius = self.convert_si_to_unit(representation["raw"].bevel_depth) + start_param = representation["raw"].bevel_factor_start + end_param = representation["raw"].bevel_factor_end + directrixes = self.create_curves(representation["raw"]) for directrix in directrixes: - results.append(self.file.createIfcSweptDiskSolid( - directrix, radius, None, start_param, end_param)) + results.append(self.file.createIfcSweptDiskSolid(directrix, radius, None, start_param, end_param)) return results def create_extruded_area_solids(self, representation): # TODO: support unclosed surfaces - swept_area = self.file.createIfcArbitraryClosedProfileDef('AREA', None, - self.create_curves(representation['raw'].bevel_object.data)[0]) - if (representation['raw'].bevel_object.scale - Vector((1, 1, 1))).length > 0.01: - self.scale_ifc_representation(swept_area, representation['raw'].bevel_object.scale) + swept_area = self.file.createIfcArbitraryClosedProfileDef( + "AREA", None, self.create_curves(representation["raw"].bevel_object.data)[0] + ) + if (representation["raw"].bevel_object.scale - Vector((1, 1, 1))).length > 0.01: + self.scale_ifc_representation(swept_area, representation["raw"].bevel_object.scale) swept_area_solids = [] - for spline in representation['raw'].splines: + for spline in representation["raw"].splines: points = self.get_spline_points(spline) if not points: continue @@ -2539,14 +2617,17 @@ class IfcExporter(): # pt2=next_point.handle_left, # pt3=next_point.co, # step=j_percent) - tilt_matrix = Matrix.Rotation(points[0].tilt, 4, 'Z') - x_axis = unit_direction.to_track_quat('-Y', 'Z') @ Vector((1, 0, 0)) @ tilt_matrix - position = self.create_ifc_axis_2_placement_3d( - points[1].co, unit_direction, x_axis) - swept_area_solids.append(self.file.createIfcExtrudedAreaSolid( - swept_area, position, - self.file.createIfcDirection((0., 0., 1.)), - self.convert_si_to_unit(direction.length))) + tilt_matrix = Matrix.Rotation(points[0].tilt, 4, "Z") + x_axis = unit_direction.to_track_quat("-Y", "Z") @ Vector((1, 0, 0)) @ tilt_matrix + position = self.create_ifc_axis_2_placement_3d(points[1].co, unit_direction, x_axis) + swept_area_solids.append( + self.file.createIfcExtrudedAreaSolid( + swept_area, + position, + self.file.createIfcDirection((0.0, 0.0, 1.0)), + self.convert_si_to_unit(direction.length), + ) + ) # TODO: support other types of swept areas # swept_area_solid = self.file.createIfcFixedReferenceSweptAreaSolid( # swept_area, self.origin, # self.create_curves(representation['raw'])[0], @@ -2555,49 +2636,46 @@ class IfcExporter(): def scale_ifc_representation(self, rep, scale): for element in self.file.traverse(rep): - if not element.is_a('IfcCartesianPoint'): + if not element.is_a("IfcCartesianPoint"): continue - element.Coordinates = tuple(Vector(element.Coordinates) @ Matrix(( - (scale[0], 0, 0), - (0, scale[1], 0), - (0, 0, scale[2])))) + element.Coordinates = tuple( + Vector(element.Coordinates) @ Matrix(((scale[0], 0, 0), (0, scale[1], 0), (0, 0, scale[2]))) + ) def create_vertex_point(self, point): - return self.file.createIfcVertexPoint( - self.create_cartesian_point(point.x, point.y, point.z)) + return self.file.createIfcVertexPoint(self.create_cartesian_point(point.x, point.y, point.z)) def get_spline_points(self, spline): return spline.bezier_points if spline.bezier_points else spline.points def create_edge(self, curve): - if hasattr(curve, 'splines'): + if hasattr(curve, "splines"): points = self.get_spline_points(curve.splines[0]) else: points = curve.vertices if not points: return - return self.file.createIfcEdge( - self.create_vertex_point(points[0].co), - self.create_vertex_point(points[1].co)) + return self.file.createIfcEdge(self.create_vertex_point(points[0].co), self.create_vertex_point(points[1].co)) def create_text(self, text): - if text.align_y in ['TOP_BASELINE', 'BOTTOM_BASELINE', 'BOTTOM']: - y = 'bottom' - elif text.align_y == 'CENTER': - y = 'middle' - elif text.align_y == 'TOP': - y = 'top' + if text.align_y in ["TOP_BASELINE", "BOTTOM_BASELINE", "BOTTOM"]: + y = "bottom" + elif text.align_y == "CENTER": + y = "middle" + elif text.align_y == "TOP": + y = "top" - if text.align_x == 'LEFT': - x = 'left' - elif text.align_x == 'CENTER': - x = 'middle' - elif text.align_x == 'RIGHT': - x = 'right' + if text.align_x == "LEFT": + x = "left" + elif text.align_x == "CENTER": + x = "middle" + elif text.align_x == "RIGHT": + x = "right" # TODO: Planar extent right now is wrong ... - return self.file.createIfcTextLiteralWithExtent(text.body, self.origin, - 'RIGHT', self.file.createIfcPlanarExtent(1000, 1000), f'{y}-{x}') + return self.file.createIfcTextLiteralWithExtent( + text.body, self.origin, "RIGHT", self.file.createIfcPlanarExtent(1000, 1000), f"{y}-{x}" + ) def create_curves(self, curve, is_2d=False): if isinstance(curve, bpy.types.Mesh): @@ -2612,16 +2690,16 @@ class IfcExporter(): previous_edge = None edge_loop = [] for edge in mesh.edges: - if ((Vector(points.CoordList[edge.vertices[0]]) - Vector(points.CoordList[edge.vertices[1]])).length < 0.001): + if (Vector(points.CoordList[edge.vertices[0]]) - Vector(points.CoordList[edge.vertices[1]])).length < 0.001: # Maybe we should warn the user to weld vertices in this scenario? continue elif previous_edge is None: - edge_loop = [self.file.createIfcLineIndex((edge.vertices[0]+1, edge.vertices[1]+1))] + edge_loop = [self.file.createIfcLineIndex((edge.vertices[0] + 1, edge.vertices[1] + 1))] elif edge.vertices[0] == previous_edge.vertices[1]: - edge_loop.append(self.file.createIfcLineIndex((edge.vertices[0]+1, edge.vertices[1]+1))) + edge_loop.append(self.file.createIfcLineIndex((edge.vertices[0] + 1, edge.vertices[1] + 1))) else: edge_loops.append(edge_loop) - edge_loop = [self.file.createIfcLineIndex((edge.vertices[0]+1, edge.vertices[1]+1))] + edge_loop = [self.file.createIfcLineIndex((edge.vertices[0] + 1, edge.vertices[1] + 1))] previous_edge = edge edge_loops.append(edge_loop) for edge_loop in edge_loops: @@ -2635,91 +2713,93 @@ class IfcExporter(): points = [] for point in spline.bezier_points: if is_2d: - points.append(self.create_cartesian_point( - point.co.x, point.co.y)) + points.append(self.create_cartesian_point(point.co.x, point.co.y)) else: - points.append(self.create_cartesian_point( - point.co.x, point.co.y, point.co.z)) + points.append(self.create_cartesian_point(point.co.x, point.co.y, point.co.z)) for point in spline.points: if is_2d: - points.append(self.create_cartesian_point( - point.co.x, point.co.y)) + points.append(self.create_cartesian_point(point.co.x, point.co.y)) else: - points.append(self.create_cartesian_point( - point.co.x, point.co.y, point.co.z)) + points.append(self.create_cartesian_point(point.co.x, point.co.y, point.co.z)) if spline.use_cyclic_u: points.append(points[0]) results.append(self.file.createIfcPolyline(points)) return results def create_native_representation(self, representation): - obj = representation['raw_object'] + obj = representation["raw_object"] items = {} for index, vg in enumerate(obj.vertex_groups): - components = vg.name.split('/') + components = vg.name.split("/") key = components[1] - if components[0] == 'Item': - items[key] = { - 'name': components[2], - 'subitems': {} - } - elif components[0] == 'Subitem': - items[key]['subitems'][components[2]] = self.get_vertices_in_vertex_group(obj, index) + if components[0] == "Item": + items[key] = {"name": components[2], "subitems": {}} + elif components[0] == "Subitem": + items[key]["subitems"][components[2]] = self.get_vertices_in_vertex_group(obj, index) ifc_items = [] for item in items.values(): - if item['name'] == 'IfcExtrudedAreaSolid': + if item["name"] == "IfcExtrudedAreaSolid": ifc_items.append(self.create_native_extruded_area_solid(obj, item)) - elif item['name'] == 'IfcFacetedBrep': + elif item["name"] == "IfcFacetedBrep": # TODO: check if we allow representation item type mixing return self.create_solid_representation(representation) return self.file.createIfcShapeRepresentation( - self.ifc_rep_context[representation['context']][representation['subcontext']][ - representation['target_view']]['ifc'], - representation['subcontext'], 'SweptSolid', ifc_items) + self.ifc_rep_context[representation["context"]][representation["subcontext"]][ + representation["target_view"] + ]["ifc"], + representation["subcontext"], + "SweptSolid", + ifc_items, + ) def get_vertices_in_vertex_group(self, obj, vg_index): return [v.index for v in obj.data.vertices if vg_index in [g.group for g in v.groups]] def create_native_extruded_area_solid(self, obj, item): - extrusion_edge = self.get_edges_in_v_indices(obj, item['subitems']['ExtrudedDirection'])[0] + extrusion_edge = self.get_edges_in_v_indices(obj, item["subitems"]["ExtrudedDirection"])[0] - if 'IfcArbitraryClosedProfileDef' in item['subitems']: - outer_curve_loop = self.get_loop_from_v_indices(obj, item['subitems']['IfcArbitraryClosedProfileDef']) + if "IfcArbitraryClosedProfileDef" in item["subitems"]: + outer_curve_loop = self.get_loop_from_v_indices(obj, item["subitems"]["IfcArbitraryClosedProfileDef"]) curve_ucs = self.get_curve_profile_coordinate_system(obj, outer_curve_loop) outer_curve = self.create_polyline_from_loop(obj, outer_curve_loop, curve_ucs) - curve = self.file.createIfcArbitraryClosedProfileDef('AREA', None, outer_curve) - elif 'IfcRectangleProfileDef' in item['subitems']: - outer_curve_loop = self.get_loop_from_v_indices(obj, item['subitems']['IfcRectangleProfileDef']) + curve = self.file.createIfcArbitraryClosedProfileDef("AREA", None, outer_curve) + elif "IfcRectangleProfileDef" in item["subitems"]: + outer_curve_loop = self.get_loop_from_v_indices(obj, item["subitems"]["IfcRectangleProfileDef"]) curve_ucs = self.get_curve_profile_coordinate_system(obj, outer_curve_loop) xdim = self.convert_si_to_unit( - (obj.data.vertices[outer_curve_loop[0]].co - obj.data.vertices[outer_curve_loop[1]].co).length) + (obj.data.vertices[outer_curve_loop[0]].co - obj.data.vertices[outer_curve_loop[1]].co).length + ) ydim = self.convert_si_to_unit( - (obj.data.vertices[outer_curve_loop[1]].co - obj.data.vertices[outer_curve_loop[2]].co).length) - curve = self.file.createIfcRectangleProfileDef('AREA', None, None, xdim, ydim) - elif 'IfcCircleProfileDef' in item['subitems']: - indices = item['subitems']['IfcCircleProfileDef'] + (obj.data.vertices[outer_curve_loop[1]].co - obj.data.vertices[outer_curve_loop[2]].co).length + ) + curve = self.file.createIfcRectangleProfileDef("AREA", None, None, xdim, ydim) + elif "IfcCircleProfileDef" in item["subitems"]: + indices = item["subitems"]["IfcCircleProfileDef"] outer_curve_loop = self.get_loop_from_v_indices(obj, indices) curve_ucs = self.get_curve_profile_coordinate_system(obj, outer_curve_loop) - radius = self.convert_si_to_unit(abs((obj.data.vertices[indices[0]].co - - obj.data.vertices[indices[int(len(indices)/2)]].co).length) / 2) + radius = self.convert_si_to_unit( + abs((obj.data.vertices[indices[0]].co - obj.data.vertices[indices[int(len(indices) / 2)]].co).length) + / 2 + ) center = Vector((0, 0)) position = self.create_ifc_axis_2_placement_2d(center, Vector((1, 0))) - curve = self.file.createIfcCircleProfileDef('AREA', None, position, radius) + curve = self.file.createIfcCircleProfileDef("AREA", None, position, radius) - position = self.create_ifc_axis_2_placement_3d( - curve_ucs['center'], curve_ucs['z_axis'], curve_ucs['x_axis']) + position = self.create_ifc_axis_2_placement_3d(curve_ucs["center"], curve_ucs["z_axis"], curve_ucs["x_axis"]) direction = self.get_extrusion_direction(obj, outer_curve_loop, extrusion_edge, curve_ucs) unit_direction = direction.normalized() return self.file.createIfcExtrudedAreaSolid( - curve, position, self.file.createIfcDirection(( - unit_direction.x, unit_direction.y, unit_direction.z)), - self.convert_si_to_unit(direction.length)) + curve, + position, + self.file.createIfcDirection((unit_direction.x, unit_direction.y, unit_direction.z)), + self.convert_si_to_unit(direction.length), + ) def create_swept_solid_representation(self, representation): # TODO: deprecate this in favour of native representations - obj = representation['raw_object'] - mesh = representation['raw'] + obj = representation["raw_object"] + mesh = representation["raw"] items = [] for swept_solid in mesh.BIMMeshProperties.swept_solids: extrusion_edge = self.get_edges_in_v_indices(obj, json.loads(swept_solid.extrusion))[0] @@ -2729,32 +2809,39 @@ class IfcExporter(): for indices in json.loads(swept_solid.inner_curves): loop = self.get_loop_from_v_indices(obj, indices) curve_ucs = self.get_curve_profile_coordinate_system(obj, loop) - inner_curves.append( - self.create_polyline_from_loop(obj, loop, curve_ucs)) + inner_curves.append(self.create_polyline_from_loop(obj, loop, curve_ucs)) outer_curve_loop = self.get_loop_from_v_indices(obj, json.loads(swept_solid.outer_curve)) curve_ucs = self.get_curve_profile_coordinate_system(obj, outer_curve_loop) outer_curve = self.create_polyline_from_loop(obj, outer_curve_loop, curve_ucs) if inner_curves: - curve = self.file.createIfcArbitraryProfileDefWithVoids('AREA', None, - outer_curve, inner_curves) + curve = self.file.createIfcArbitraryProfileDefWithVoids("AREA", None, outer_curve, inner_curves) else: - curve = self.file.createIfcArbitraryClosedProfileDef('AREA', None, outer_curve) + curve = self.file.createIfcArbitraryClosedProfileDef("AREA", None, outer_curve) direction = self.get_extrusion_direction(obj, outer_curve_loop, extrusion_edge, curve_ucs) unit_direction = direction.normalized() position = self.create_ifc_axis_2_placement_3d( - curve_ucs['center'], curve_ucs['z_axis'], curve_ucs['x_axis']) + curve_ucs["center"], curve_ucs["z_axis"], curve_ucs["x_axis"] + ) - items.append(self.file.createIfcExtrudedAreaSolid( - curve, position, self.file.createIfcDirection(( - unit_direction.x, unit_direction.y, unit_direction.z)), - self.convert_si_to_unit(direction.length))) + items.append( + self.file.createIfcExtrudedAreaSolid( + curve, + position, + self.file.createIfcDirection((unit_direction.x, unit_direction.y, unit_direction.z)), + self.convert_si_to_unit(direction.length), + ) + ) return self.file.createIfcShapeRepresentation( - self.ifc_rep_context[representation['context']][representation['subcontext']][ - representation['target_view']]['ifc'], - representation['subcontext'], 'SweptSolid', items) + self.ifc_rep_context[representation["context"]][representation["subcontext"]][ + representation["target_view"] + ]["ifc"], + representation["subcontext"], + "SweptSolid", + items, + ) def get_start_and_end_of_extrusion(self, profile_points, extrusion_edge): if extrusion_edge.vertices[0] in profile_points: @@ -2762,12 +2849,10 @@ class IfcExporter(): return (extrusion_edge.vertices[1], extrusion_edge.vertices[0]) def get_curve_profile_coordinate_system(self, obj, loop): - profile_face = bpy.data.meshes.new('profile_face') - profile_verts = [( - obj.data.vertices[p].co.x, - obj.data.vertices[p].co.y, - obj.data.vertices[p].co.z - ) for p in loop] + profile_face = bpy.data.meshes.new("profile_face") + profile_verts = [ + (obj.data.vertices[p].co.x, obj.data.vertices[p].co.y, obj.data.vertices[p].co.z) for p in loop + ] profile_faces = [tuple(range(0, len(profile_verts)))] profile_face.from_pydata(profile_verts, [], profile_faces) center = profile_face.polygons[0].center @@ -2780,26 +2865,24 @@ class IfcExporter(): matrix = Matrix((x_axis, y_axis, z_axis)) matrix.normalize() return { - 'center': center, - 'x_axis': x_axis, - 'y_axis': y_axis, - 'z_axis': z_axis, - 'matrix': matrix.to_4x4() @ Matrix.Translation(-center) + "center": center, + "x_axis": x_axis, + "y_axis": y_axis, + "z_axis": z_axis, + "matrix": matrix.to_4x4() @ Matrix.Translation(-center), } def create_polyline_from_loop(self, obj, loop, curve_ucs): points = [] for point in loop: - transformed_point = curve_ucs['matrix'] @ obj.data.vertices[point].co - points.append(self.create_cartesian_point( - transformed_point.x, transformed_point.y)) + transformed_point = curve_ucs["matrix"] @ obj.data.vertices[point].co + points.append(self.create_cartesian_point(transformed_point.x, transformed_point.y)) points.append(points[0]) return self.file.createIfcPolyline(points) def get_extrusion_direction(self, obj, outer_curve_loop, extrusion_edge, curve_ucs): start, end = self.get_start_and_end_of_extrusion(outer_curve_loop, extrusion_edge) - return curve_ucs['matrix'] @ ( - curve_ucs['center'] + (obj.data.vertices[end].co - obj.data.vertices[start].co)) + return curve_ucs["matrix"] @ (curve_ucs["center"] + (obj.data.vertices[end].co - obj.data.vertices[start].co)) def get_loop_from_v_indices(self, obj, indices): edges = self.get_edges_in_v_indices(obj, indices) @@ -2808,8 +2891,7 @@ class IfcExporter(): return loop def get_edges_in_v_indices(self, obj, indices): - return [e for e in obj.data.edges - if (e.vertices[0] in indices and e.vertices[1] in indices)] + return [e for e in obj.data.edges if (e.vertices[0] in indices and e.vertices[1] in indices)] def get_loop_from_edges(self, edges): while edges: @@ -2848,54 +2930,77 @@ class IfcExporter(): def create_point_cloud_representation(self, representation): import space_view3d_point_cloud_visualizer as pcv - if representation['raw'].uuid not in pcv.PCVManager.cache: + + if representation["raw"].uuid not in pcv.PCVManager.cache: return return self.file.createIfcShapeRepresentation( - self.ifc_rep_context[representation['context']][representation['subcontext']][ - representation['target_view']]['ifc'], - representation['subcontext'], 'PointCloud', - [self.file.createIfcCartesianPointList3D( - pcv.PCVManager.cache[representation['raw'].uuid]['points'].tolist())]) + self.ifc_rep_context[representation["context"]][representation["subcontext"]][ + representation["target_view"] + ]["ifc"], + representation["subcontext"], + "PointCloud", + [ + self.file.createIfcCartesianPointList3D( + pcv.PCVManager.cache[representation["raw"].uuid]["points"].tolist() + ) + ], + ) def create_solid_representation(self, representation): - mesh = representation['raw'] - if not representation['is_parametric']: - mesh = representation['raw_object'].evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh() - if self.schema_version == 'IFC2X3' or self.ifc_export_settings.should_force_faceted_brep: + mesh = representation["raw"] + if not representation["is_parametric"]: + mesh = representation["raw_object"].evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh() + if self.schema_version == "IFC2X3" or self.ifc_export_settings.should_force_faceted_brep: return self.create_faceted_brep(representation, mesh) return self.create_polygonal_face_set(representation, mesh) def create_polygonal_face_set(self, representation, mesh): - n_slots = max(1, len(representation['raw_object'].material_slots)) + n_slots = max(1, len(representation["raw_object"].material_slots)) ifc_raw_items = [None] * n_slots for i, value in enumerate(ifc_raw_items): ifc_raw_items[i] = [] for polygon in mesh.polygons: - ifc_raw_items[polygon.material_index % n_slots].append(self.file.createIfcIndexedPolygonalFace([v+1 for v in polygon.vertices])) + ifc_raw_items[polygon.material_index % n_slots].append( + self.file.createIfcIndexedPolygonalFace([v + 1 for v in polygon.vertices]) + ) coordinates = self.file.createIfcCartesianPointList3D([self.convert_si_to_unit(v.co) for v in mesh.vertices]) items = [self.file.createIfcPolygonalFaceSet(coordinates, None, i) for i in ifc_raw_items if i] return self.file.createIfcShapeRepresentation( - self.ifc_rep_context[representation['context']][representation['subcontext']][ - representation['target_view']]['ifc'], - representation['subcontext'], 'Tessellation', items) + self.ifc_rep_context[representation["context"]][representation["subcontext"]][ + representation["target_view"] + ]["ifc"], + representation["subcontext"], + "Tessellation", + items, + ) def create_faceted_brep(self, representation, mesh): self.create_vertices(mesh.vertices) - n_slots = max(1, len(representation['raw_object'].material_slots)) + n_slots = max(1, len(representation["raw_object"].material_slots)) ifc_raw_items = [None] * n_slots for i, value in enumerate(ifc_raw_items): ifc_raw_items[i] = [] for polygon in mesh.polygons: - ifc_raw_items[polygon.material_index % n_slots].append(self.file.createIfcFace([ - self.file.createIfcFaceOuterBound( - self.file.createIfcPolyLoop([self.ifc_vertices[vertice] for vertice in polygon.vertices]), - True)])) + ifc_raw_items[polygon.material_index % n_slots].append( + self.file.createIfcFace( + [ + self.file.createIfcFaceOuterBound( + self.file.createIfcPolyLoop([self.ifc_vertices[vertice] for vertice in polygon.vertices]), + True, + ) + ] + ) + ) # TODO: May not actually be a closed shell, but who checks anyway? items = [self.file.createIfcFacetedBrep(self.file.createIfcClosedShell(i)) for i in ifc_raw_items if i] return self.file.createIfcShapeRepresentation( - self.ifc_rep_context[representation['context']][representation['subcontext']][ - representation['target_view']]['ifc'], - representation['subcontext'], 'Brep', items) + self.ifc_rep_context[representation["context"]][representation["subcontext"]][ + representation["target_view"] + ]["ifc"], + representation["subcontext"], + "Brep", + items, + ) def create_cartesian_point_list_from_vertices(self, vertices, is_2d=False): if is_2d: @@ -2927,154 +3032,232 @@ class IfcExporter(): for relating_building_element, related_opening_elements in self.ifc_parser.rel_voids_elements.items(): for related_opening_element in related_opening_elements: self.file.createIfcRelVoidsElement( - ifcopenshell.guid.new(), self.owner_history, None, None, - self.ifc_parser.products[relating_building_element]['ifc'], - self.ifc_parser.products[related_opening_element]['ifc'] + ifcopenshell.guid.new(), + self.owner_history, + None, + None, + self.ifc_parser.products[relating_building_element]["ifc"], + self.ifc_parser.products[related_opening_element]["ifc"], ) def relate_opening_elements_to_fillings(self): for relating_opening_element, related_building_elements in self.ifc_parser.rel_fills_elements.items(): for related_building_element in related_building_elements: self.file.createIfcRelFillsElement( - ifcopenshell.guid.new(), self.owner_history, None, None, - self.ifc_parser.products[relating_opening_element]['ifc'], - self.ifc_parser.products[related_building_element]['ifc'] + ifcopenshell.guid.new(), + self.owner_history, + None, + None, + self.ifc_parser.products[relating_opening_element]["ifc"], + self.ifc_parser.products[related_building_element]["ifc"], ) def relate_objects_to_projection_elements(self): for relating_building_element, related_projection_elements in self.ifc_parser.rel_projects_elements.items(): for related_projection_element in related_projection_elements: self.file.createIfcRelProjectsElement( - ifcopenshell.guid.new(), self.owner_history, None, None, - self.ifc_parser.products[relating_building_element]['ifc'], - self.ifc_parser.products[related_projection_element]['ifc'] + ifcopenshell.guid.new(), + self.owner_history, + None, + None, + self.ifc_parser.products[relating_building_element]["ifc"], + self.ifc_parser.products[related_projection_element]["ifc"], ) def relate_elements_to_spatial_structures(self): for relating_structure, related_elements in self.ifc_parser.rel_contained_in_spatial_structure.items(): self.file.createIfcRelContainedInSpatialStructure( - ifcopenshell.guid.new(), self.owner_history, None, None, - [self.ifc_parser.products[e]['ifc'] for e in related_elements], - self.ifc_parser.spatial_structure_elements[relating_structure]['ifc']) + ifcopenshell.guid.new(), + self.owner_history, + None, + None, + [self.ifc_parser.products[e]["ifc"] for e in related_elements], + self.ifc_parser.spatial_structure_elements[relating_structure]["ifc"], + ) def relate_nested_elements_to_hosted_elements(self): for relating_object, related_objects in self.ifc_parser.rel_nests.items(): self.file.createIfcRelNests( - ifcopenshell.guid.new(), self.owner_history, None, None, - self.ifc_parser.products[relating_object]['ifc'], - [o['ifc'] for o in related_objects]) + ifcopenshell.guid.new(), + self.owner_history, + None, + None, + self.ifc_parser.products[relating_object]["ifc"], + [o["ifc"] for o in related_objects], + ) def relate_objects_to_types(self): for relating_type, related_objects in self.ifc_parser.rel_defines_by_type.items(): self.file.createIfcRelDefinesByType( - ifcopenshell.guid.new(), self.owner_history, None, None, - [self.ifc_parser.products[o]['ifc'] for o in related_objects], - self.ifc_parser.type_products[relating_type]['ifc']) + ifcopenshell.guid.new(), + self.owner_history, + None, + None, + [self.ifc_parser.products[o]["ifc"] for o in related_objects], + self.ifc_parser.type_products[relating_type]["ifc"], + ) def relate_objects_to_qtos(self): for relating_property_key, related_objects in self.ifc_parser.rel_defines_by_qto.items(): self.file.createIfcRelDefinesByProperties( - ifcopenshell.guid.new(), self.owner_history, None, None, - [o['ifc'] for o in related_objects], - self.ifc_parser.qtos[relating_property_key]['ifc']) + ifcopenshell.guid.new(), + self.owner_history, + None, + None, + [o["ifc"] for o in related_objects], + self.ifc_parser.qtos[relating_property_key]["ifc"], + ) def relate_objects_to_psets(self): for relating_property_key, related_objects in self.ifc_parser.rel_defines_by_pset.items(): - if self.ifc_parser.psets[relating_property_key]['ifc']: + if self.ifc_parser.psets[relating_property_key]["ifc"]: self.file.createIfcRelDefinesByProperties( - ifcopenshell.guid.new(), self.owner_history, None, None, - [o['ifc'] for o in related_objects], - self.ifc_parser.psets[relating_property_key]['ifc']) + ifcopenshell.guid.new(), + self.owner_history, + None, + None, + [o["ifc"] for o in related_objects], + self.ifc_parser.psets[relating_property_key]["ifc"], + ) def relate_objects_to_materials(self): if not self.ifc_export_settings.has_representations: return for relating_material_key, related_objects in self.ifc_parser.rel_associates_material.items(): self.file.createIfcRelAssociatesMaterial( - ifcopenshell.guid.new(), self.owner_history, None, None, - [o['ifc'] for o in related_objects], - self.ifc_parser.materials[relating_material_key]['ifc']) + ifcopenshell.guid.new(), + self.owner_history, + None, + None, + [o["ifc"] for o in related_objects], + self.ifc_parser.materials[relating_material_key]["ifc"], + ) def relate_objects_to_material_sets(self, set_type): if not self.ifc_export_settings.has_representations: return - for product_index, related_materials in getattr(self.ifc_parser, f'rel_associates_material_{set_type}_set').items(): - material_set = self.file.create_entity(f'IfcMaterial{set_type.capitalize()}Set', **{ - f'Material{set_type.capitalize()}s': [self.ifc_parser.materials[m]['part_ifc'] for m in related_materials] - }) + for product_index, related_materials in getattr( + self.ifc_parser, f"rel_associates_material_{set_type}_set" + ).items(): + material_set = self.file.create_entity( + f"IfcMaterial{set_type.capitalize()}Set", + **{ + f"Material{set_type.capitalize()}s": [ + self.ifc_parser.materials[m]["part_ifc"] for m in related_materials + ] + }, + ) self.file.createIfcRelAssociatesMaterial( - ifcopenshell.guid.new(), self.owner_history, None, None, - [self.ifc_parser.products[product_index]['ifc']], - material_set) + ifcopenshell.guid.new(), + self.owner_history, + None, + None, + [self.ifc_parser.products[product_index]["ifc"]], + material_set, + ) def relate_spaces_to_boundary_elements(self): - for relating_space_index, relationships, in self.ifc_parser.rel_space_boundaries.items(): + for ( + relating_space_index, + relationships, + ) in self.ifc_parser.rel_space_boundaries.items(): for relationship in relationships: - relationship['attributes']['GlobalId'] = ifcopenshell.guid.new() - relationship['attributes']['RelatedBuildingElement'] = self.ifc_parser.products[ - self.ifc_parser.get_product_index_from_raw_name( - relationship['related_building_element_raw_name'])]['ifc'] - relationship['attributes']['RelatingSpace'] = self.ifc_parser.products[relating_space_index]['ifc'] - relationship['attributes']['ConnectionGeometry'] = self.create_connection_geometry( - self.ifc_parser.products[relating_space_index], - relationship['connection_geometry_face_index']) - self.file.create_entity(relationship['class'], **relationship['attributes']) + relationship["attributes"]["GlobalId"] = ifcopenshell.guid.new() + relationship["attributes"]["RelatedBuildingElement"] = self.ifc_parser.products[ + self.ifc_parser.get_product_index_from_raw_name(relationship["related_building_element_raw_name"]) + ]["ifc"] + relationship["attributes"]["RelatingSpace"] = self.ifc_parser.products[relating_space_index]["ifc"] + relationship["attributes"]["ConnectionGeometry"] = self.create_connection_geometry( + self.ifc_parser.products[relating_space_index], relationship["connection_geometry_face_index"] + ) + self.file.create_entity(relationship["class"], **relationship["attributes"]) def create_connection_geometry(self, product, face_index): - mesh = product['raw'].data + mesh = product["raw"].data polygon = mesh.polygons[int(face_index)] vertex_on_polygon = mesh.vertices[polygon.vertices[0]].co center = polygon.center normal = polygon.normal forward = center - vertex_on_polygon - return self.file.createIfcFaceSurface([self.file.createIfcFaceOuterBound( - self.file.createIfcPolyLoop([ - self.create_cartesian_point( - mesh.vertices[vertice].co.x, - mesh.vertices[vertice].co.y, - mesh.vertices[vertice].co.z) - for vertice in polygon.vertices]), - True)], - self.file.createIfcPlane(self.file.createIfcAxis2Placement3D( - self.create_cartesian_point(center.x, center.y, center.z), - self.file.createIfcDirection((normal.x, normal.y, normal.z)), - self.file.createIfcDirection((forward.x, forward.y, forward.z)))), - True) + return self.file.createIfcFaceSurface( + [ + self.file.createIfcFaceOuterBound( + self.file.createIfcPolyLoop( + [ + self.create_cartesian_point( + mesh.vertices[vertice].co.x, mesh.vertices[vertice].co.y, mesh.vertices[vertice].co.z + ) + for vertice in polygon.vertices + ] + ), + True, + ) + ], + self.file.createIfcPlane( + self.file.createIfcAxis2Placement3D( + self.create_cartesian_point(center.x, center.y, center.z), + self.file.createIfcDirection((normal.x, normal.y, normal.z)), + self.file.createIfcDirection((forward.x, forward.y, forward.z)), + ) + ), + True, + ) def relate_to_documents(self, relationships): for relating_document_key, related_objects in relationships.items(): self.file.createIfcRelAssociatesDocument( - ifcopenshell.guid.new(), self.owner_history, None, None, - [o['ifc'] for o in related_objects], - self.ifc_parser.document_references[relating_document_key]['ifc']) + ifcopenshell.guid.new(), + self.owner_history, + None, + None, + [o["ifc"] for o in related_objects], + self.ifc_parser.document_references[relating_document_key]["ifc"], + ) def relate_to_classifications(self, relationships): for relating_key, related_objects in relationships.items(): self.file.createIfcRelAssociatesClassification( - ifcopenshell.guid.new(), self.owner_history, None, None, - [o['ifc'] for o in related_objects], - self.ifc_parser.classification_references[relating_key]['ifc']) + ifcopenshell.guid.new(), + self.owner_history, + None, + None, + [o["ifc"] for o in related_objects], + self.ifc_parser.classification_references[relating_key]["ifc"], + ) def relate_to_constraints(self, relationships): for relating_key, related_objects in relationships.items(): self.file.createIfcRelAssociatesConstraint( - ifcopenshell.guid.new(), self.owner_history, None, None, - [o['ifc'] for o in related_objects], None, - self.ifc_parser.constraints[relating_key]['ifc']) + ifcopenshell.guid.new(), + self.owner_history, + None, + None, + [o["ifc"] for o in related_objects], + None, + self.ifc_parser.constraints[relating_key]["ifc"], + ) def relate_structural_members_to_connections(self): for relating_member, relating_connection in self.ifc_parser.rel_connects_structural_member.items(): - self.file.create_entity('IfcRelConnectsStructuralMember', **{ - 'RelatingStructuralMember': self.ifc_parser.products[relating_member]['ifc'], - 'RelatedStructuralConnection': self.ifc_parser.products[relating_connection]['ifc'] - }) + self.file.create_entity( + "IfcRelConnectsStructuralMember", + **{ + "RelatingStructuralMember": self.ifc_parser.products[relating_member]["ifc"], + "RelatedStructuralConnection": self.ifc_parser.products[relating_connection]["ifc"], + }, + ) def relate_objects_to_groups(self): for relating_group, related_objects in self.ifc_parser.rel_assigns_to_group.items(): self.file.createIfcRelAssignsToGroup( - ifcopenshell.guid.new(), self.owner_history, None, None, - [self.ifc_parser.products[o]['ifc'] for o in related_objects], None, - self.ifc_parser.groups[relating_group]['ifc']) + ifcopenshell.guid.new(), + self.owner_history, + None, + None, + [self.ifc_parser.products[o]["ifc"] for o in related_objects], + None, + self.ifc_parser.groups[relating_group]["ifc"], + ) def convert_si_to_unit(self, co): return co / self.ifc_parser.unit_scale @@ -3083,30 +3266,30 @@ class IfcExporter(): return co * self.ifc_parser.unit_scale def write_ifc_file(self): - extension = self.ifc_export_settings.output_file.split('.')[-1] - if extension == 'ifczip': + extension = self.ifc_export_settings.output_file.split(".")[-1] + if extension == "ifczip": with tempfile.TemporaryDirectory() as unzipped_path: filename, ext = os.path.splitext(os.path.basename(self.ifc_export_settings.output_file)) - tmp_name = '{}.ifc'.format(filename) + tmp_name = "{}.ifc".format(filename) tmp_file = os.path.join(unzipped_path, tmp_name) self.file.write(tmp_file) - with zipfile.ZipFile(self.ifc_export_settings.output_file, - mode='w', compression=zipfile.ZIP_DEFLATED, compresslevel=9) as zf: + with zipfile.ZipFile( + self.ifc_export_settings.output_file, mode="w", compression=zipfile.ZIP_DEFLATED, compresslevel=9 + ) as zf: zf.write(tmp_file) - elif extension == 'ifc': + elif extension == "ifc": self.file.write(self.ifc_export_settings.output_file) - elif extension == 'ifcjson': + elif extension == "ifcjson": import ifcjson - if self.ifc_export_settings.json_version == '4': + + if self.ifc_export_settings.json_version == "4": jsonData = ifcjson.IFC2JSON4(self.file, self.ifc_export_settings.json_compact).spf2Json() - with open(self.ifc_export_settings.output_file, 'w') as outfile: - json.dump(jsonData, outfile, - indent=None if self.ifc_export_settings.json_compact else 4) - elif self.ifc_export_settings.json_version == '5a': + with open(self.ifc_export_settings.output_file, "w") as outfile: + json.dump(jsonData, outfile, indent=None if self.ifc_export_settings.json_compact else 4) + elif self.ifc_export_settings.json_version == "5a": jsonData = ifcjson.IFC2JSON5a(self.file, self.ifc_export_settings.json_compact).spf2Json() - with open(self.ifc_export_settings.output_file, 'w') as outfile: - json.dump(jsonData, outfile, - indent=None if self.ifc_export_settings.json_compact else 4) + with open(self.ifc_export_settings.output_file, "w") as outfile: + json.dump(jsonData, outfile, indent=None if self.ifc_export_settings.json_compact else 4) class IfcExportSettings: @@ -3117,11 +3300,31 @@ class IfcExportSettings: self.output_file = None self.has_representations = True self.has_quantities = True - self.contexts = ['Model', 'Plan'] - self.subcontexts = ['Annotation', 'Axis', 'Box', 'FootPrint', 'Reference', 'Body', 'Clearance', 'CoG', 'Profile', 'SurveyPoints'] - self.schema_version = 'IFC4' - self.target_views = ['GRAPH_VIEW', 'SKETCH_VIEW', 'MODEL_VIEW', 'PLAN_VIEW', 'REFLECTED_PLAN_VIEW', - 'SECTION_VIEW', 'ELEVATION_VIEW', 'USERDEFINED', 'NOTDEFINED'] + self.contexts = ["Model", "Plan"] + self.subcontexts = [ + "Annotation", + "Axis", + "Box", + "FootPrint", + "Reference", + "Body", + "Clearance", + "CoG", + "Profile", + "SurveyPoints", + ] + self.schema_version = "IFC4" + self.target_views = [ + "GRAPH_VIEW", + "SKETCH_VIEW", + "MODEL_VIEW", + "PLAN_VIEW", + "REFLECTED_PLAN_VIEW", + "SECTION_VIEW", + "ELEVATION_VIEW", + "USERDEFINED", + "NOTDEFINED", + ] self.should_use_presentation_style_assignment = False self.should_guess_quantities = False self.context_tree = [] @@ -3143,16 +3346,15 @@ class IfcExportSettings: settings.should_force_faceted_brep = scene_bim.export_should_force_faceted_brep settings.should_roundtrip_native = scene_bim.import_export_should_roundtrip_native settings.context_tree = [] - for ifc_context in ['model', 'plan']: - if getattr(scene_bim, 'has_{}_context'.format(ifc_context)): + for ifc_context in ["model", "plan"]: + if getattr(scene_bim, "has_{}_context".format(ifc_context)): subcontexts = {} - for subcontext in getattr(scene_bim, '{}_subcontexts'.format(ifc_context)): + for subcontext in getattr(scene_bim, "{}_subcontexts".format(ifc_context)): subcontexts.setdefault(subcontext.name, []).append(subcontext.target_view) - settings.context_tree.append({ - 'name': ifc_context.title(), - 'subcontexts': [ - {'name': key, 'target_views': value} - for key, value in subcontexts.items() - ] - }) + settings.context_tree.append( + { + "name": ifc_context.title(), + "subcontexts": [{"name": key, "target_views": value} for key, value in subcontexts.items()], + } + ) return settings diff --git a/src/ifcblenderexport/blenderbim/bim/helper.py b/src/ifcblenderexport/blenderbim/bim/helper.py index 3e7f142195..3f57bca5ad 100644 --- a/src/ifcblenderexport/blenderbim/bim/helper.py +++ b/src/ifcblenderexport/blenderbim/bim/helper.py @@ -5,7 +5,7 @@ import bpy def get_representation_elements(ifc_file, step_id): results = [] for child in ifc_file.traverse(ifc_file.by_id(step_id)): - if hasattr(child, 'StyledByItem') and child.StyledByItem: + if hasattr(child, "StyledByItem") and child.StyledByItem: for styled_by_item in child.StyledByItem: for style in styled_by_item.Styles: for style_child in ifc_file.traverse(style): @@ -16,49 +16,92 @@ def get_representation_elements(ifc_file, step_id): # TODO: Deprecate this in favour of ifcopenshell.util.unit + class SIUnitHelper: - prefixes = {"EXA": 1e18, "PETA": 1e15, "TERA": 1e12, "GIGA": 1e9, "MEGA": - 1e6, "KILO": 1e3, "HECTO": 1e2, "DECA": 1e1, "DECI": 1e-1, "CENTI": - 1e-2, "MILLI": 1e-3, "MICRO": 1e-6, "NANO": 1e-9, "PICO": 1e-12, - "FEMTO": 1e-15, "ATTO": 1e-18} - unit_names = ["AMPERE", "BECQUEREL", "CANDELA", "COULOMB", - "CUBIC_METRE", "DEGREE CELSIUS", "FARAD", "GRAM", "GRAY", "HENRY", - "HERTZ", "JOULE", "KELVIN", "LUMEN", "LUX", "MOLE", "NEWTON", "OHM", - "PASCAL", "RADIAN", "SECOND", "SIEMENS", "SIEVERT", "SQUARE METRE", - "METRE", "STERADIAN", "TESLA", "VOLT", "WATT", "WEBER"] + prefixes = { + "EXA": 1e18, + "PETA": 1e15, + "TERA": 1e12, + "GIGA": 1e9, + "MEGA": 1e6, + "KILO": 1e3, + "HECTO": 1e2, + "DECA": 1e1, + "DECI": 1e-1, + "CENTI": 1e-2, + "MILLI": 1e-3, + "MICRO": 1e-6, + "NANO": 1e-9, + "PICO": 1e-12, + "FEMTO": 1e-15, + "ATTO": 1e-18, + } + unit_names = [ + "AMPERE", + "BECQUEREL", + "CANDELA", + "COULOMB", + "CUBIC_METRE", + "DEGREE CELSIUS", + "FARAD", + "GRAM", + "GRAY", + "HENRY", + "HERTZ", + "JOULE", + "KELVIN", + "LUMEN", + "LUX", + "MOLE", + "NEWTON", + "OHM", + "PASCAL", + "RADIAN", + "SECOND", + "SIEMENS", + "SIEVERT", + "SQUARE METRE", + "METRE", + "STERADIAN", + "TESLA", + "VOLT", + "WATT", + "WEBER", + ] si_conversions = { - 'inch': 0.0254, - 'foot': 0.3048, - 'yard': 0.914, - 'mile': 1609, - 'square inch': 0.0006452, - 'square foot': 0.09290304, - 'square yard': 0.83612736, - 'acre': 4046.86, - 'square mile': 2588881, - 'cubic inch': 0.00001639, - 'cubic foot': 0.02831684671168849, - 'cubic yard': 0.7636, - 'litre': 0.001, - 'fluid ounce UK': 0.0000284130625, - 'fluid ounce US': 0.00002957353, - 'pint UK': 0.000568, - 'pint US': 0.000473, - 'gallon UK': 0.004546, - 'gallon US': 0.003785, - 'degree': math.pi/180, - 'ounce': 0.02835, - 'pound': 0.454, - 'ton UK': 1016.0469088, - 'ton US': 907.18474, - 'lbf': 4.4482216153, - 'kip': 4448.2216153, - 'psi': 6894.7572932, - 'ksi': 6894757.2932, - 'minute': 60, - 'hour': 3600, - 'day': 86400, - 'btu': 1055.056} + "inch": 0.0254, + "foot": 0.3048, + "yard": 0.914, + "mile": 1609, + "square inch": 0.0006452, + "square foot": 0.09290304, + "square yard": 0.83612736, + "acre": 4046.86, + "square mile": 2588881, + "cubic inch": 0.00001639, + "cubic foot": 0.02831684671168849, + "cubic yard": 0.7636, + "litre": 0.001, + "fluid ounce UK": 0.0000284130625, + "fluid ounce US": 0.00002957353, + "pint UK": 0.000568, + "pint US": 0.000473, + "gallon UK": 0.004546, + "gallon US": 0.003785, + "degree": math.pi / 180, + "ounce": 0.02835, + "pound": 0.454, + "ton UK": 1016.0469088, + "ton US": 907.18474, + "lbf": 4.4482216153, + "kip": 4448.2216153, + "psi": 6894.7572932, + "ksi": 6894757.2932, + "minute": 60, + "hour": 3600, + "day": 86400, + "btu": 1055.056, + } @staticmethod def get_prefix(text): @@ -78,7 +121,7 @@ class SIUnitHelper: @staticmethod def get_unit_name(text): for name in SIUnitHelper.unit_names: - if name in text.upper().replace('METER', 'METRE'): + if name in text.upper().replace("METER", "METRE"): return name @staticmethod @@ -100,20 +143,20 @@ class SIUnitHelper: value *= SIUnitHelper.si_conversions[from_unit] elif from_prefix: value *= SIUnitHelper.get_prefix_multiplier(from_prefix) - if 'SQUARE' in from_unit: + if "SQUARE" in from_unit: value *= SIUnitHelper.get_prefix_multiplier(from_prefix) - elif 'CUBIC' in from_unit: + elif "CUBIC" in from_unit: value *= SIUnitHelper.get_prefix_multiplier(from_prefix) value *= SIUnitHelper.get_prefix_multiplier(from_prefix) if to_unit in SIUnitHelper.si_conversions: return value * (1 / SIUnitHelper.si_conversions[to_unit]) elif to_prefix: - value *= (1 / SIUnitHelper.get_prefix_multiplier(to_prefix)) - if 'SQUARE' in from_unit: - value *= (1 / SIUnitHelper.get_prefix_multiplier(to_prefix)) - elif 'CUBIC' in from_unit: - value *= (1 / SIUnitHelper.get_prefix_multiplier(to_prefix)) - value *= (1 / SIUnitHelper.get_prefix_multiplier(to_prefix)) + value *= 1 / SIUnitHelper.get_prefix_multiplier(to_prefix) + if "SQUARE" in from_unit: + value *= 1 / SIUnitHelper.get_prefix_multiplier(to_prefix) + elif "CUBIC" in from_unit: + value *= 1 / SIUnitHelper.get_prefix_multiplier(to_prefix) + value *= 1 / SIUnitHelper.get_prefix_multiplier(to_prefix) return value @@ -141,39 +184,38 @@ def format_distance(value, isArea=False, hide_units=True): # Imperial Formating if unit_system == "IMPERIAL": precision = bpy.context.scene.BIMProperties.imperial_precision - if precision == 'NONE': + if precision == "NONE": precision = 256 - elif precision == '1': + elif precision == "1": precision = 1 - elif '/' in precision: - precision = int(precision.split('/')[1]) + elif "/" in precision: + precision = int(precision.split("/")[1]) base = int(precision) decInches = value * toInches # Seperate ft and inches # Unless Inches are the specified Length Unit - if unit_length != 'INCHES': - feet = math.floor(decInches/inPerFoot) - decInches -= feet*inPerFoot + if unit_length != "INCHES": + feet = math.floor(decInches / inPerFoot) + decInches -= feet * inPerFoot else: feet = 0 - - #Seperate Fractional Inches + # Seperate Fractional Inches inches = math.floor(decInches) if inches != 0: - frac = round(base*(decInches-inches)) + frac = round(base * (decInches - inches)) else: - frac = round(base*(decInches)) + frac = round(base * (decInches)) - #Set proper numerator and denominator + # Set proper numerator and denominator if frac != base: numcycles = int(math.log2(base)) for i in range(numcycles): - if frac%2 == 0: - frac = int(frac/2) - base = int(base/2) + if frac % 2 == 0: + frac = int(frac / 2) + base = int(base / 2) else: break else: @@ -185,48 +227,52 @@ def format_distance(value, isArea=False, hide_units=True): feet += 1 inches = 0 - if inches !=0: + if inches != 0: inchesString = str(inches) - if frac != 0: inchesString += "-" - else: inchesString += "\"" - else: inchesString = "" + if frac != 0: + inchesString += "-" + else: + inchesString += '"' + else: + inchesString = "" if feet != 0: feetString = str(feet) + "' " - else: feetString = "" + else: + feetString = "" if frac != 0: - fracString = str(frac) + "/" + str(base) +"\"" - else: fracString = "" + fracString = str(frac) + "/" + str(base) + '"' + else: + fracString = "" if not isArea: tx_dist = feetString + inchesString + fracString else: - tx_dist = str('%1.3f' % (value*toInches/inPerFoot)) + " sq. ft." - + tx_dist = str("%1.3f" % (value * toInches / inPerFoot)) + " sq. ft." # METRIC FORMATING elif unit_system == "METRIC": precision = bpy.context.scene.BIMProperties.metric_precision if precision != 0: - value = precision * round(float(value)/precision) + value = precision * round(float(value) / precision) # Meters - if unit_length == 'METERS': - fmt = '%1.3f' + if unit_length == "METERS": + fmt = "%1.3f" if hide_units is False: fmt += " m" tx_dist = fmt % value # Centimeters - elif unit_length == 'CENTIMETERS': - fmt = '%1.1f' + elif unit_length == "CENTIMETERS": + fmt = "%1.1f" if hide_units is False: fmt += " cm" d_cm = value * (100) tx_dist = fmt % d_cm - #Millimeters - elif unit_length == 'MILLIMETERS': - fmt = '%1.0f' + # Millimeters + elif unit_length == "MILLIMETERS": + fmt = "%1.0f" if hide_units is False: fmt += " mm" d_mm = value * (1000) @@ -235,19 +281,19 @@ def format_distance(value, isArea=False, hide_units=True): # Otherwise Use Adaptive Units else: if round(value, 2) >= 1.0: - fmt = '%1.3f' + fmt = "%1.3f" if hide_units is False: fmt += " m" tx_dist = fmt % value else: if round(value, 2) >= 0.01: - fmt = '%1.1f' + fmt = "%1.1f" if hide_units is False: fmt += " cm" d_cm = value * (100) tx_dist = fmt % d_cm else: - fmt = '%1.0f' + fmt = "%1.0f" if hide_units is False: fmt += " mm" d_mm = value * (1000) @@ -257,5 +303,4 @@ def format_distance(value, isArea=False, hide_units=True): else: tx_dist = fmt % value - return tx_dist diff --git a/src/ifcblenderexport/blenderbim/bim/ifc.py b/src/ifcblenderexport/blenderbim/bim/ifc.py index a4eeadf5f6..147a205c15 100644 --- a/src/ifcblenderexport/blenderbim/bim/ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/ifc.py @@ -1,10 +1,11 @@ import bpy import ifcopenshell -class IfcStore(): - path = '' + +class IfcStore: + path = "" file = None - pset_template_path = '' + pset_template_path = "" pset_template_file = None @staticmethod diff --git a/src/ifcblenderexport/blenderbim/bim/import_ifc.py b/src/ifcblenderexport/blenderbim/bim/import_ifc.py index 0eaf17c046..e7a295ff98 100644 --- a/src/ifcblenderexport/blenderbim/bim/import_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/import_ifc.py @@ -23,6 +23,7 @@ from . import helper from . import schema from . import ifc + class FileCopy(threading.Thread): def __init__(self, file_path, destination): threading.Thread.__init__(self) @@ -32,7 +33,8 @@ class FileCopy(threading.Thread): def run(self): shutil.copy(self.file_path, self.destination) -class MaterialCreator(): + +class MaterialCreator: def __init__(self, ifc_import_settings): self.mesh = None self.materials = {} @@ -44,11 +46,11 @@ class MaterialCreator(): self.current_object_materials = [] self.obj = obj self.mesh = mesh - if (hasattr(element, 'Representation') and not element.Representation) \ - or (hasattr(element, 'RepresentationMaps') and not element.RepresentationMaps): + if (hasattr(element, "Representation") and not element.Representation) or ( + hasattr(element, "RepresentationMaps") and not element.RepresentationMaps + ): return - if self.ifc_import_settings.should_treat_styled_item_as_material \ - and self.mesh.name in self.parsed_meshes: + if self.ifc_import_settings.should_treat_styled_item_as_material and self.mesh.name in self.parsed_meshes: return self.parse_material(element) self.parsed_meshes.append(self.mesh.name) @@ -58,11 +60,11 @@ class MaterialCreator(): def parse_representations(self, element): has_parsed = False - if hasattr(element, 'Representation'): + if hasattr(element, "Representation"): for representation in element.Representation.Representations: if self.parse_representation(representation): has_parsed = True - elif hasattr(element, 'RepresentationMaps'): + elif hasattr(element, "RepresentationMaps"): for representation_map in element.RepresentationMaps: if self.parse_representation(representation_map.MappedRepresentation): has_parsed = True @@ -105,18 +107,18 @@ class MaterialCreator(): return True def assign_material_slots_to_faces(self, obj, mesh): - if 'ios_materials' not in mesh or not mesh['ios_materials']: + if "ios_materials" not in mesh or not mesh["ios_materials"]: return if len(obj.material_slots) == 1: return material_to_slot = {} - for i, material in enumerate(mesh['ios_materials']): - if material == 'NULLMAT': + for i, material in enumerate(mesh["ios_materials"]): + if material == "NULLMAT": continue - elif 'surface-style-' in material: - material = material.split('-')[2] - if len(bytes(material, 'utf-8')) > 63: # Blender material names are up to 63 UTF-8 bytes - material = bytes(material, 'utf-8')[0:63].decode('utf-8') + elif "surface-style-" in material: + material = material.split("-")[2] + if len(bytes(material, "utf-8")) > 63: # Blender material names are up to 63 UTF-8 bytes + material = bytes(material, "utf-8")[0:63].decode("utf-8") slot_index = obj.material_slots.find(material) if slot_index == -1: @@ -128,21 +130,20 @@ class MaterialCreator(): slot_index = [self.canonicalise_material_name(s.name) for s in obj.material_slots].index(material) material_to_slot[i] = slot_index - if len(mesh.polygons) == len(mesh['ios_material_ids']): - material_index = [(material_to_slot[mat_id] if mat_id != -1 - else 0) for mat_id in mesh['ios_material_ids']] - mesh.polygons.foreach_set('material_index', material_index) + if len(mesh.polygons) == len(mesh["ios_material_ids"]): + material_index = [(material_to_slot[mat_id] if mat_id != -1 else 0) for mat_id in mesh["ios_material_ids"]] + mesh.polygons.foreach_set("material_index", material_index) def canonicalise_material_name(self, name): - return re.sub(r'\.[0-9]{3}$', '', name) + return re.sub(r"\.[0-9]{3}$", "", name) def parse_material(self, element): for association in element.HasAssociations: - if association.is_a('IfcRelAssociatesMaterial'): + if association.is_a("IfcRelAssociatesMaterial"): material_select = association.RelatingMaterial - if material_select.is_a('IfcMaterialDefinition'): + if material_select.is_a("IfcMaterialDefinition"): self.create_definition(material_select) - elif material_select.is_a('IfcMaterialLayerSetUsage'): + elif material_select.is_a("IfcMaterialLayerSetUsage"): self.create_layer_set_usage(material_select) def create_layer_set_usage(self, usage): @@ -150,13 +151,13 @@ class MaterialCreator(): self.create_definition(usage.ForLayerSet) def create_definition(self, material): - if material.is_a('IfcMaterial'): + if material.is_a("IfcMaterial"): self.create_single(material) - elif material.is_a('IfcMaterialLayerSet'): + elif material.is_a("IfcMaterialLayerSet"): self.create_layer_set(material) - elif material.is_a('IfcMaterialConstituentSet'): + elif material.is_a("IfcMaterialConstituentSet"): self.create_constituent_set(material) - elif material.is_a('IfcMaterialList'): + elif material.is_a("IfcMaterialList"): self.create_material_list(material) def create_single(self, material): @@ -192,14 +193,13 @@ class MaterialCreator(): self.materials[material.Name] = obj = bpy.data.materials.new(material.Name) for pset in getattr(material, "HasProperties", ()): self.add_pset(pset, obj) - if not material.HasRepresentation \ - or not material.HasRepresentation[0].Representations: + if not material.HasRepresentation or not material.HasRepresentation[0].Representations: return for representation in material.HasRepresentation[0].Representations: if not representation.Items: continue for item in representation.Items: - if not item.is_a('IfcStyledItem'): + if not item.is_a("IfcStyledItem"): continue self.parse_styled_item(item, obj) @@ -207,11 +207,11 @@ class MaterialCreator(): new_pset = obj.BIMMaterialProperties.psets.add() new_pset.name = pset.Name if new_pset.name in schema.ifc.psets: - for prop_name in schema.ifc.psets[new_pset.name]['HasPropertyTemplates'].keys(): + for prop_name in schema.ifc.psets[new_pset.name]["HasPropertyTemplates"].keys(): prop = new_pset.properties.add() prop.name = prop_name for prop in pset.Properties: - if prop.is_a('IfcPropertySingleValue') and prop.NominalValue: + if prop.is_a("IfcPropertySingleValue") and prop.NominalValue: index = new_pset.properties.find(prop.Name) if index >= 0: new_pset.properties[index].string_value = str(prop.NominalValue.wrappedValue) @@ -225,7 +225,7 @@ class MaterialCreator(): return styled_item.Name styles = self.get_styled_item_styles(styled_item) for style in styles: - if not style.is_a('IfcSurfaceStyle'): + if not style.is_a("IfcSurfaceStyle"): continue if style.Name: return style.Name @@ -235,22 +235,22 @@ class MaterialCreator(): def parse_styled_item(self, styled_item, material): styles = self.get_styled_item_styles(styled_item) for style in styles: - if not style.is_a('IfcSurfaceStyle'): + if not style.is_a("IfcSurfaceStyle"): continue external_style = None for surface_style in style.Styles: - if surface_style.is_a('IfcSurfaceStyleShading'): - alpha = 1. + if surface_style.is_a("IfcSurfaceStyleShading"): + alpha = 1.0 # Transparency was added in IFC4 - if hasattr(surface_style, 'Transparency') \ - and surface_style.Transparency: + if hasattr(surface_style, "Transparency") and surface_style.Transparency: alpha = 1 - surface_style.Transparency material.diffuse_color = ( surface_style.SurfaceColour.Red, surface_style.SurfaceColour.Green, surface_style.SurfaceColour.Blue, - alpha) - elif surface_style.is_a('IfcExternallyDefinedSurfaceStyle'): + alpha, + ) + elif surface_style.is_a("IfcExternallyDefinedSurfaceStyle"): external_style = surface_style if external_style: material.BIMMaterialProperties.is_external = True @@ -263,7 +263,7 @@ class MaterialCreator(): def get_styled_item_styles(self, styled_item): styles = [] for style in styled_item.Styles: - if style.is_a('IfcPresentationStyleAssignment'): + if style.is_a("IfcPresentationStyleAssignment"): styles.extend(self.get_styled_item_styles(style)) else: styles.append(style) @@ -272,7 +272,7 @@ class MaterialCreator(): def resolve_mapped_representation_items(self, representation): items = [] for item in representation.Items: - if item.is_a('IfcMappedItem'): + if item.is_a("IfcMappedItem"): items.extend(item.MappingSource.MappedRepresentation.Items) else: items.append(item) @@ -283,10 +283,11 @@ class MaterialCreator(): self.current_object_materials.append(material.name) if is_styled_item: index = len(self.obj.material_slots) - 1 - self.obj.material_slots[index].link = 'OBJECT' + self.obj.material_slots[index].link = "OBJECT" self.obj.material_slots[index].material = material -class IfcImporter(): + +class IfcImporter: def __init__(self, ifc_import_settings): self.ifc_import_settings = ifc_import_settings self.diff = None @@ -333,139 +334,143 @@ class IfcImporter(): return if not self.time: self.time = time.time() - print('{} :: {:.2f}'.format(message, time.time() - self.time)) + print("{} :: {:.2f}".format(message, time.time() - self.time)) self.time = time.time() def execute(self): - self.profile_code('Starting import process') + self.profile_code("Starting import process") self.load_diff() - self.profile_code('Load diff') + self.profile_code("Load diff") self.purge_diff() - self.profile_code('Purge diffs') + self.profile_code("Purge diffs") self.load_existing_rooted_elements() - self.profile_code('Load existing rooted elements') + self.profile_code("Load existing rooted elements") self.cache_file() - self.profile_code('Caching file') + self.profile_code("Caching file") self.load_file() - self.profile_code('Loading file') + self.profile_code("Loading file") self.set_ifc_file() - self.profile_code('Setting file') + self.profile_code("Setting file") if self.ifc_import_settings.should_auto_set_workarounds: self.auto_set_workarounds() - self.profile_code('Set vendor worksarounds') + self.profile_code("Set vendor worksarounds") self.calculate_unit_scale() - self.profile_code('Calculate unit scale') + self.profile_code("Calculate unit scale") self.patch_ifc() - self.profile_code('Patching ifc') + self.profile_code("Patching ifc") self.set_units() - self.profile_code('Set units') + self.profile_code("Set units") self.create_geometric_representation_contexts() - self.profile_code('Create contexts') + self.profile_code("Create contexts") self.create_project() - self.profile_code('Create project') + self.profile_code("Create project") self.create_classifications() - self.profile_code('Create classifications') + self.profile_code("Create classifications") self.create_constraints() - self.profile_code('Create constraints') + self.profile_code("Create constraints") self.create_document_information() - self.profile_code('Create doc info') + self.profile_code("Create doc info") self.create_document_references() - self.profile_code('Create doc refs') + self.profile_code("Create doc refs") self.create_spatial_hierarchy() - self.profile_code('Create spatial hierarchy') + self.profile_code("Create spatial hierarchy") self.create_type_products() - self.profile_code('Create type products') + self.profile_code("Create type products") if self.ifc_import_settings.should_import_aggregates: self.create_aggregates() - self.profile_code('Create aggregates') + self.profile_code("Create aggregates") self.create_openings_collection() - self.profile_code('Create opening collection') + self.profile_code("Create opening collection") self.process_element_filter() - self.profile_code('Process element filter') + self.profile_code("Process element filter") if self.ifc_import_settings.should_import_native: self.parse_native_elements() - self.profile_code('Parsing native elements') + self.profile_code("Parsing native elements") self.create_georeferencing() - self.profile_code('Georeferencing ifc') + self.profile_code("Georeferencing ifc") self.create_groups() - self.profile_code('Creating groups') + self.profile_code("Creating groups") self.create_grids() - self.profile_code('Creating grids') + self.profile_code("Creating grids") if self.ifc_import_settings.should_import_native: self.create_native_products() - self.profile_code('Creating native products') + self.profile_code("Creating native products") # TODO: Deprecate after bug #682 is fixed and the new importer is stable if self.ifc_import_settings.should_use_legacy: self.create_products_legacy() else: self.create_products() - self.profile_code('Creating meshified products') + self.profile_code("Creating meshified products") self.relate_openings() - self.profile_code('Relating openings') + self.profile_code("Relating openings") self.place_objects_in_spatial_tree() - self.profile_code('Placing objects in spatial tree') + self.profile_code("Placing objects in spatial tree") if self.ifc_import_settings.should_merge_aggregates: self.merge_aggregates() - self.profile_code('Merging aggregates') + self.profile_code("Merging aggregates") if self.ifc_import_settings.should_merge_by_class: self.merge_by_class() - self.profile_code('Merging by class') + self.profile_code("Merging by class") elif self.ifc_import_settings.should_merge_by_material: self.merge_by_material() - self.profile_code('Merging by material') - if self.ifc_import_settings.should_merge_materials_by_colour \ - or (self.ifc_import_settings.should_auto_set_workarounds \ - and len(self.material_creator.materials) > 300): + self.profile_code("Merging by material") + if self.ifc_import_settings.should_merge_materials_by_colour or ( + self.ifc_import_settings.should_auto_set_workarounds and len(self.material_creator.materials) > 300 + ): self.merge_materials_by_colour() - self.profile_code('Merging by colour') + self.profile_code("Merging by colour") self.add_project_to_scene() - self.profile_code('Add project to scene') - if self.ifc_import_settings.should_clean_mesh and len(self.file.by_type('IfcElement')) < 10000: + self.profile_code("Add project to scene") + if self.ifc_import_settings.should_clean_mesh and len(self.file.by_type("IfcElement")) < 10000: self.clean_mesh() - self.profile_code('Mesh cleaning') + self.profile_code("Mesh cleaning") def auto_set_workarounds(self): - if 'DDS-CAD' in self.file.wrapped_data.header.file_name.originating_system \ - or 'DDS' in self.file.wrapped_data.header.file_name.preprocessor_version: + if ( + "DDS-CAD" in self.file.wrapped_data.header.file_name.originating_system + or "DDS" in self.file.wrapped_data.header.file_name.preprocessor_version + ): self.ifc_import_settings.should_treat_styled_item_as_material = True self.ifc_import_settings.should_reset_absolute_coordinates = True - applications = self.file.by_type('IfcApplication') + applications = self.file.by_type("IfcApplication") if not applications: return - if applications[0].ApplicationIdentifier == 'Revit': + if applications[0].ApplicationIdentifier == "Revit": self.ifc_import_settings.should_treat_styled_item_as_material = True - if self.is_ifc_class_far_away('IfcSite'): + if self.is_ifc_class_far_away("IfcSite"): self.ifc_import_settings.should_ignore_site_coordinates = True - if self.is_ifc_class_far_away('IfcBuilding'): + if self.is_ifc_class_far_away("IfcBuilding"): self.ifc_import_settings.should_ignore_building_coordinates = True - elif 'prostructures' in applications[0].ApplicationFullName.lower(): + elif "prostructures" in applications[0].ApplicationFullName.lower(): self.ifc_import_settings.should_allow_non_element_aggregates = True - elif applications[0].ApplicationFullName.lower() == '12d model': + elif applications[0].ApplicationFullName.lower() == "12d model": self.ifc_import_settings.should_reset_absolute_coordinates = True - elif 'Civil 3D' in applications[0].ApplicationFullName: + elif "Civil 3D" in applications[0].ApplicationFullName: self.ifc_import_settings.should_reset_absolute_coordinates = True - elif applications[0].ApplicationFullName == 'Tekla Structures': - if self.is_ifc_class_far_away('IfcSite'): + elif applications[0].ApplicationFullName == "Tekla Structures": + if self.is_ifc_class_far_away("IfcSite"): self.ifc_import_settings.should_ignore_site_coordinates = True def is_ifc_class_far_away(self, ifc_class): for site in self.file.by_type(ifc_class): - if not site.ObjectPlacement \ - or not site.ObjectPlacement.RelativePlacement \ - or not site.ObjectPlacement.RelativePlacement.Location: + if ( + not site.ObjectPlacement + or not site.ObjectPlacement.RelativePlacement + or not site.ObjectPlacement.RelativePlacement.Location + ): continue if self.is_point_far_away(site.ObjectPlacement.RelativePlacement.Location): return True def is_point_far_away(self, point): # Arbitrary threshold based on experience - if hasattr(point, 'Coordinates'): - return abs(point.Coordinates[0]) > 1000000 \ - or abs(point.Coordinates[1]) > 1000000 \ + if hasattr(point, "Coordinates"): + return ( + abs(point.Coordinates[0]) > 1000000 + or abs(point.Coordinates[1]) > 1000000 or abs(point.Coordinates[2]) > 1000000 - return abs(point[0]) > 1000000 \ - or abs(point[1]) > 1000000 \ - or abs(point[2]) > 1000000 + ) + return abs(point[0]) > 1000000 or abs(point[1]) > 1000000 or abs(point[2]) > 1000000 def process_element_filter(self): if not self.ifc_import_settings.ifc_selector: @@ -473,9 +478,9 @@ class IfcImporter(): self.include_elements = [] selector = ifcopenshell.util.selector.Selector() elements = selector.parse(self.file, self.ifc_import_settings.ifc_selector) - if self.ifc_import_settings.ifc_import_filter == 'WHITELIST': + if self.ifc_import_settings.ifc_import_filter == "WHITELIST": self.include_elements = elements - elif self.ifc_import_settings.ifc_import_filter == 'BLACKLIST': + elif self.ifc_import_settings.ifc_import_filter == "BLACKLIST": self.exclude_elements = elements def parse_native_elements(self): @@ -498,26 +503,26 @@ class IfcImporter(): self.native_elements = filtered_native_elements def parse_native_swept_disk_solid(self): - for element in self.file.by_type('IfcSweptDiskSolid'): - if [e for e in self.file.get_inverse(element) if e.is_a('IfcBooleanResult')]: + for element in self.file.by_type("IfcSweptDiskSolid"): + if [e for e in self.file.get_inverse(element) if e.is_a("IfcBooleanResult")]: continue self.swap_out_with_dummy_geometry(element) def parse_native_extruded_area_solid(self): - for element in self.file.by_type('IfcExtrudedAreaSolid'): + for element in self.file.by_type("IfcExtrudedAreaSolid"): if element.SweptArea.is_a() not in [ - 'IfcArbitraryClosedProfileDef', - 'IfcRectangleProfileDef', - 'IfcCircleProfileDef' - ]: + "IfcArbitraryClosedProfileDef", + "IfcRectangleProfileDef", + "IfcCircleProfileDef", + ]: continue - if [e for e in self.file.get_inverse(element) if e.is_a('IfcBooleanResult')]: + if [e for e in self.file.get_inverse(element) if e.is_a("IfcBooleanResult")]: continue self.swap_out_with_dummy_geometry(element) def parse_native_faceted_brep(self): - for element in self.file.by_type('IfcFacetedBrep'): - if [e for e in self.file.get_inverse(element) if e.is_a('IfcBooleanResult')]: + for element in self.file.by_type("IfcFacetedBrep"): + if [e for e in self.file.get_inverse(element) if e.is_a("IfcBooleanResult")]: continue self.swap_out_with_dummy_geometry(element) @@ -525,15 +530,15 @@ class IfcImporter(): dummy_geometry = self.get_dummy_geometry() inverse_elements = self.file.get_inverse(element) for inverse_element in inverse_elements: - if inverse_element.is_a('IfcShapeRepresentation'): - inverse_element.RepresentationType = 'Curve' + if inverse_element.is_a("IfcShapeRepresentation"): + inverse_element.RepresentationType = "Curve" for product in self.get_products_from_shape_representation(inverse_element): self.native_elements.setdefault(product.GlobalId, {})[dummy_geometry.id()] = element ifcopenshell.util.element.replace_attribute(inverse_element, element, dummy_geometry) def get_dummy_geometry(self): - point = self.file.createIfcCartesianPoint((0., 0., 0.)) - direction = self.file.createIfcVector(self.file.createIfcDirection((0., 0., 1.)), 1000.) + point = self.file.createIfcCartesianPoint((0.0, 0.0, 0.0)) + direction = self.file.createIfcVector(self.file.createIfcDirection((0.0, 0.0, 1.0)), 1000.0) return self.file.createIfcLine(point, direction) def get_products_from_shape_representation(self, element): @@ -541,18 +546,18 @@ class IfcImporter(): for rep_map in element.RepresentationMap: for usage in rep_map.MapUsage: for inverse_element in self.file.get_inverse(usage): - if inverse_element.is_a('IfcShapeRepresentation'): + if inverse_element.is_a("IfcShapeRepresentation"): products.extend(self.get_products_from_shape_representation(inverse_element)) return products def patch_ifc(self): - project = self.file.by_type('IfcProject')[0] + project = self.file.by_type("IfcProject")[0] if self.ifc_import_settings.should_ignore_site_coordinates: - sites = self.find_decomposed_ifc_class(project, 'IfcSite') + sites = self.find_decomposed_ifc_class(project, "IfcSite") for site in sites: self.patch_placement_to_origin(site) if self.ifc_import_settings.should_ignore_building_coordinates: - buildings = self.find_decomposed_ifc_class(project, 'IfcBuilding') + buildings = self.find_decomposed_ifc_class(project, "IfcBuilding") for building in buildings: self.patch_placement_to_origin(building) if self.ifc_import_settings.should_reset_absolute_coordinates: @@ -563,7 +568,7 @@ class IfcImporter(): # method will not work all the time, but will catch most issues. offset_point = None try: - point_lists = self.file.by_type('IfcCartesianPointList3D') + point_lists = self.file.by_type("IfcCartesianPointList3D") except: # IFC2X3 does not have IfcCartesianPointList3D point_lists = [] @@ -575,41 +580,42 @@ class IfcImporter(): continue if not offset_point: offset_point = (point[0], point[1], point[2]) - self.ifc_import_settings.logger.info('Resetting absolute coordinates by %s', point) - point = ( - point[0] - offset_point[0], - point[1] - offset_point[1], - point[2] - offset_point[2] - ) + self.ifc_import_settings.logger.info("Resetting absolute coordinates by %s", point) + point = (point[0] - offset_point[0], point[1] - offset_point[1], point[2] - offset_point[2]) coord_list[i] = point point_list.CoordList = coord_list - for point in self.file.by_type('IfcCartesianPoint'): + for point in self.file.by_type("IfcCartesianPoint"): if len(point.Coordinates) == 2 or not self.is_point_far_away(point): continue if not offset_point: offset_point = (point.Coordinates[0], point.Coordinates[1], point.Coordinates[2]) - self.ifc_import_settings.logger.info('Resetting absolute coordinates by %s', point) + self.ifc_import_settings.logger.info("Resetting absolute coordinates by %s", point) point.Coordinates = ( point.Coordinates[0] - offset_point[0], point.Coordinates[1] - offset_point[1], - point.Coordinates[2] - offset_point[2] + point.Coordinates[2] - offset_point[2], ) if not offset_point: return - if self.file.wrapped_data.schema == 'IFC2X3': + if self.file.wrapped_data.schema == "IFC2X3": properties = [ - self.file.createIfcPropertySingleValue('Eastings', None, - self.file.createIfcLengthMeasure(offset_point[0])), - self.file.createIfcPropertySingleValue('Northings', None, - self.file.createIfcLengthMeasure(offset_point[1])), - self.file.createIfcPropertySingleValue('OrthogonalHeight', None, - self.file.createIfcLengthMeasure(offset_point[2])) + self.file.createIfcPropertySingleValue( + "Eastings", None, self.file.createIfcLengthMeasure(offset_point[0]) + ), + self.file.createIfcPropertySingleValue( + "Northings", None, self.file.createIfcLengthMeasure(offset_point[1]) + ), + self.file.createIfcPropertySingleValue( + "OrthogonalHeight", None, self.file.createIfcLengthMeasure(offset_point[2]) + ), ] history = self.file.createIfcOwnerHistory() pset = self.file.createIfcPropertySet( - ifcopenshell.guid.new(), history, 'EPset_MapConversion', None, properties) + ifcopenshell.guid.new(), history, "EPset_MapConversion", None, properties + ) self.file.createIfcRelDefinesByProperties( - ifcopenshell.guid.new(), history, None, None, self.file.by_type('IfcSite'), pset) + ifcopenshell.guid.new(), history, None, None, self.file.by_type("IfcSite"), pset + ) else: # We don't have the full geolocation information, so we'll add what we can scene = bpy.context.scene @@ -630,40 +636,40 @@ class IfcImporter(): return results def patch_placement_to_origin(self, element): - element.ObjectPlacement.RelativePlacement.Location.Coordinates = (0., 0., 0.) + element.ObjectPlacement.RelativePlacement.Location.Coordinates = (0.0, 0.0, 0.0) if element.ObjectPlacement.RelativePlacement.Axis: - element.ObjectPlacement.RelativePlacement.Axis.DirectionRatios = (0., 0., 1.) + element.ObjectPlacement.RelativePlacement.Axis.DirectionRatios = (0.0, 0.0, 1.0) if element.ObjectPlacement.RelativePlacement.RefDirection: - element.ObjectPlacement.RelativePlacement.RefDirection.DirectionRatios = (1., 0., 0.) + element.ObjectPlacement.RelativePlacement.RefDirection.DirectionRatios = (1.0, 0.0, 0.0) def create_georeferencing(self): try: - map_conversion = self.file.by_type('IfcMapConversion') - projected_crs = self.file.by_type('IfcProjectedCRS') + map_conversion = self.file.by_type("IfcMapConversion") + projected_crs = self.file.by_type("IfcProjectedCRS") if not map_conversion or not projected_crs: return except: - return # For example, in IFC2X3 + return # For example, in IFC2X3 map_conversion = map_conversion[0] projected_crs = projected_crs[0] scene = bpy.context.scene scene.BIMProperties.has_georeferencing = True map_conversion_map = { - 'Eastings': 'eastings', - 'Northings': 'northings', - 'OrthogonalHeight': 'orthogonal_height', - 'XAxisAbscissa': 'x_axis_abscissa', - 'XAxisOrdinate': 'x_axis_ordinate', - 'Scale': 'scale' + "Eastings": "eastings", + "Northings": "northings", + "OrthogonalHeight": "orthogonal_height", + "XAxisAbscissa": "x_axis_abscissa", + "XAxisOrdinate": "x_axis_ordinate", + "Scale": "scale", } target_crs_map = { - 'Name': 'name', - 'Description': 'description', - 'GeodeticDatum': 'geodetic_datum', - 'VerticalDatum': 'vertical_datum', - 'MapProjection': 'map_projection', - 'MapZone': 'map_zone', - 'MapUnit': 'map_unit' + "Name": "name", + "Description": "description", + "GeodeticDatum": "geodetic_datum", + "VerticalDatum": "vertical_datum", + "MapProjection": "map_projection", + "MapZone": "map_zone", + "MapUnit": "map_unit", } for keyA, keyB in map_conversion_map.items(): value = getattr(map_conversion, keyA) @@ -672,43 +678,40 @@ class IfcImporter(): for keyA, keyB in target_crs_map.items(): value = getattr(projected_crs, keyA) if value is not None: - if keyA == 'MapUnit': + if keyA == "MapUnit": value = self.get_unit_name(value) setattr(scene.TargetCRS, keyB, str(value)) def get_unit_name(self, named_unit): - name = '' - if hasattr(named_unit, 'Prefix') and named_unit.Prefix: + name = "" + if hasattr(named_unit, "Prefix") and named_unit.Prefix: name += named_unit.Prefix name += named_unit.Name return name def create_groups(self): group_collection = None - for collection in self.project['blender'].children: - if collection.name == 'Groups': + for collection in self.project["blender"].children: + if collection.name == "Groups": group_collection = collection break if group_collection is None: - group_collection = bpy.data.collections.new('Groups') - self.project['blender'].children.link(group_collection) - for element in self.file.by_type('IfcGroup'): + group_collection = bpy.data.collections.new("Groups") + self.project["blender"].children.link(group_collection) + for element in self.file.by_type("IfcGroup"): self.create_group(element, group_collection) def create_group(self, element, group_collection): if element.GlobalId in self.existing_elements: obj = self.existing_elements[element.GlobalId] else: - obj = bpy.data.objects.new(f'{element.is_a()}/{element.Name}', None) + obj = bpy.data.objects.new(f"{element.is_a()}/{element.Name}", None) self.add_element_attributes(element, obj) group_collection.objects.link(obj) - self.groups[element.GlobalId] = { - 'ifc': element, - 'blender': obj - } + self.groups[element.GlobalId] = {"ifc": element, "blender": obj} def create_grids(self): - grids = self.file.by_type('IfcGrid') + grids = self.file.by_type("IfcGrid") for grid in grids: shape = None if grid.Representation: @@ -719,14 +722,14 @@ class IfcImporter(): element_matrix[0][3] *= self.unit_scale element_matrix[1][3] *= self.unit_scale element_matrix[2][3] *= self.unit_scale - u_axes = bpy.data.collections.new('UAxes') + u_axes = bpy.data.collections.new("UAxes") collection.children.link(u_axes) - v_axes = bpy.data.collections.new('VAxes') + v_axes = bpy.data.collections.new("VAxes") collection.children.link(v_axes) self.create_grid_axes(grid.UAxes, u_axes, element_matrix) self.create_grid_axes(grid.VAxes, v_axes, element_matrix) if grid.WAxes: - w_axes = bpy.data.collections.new('WAxes') + w_axes = bpy.data.collections.new("WAxes") collection.children.link(w_axes) self.create_grid_axes(grid.WAxes, w_axes, element_matrix) @@ -734,25 +737,25 @@ class IfcImporter(): for axis in axes: shape = ifcopenshell.geom.create_shape(self.settings_2d, axis.AxisCurve) mesh = self.create_mesh(axis, shape) - obj = bpy.data.objects.new(f'IfcGridAxis/{axis.AxisTag}', mesh) + obj = bpy.data.objects.new(f"IfcGridAxis/{axis.AxisTag}", mesh) obj.matrix_world = matrix_world self.add_element_attributes(axis, obj) grid.objects.link(obj) def create_type_products(self): - type_products = self.file.by_type('IfcTypeProduct') - for collection in self.project['blender'].children: - if collection.name == 'Types': + type_products = self.file.by_type("IfcTypeProduct") + for collection in self.project["blender"].children: + if collection.name == "Types": self.type_collection = collection break if not self.type_collection: - self.type_collection = bpy.data.collections.new('Types') - self.project['blender'].children.link(self.type_collection) + self.type_collection = bpy.data.collections.new("Types") + self.project["blender"].children.link(self.type_collection) for type_product in type_products: self.create_type_product(type_product) def create_type_product(self, element): - self.ifc_import_settings.logger.info('Creating object %s', element) + self.ifc_import_settings.logger.info("Creating object %s", element) if element.GlobalId in self.existing_elements: self.type_products[element.GlobalId] = self.existing_elements[element.GlobalId] return @@ -761,13 +764,13 @@ class IfcImporter(): if self.ifc_import_settings.should_import_type_representations and representation_map: try: shape = ifcopenshell.geom.create_shape(self.settings, representation_map.MappedRepresentation) - mesh_name = f'mesh-{shape.id}' + mesh_name = f"mesh-{shape.id}" mesh = self.meshes.get(mesh_name) if mesh is None: mesh = self.create_mesh(element, shape) self.meshes[mesh_name] = mesh except: - self.ifc_import_settings.logger.error('Failed to generate shape for %s', element) + self.ifc_import_settings.logger.error("Failed to generate shape for %s", element) obj = bpy.data.objects.new(self.get_name(element), mesh) if mesh: self.material_creator.create(element, obj, mesh) @@ -784,13 +787,15 @@ class IfcImporter(): return for representation_map in element.RepresentationMaps: context = representation_map.MappedRepresentation.ContextOfItems - if context.ContextType == 'Model' \ - and context.ContextIdentifier == 'Body' \ - and context.TargetView == 'MODEL_VIEW': + if ( + context.ContextType == "Model" + and context.ContextIdentifier == "Body" + and context.TargetView == "MODEL_VIEW" + ): return representation_map def create_products_legacy(self): - elements = self.file.by_type('IfcElement') + self.file.by_type('IfcSpace') + elements = self.file.by_type("IfcElement") + self.file.by_type("IfcSpace") for element in elements: self.create_product_legacy(element) @@ -799,8 +804,11 @@ class IfcImporter(): return # TODO: the iterator is kind of useless here, rewrite this iterator = ifcopenshell.geom.iterator( - self.settings_native, self.file, multiprocessing.cpu_count(), - include=[self.file.by_guid(guid) for guid in self.native_elements.keys()] or None) + self.settings_native, + self.file, + multiprocessing.cpu_count(), + include=[self.file.by_guid(guid) for guid in self.native_elements.keys()] or None, + ) valid_file = iterator.initialize() total = 0 checkpoint = time.time() @@ -809,27 +817,28 @@ class IfcImporter(): while True: total += 1 if total % 250 == 0: - print('{} elements processed in {:.2f}s ...'.format(total, time.time() - checkpoint)) + print("{} elements processed in {:.2f}s ...".format(total, time.time() - checkpoint)) checkpoint = time.time() shape = iterator.get() if shape: self.create_product(self.file.by_id(shape.guid), shape) if not iterator.next(): break - print('Done creating geometry') + print("Done creating geometry") def create_products(self): if self.ifc_import_settings.should_use_cpu_multiprocessing: iterator = ifcopenshell.geom.iterator( - self.settings, self.file, multiprocessing.cpu_count(), + self.settings, + self.file, + multiprocessing.cpu_count(), include=self.include_elements or None, - exclude=self.exclude_elements or None - ) + exclude=self.exclude_elements or None, + ) else: iterator = ifcopenshell.geom.iterator( - self.settings, self.file, - include=self.include_elements or None, - exclude=self.exclude_elements or None) + self.settings, self.file, include=self.include_elements or None, exclude=self.exclude_elements or None + ) valid_file = iterator.initialize() if not valid_file: return False @@ -838,36 +847,34 @@ class IfcImporter(): while True: total += 1 if total % 250 == 0: - print('{} elements processed in {:.2f}s ...'.format(total, time.time() - checkpoint)) + print("{} elements processed in {:.2f}s ...".format(total, time.time() - checkpoint)) checkpoint = time.time() shape = iterator.get() if shape: self.create_product(self.file.by_id(shape.guid), shape) if not iterator.next(): break - print('Done creating geometry') + print("Done creating geometry") def create_product(self, element, shape=None): if element is None: return - if not self.ifc_import_settings.should_import_opening_elements \ - and element.is_a('IfcOpeningElement'): + if not self.ifc_import_settings.should_import_opening_elements and element.is_a("IfcOpeningElement"): return - if not self.ifc_import_settings.should_import_spaces \ - and element.is_a('IfcSpace'): + if not self.ifc_import_settings.should_import_spaces and element.is_a("IfcSpace"): return if element.GlobalId in self.existing_elements: return self.existing_elements[element.GlobalId] - self.ifc_import_settings.logger.info('Creating object %s', element) + self.ifc_import_settings.logger.info("Creating object %s", element) is_fresh_mesh = False if shape: # TODO: make names more meaningful - mesh_name = f'mesh-{shape.geometry.id}' + mesh_name = f"mesh-{shape.geometry.id}" mesh = self.meshes.get(mesh_name) if mesh is None: if element.GlobalId in self.native_elements: @@ -883,15 +890,14 @@ class IfcImporter(): if shape: m = shape.transformation.matrix.data - mat = mathutils.Matrix(([m[0], m[1], m[2], 0], - [m[3], m[4], m[5], 0], - [m[6], m[7], m[8], 0], - [m[9], m[10], m[11], 1])) + mat = mathutils.Matrix( + ([m[0], m[1], m[2], 0], [m[3], m[4], m[5], 0], [m[6], m[7], m[8], 0], [m[9], m[10], m[11], 1]) + ) mat.transpose() obj.matrix_world = mat if is_fresh_mesh: self.material_creator.create(element, obj, mesh) - elif hasattr(element, 'ObjectPlacement'): + elif hasattr(element, "ObjectPlacement"): obj.matrix_world = self.get_element_matrix(element) self.add_element_representation_items(element, obj) @@ -906,17 +912,25 @@ class IfcImporter(): return obj def add_element_representation_items(self, element, obj): - if not obj.data or 'ios_items' not in obj.data: + if not obj.data or "ios_items" not in obj.data: return cumulative_vertex_index = 0 - for i, item in enumerate(obj.data['ios_items']): - vg = obj.vertex_groups.new(name=f'Item/{i}/' + item['name']) - vg.add([v.index for v in obj.data.vertices[cumulative_vertex_index:cumulative_vertex_index+item['total_vertices']]], 1, 'ADD') - for subitem in item['subitems']: - vg = obj.vertex_groups.new(name=f'Subitem/{i}/' + subitem['name']) - vg.add([v + cumulative_vertex_index for v in subitem['vertices']], - 1, 'ADD') - cumulative_vertex_index += item['total_vertices'] + for i, item in enumerate(obj.data["ios_items"]): + vg = obj.vertex_groups.new(name=f"Item/{i}/" + item["name"]) + vg.add( + [ + v.index + for v in obj.data.vertices[ + cumulative_vertex_index : cumulative_vertex_index + item["total_vertices"] + ] + ], + 1, + "ADD", + ) + for subitem in item["subitems"]: + vg = obj.vertex_groups.new(name=f"Subitem/{i}/" + subitem["name"]) + vg.add([v + cumulative_vertex_index for v in subitem["vertices"]], 1, "ADD") + cumulative_vertex_index += item["total_vertices"] def create_native_mesh(self, element, shape): # TODO This should be split off into its own module for run-time native mesh conversion @@ -924,34 +938,38 @@ class IfcImporter(): materials = [] items = [] for representation in self.get_body_representations(element.Representation.Representations): - for item in representation['raw'].Items: + for item in representation["raw"].Items: material_name = self.get_representation_item_material_name(item) if not material_name: # Magic string NULLMAT represents no material, unless this has a better approach - material_name = 'NULLMAT' + material_name = "NULLMAT" materials.append(material_name) if item.id() in data: item = data[item.id()] - if item.is_a() == 'IfcExtrudedAreaSolid': + if item.is_a() == "IfcExtrudedAreaSolid": native = self.create_native_extruded_area_solid(item, element) if native: bmesh.ops.transform( - native['blender'], matrix=representation['matrix'], verts=native['blender'].verts) + native["blender"], matrix=representation["matrix"], verts=native["blender"].verts + ) items.append(native) else: items.append(None) - elif item.is_a('IfcSweptDiskSolid'): - items.append({ - 'blender': self.transform_curve( - self.create_native_swept_disk_solid(item, element), representation['matrix']), - 'raw': item, - 'subitems': [] - }) - elif item.is_a('IfcFacetedBrep'): + elif item.is_a("IfcSweptDiskSolid"): + items.append( + { + "blender": self.transform_curve( + self.create_native_swept_disk_solid(item, element), representation["matrix"] + ), + "raw": item, + "subitems": [], + } + ) + elif item.is_a("IfcFacetedBrep"): bm = self.create_native_faceted_brep(item, element) if bm: - bmesh.ops.transform(bm, matrix=representation['matrix'], verts=bm.verts) - items.append({'blender': bm, 'raw': item, 'subitems': []}) + bmesh.ops.transform(bm, matrix=representation["matrix"], verts=bm.verts) + items.append({"blender": bm, "raw": item, "subitems": []}) else: items.append(None) else: @@ -968,29 +986,31 @@ class IfcImporter(): for i, item in enumerate(items): if not item: continue - if isinstance(item['blender'], bpy.types.Curve): + if isinstance(item["blender"], bpy.types.Curve): if bevel_depth is None: - bevel_depth = item['blender'].bevel_depth - merged_curve = item['blender'] - elif item['blender'].bevel_depth == bevel_depth: - self.merge_curves(merged_curve, item['blender']) + bevel_depth = item["blender"].bevel_depth + merged_curve = item["blender"] + elif item["blender"].bevel_depth == bevel_depth: + self.merge_curves(merged_curve, item["blender"]) else: # TODO: handle if there are multiple different radiuses # We don't have a choice but to meshify it pass - elif isinstance(item['blender'], bmesh.types.BMesh): - representation_items.append({ - 'name': item['raw'].is_a(), - 'total_vertices': len(item['blender'].verts), - 'subitems': item['subitems'] - }) - total_polygons = len(item['blender'].faces) + elif isinstance(item["blender"], bmesh.types.BMesh): + representation_items.append( + { + "name": item["raw"].is_a(), + "total_vertices": len(item["blender"].verts), + "subitems": item["subitems"], + } + ) + total_polygons = len(item["blender"].faces) if merged_bm is None: - merged_bm = item['blender'] + merged_bm = item["blender"] else: - self.merge_bmeshes(merged_bm, item['blender']) + self.merge_bmeshes(merged_bm, item["blender"]) # Magic string NULLMAT represents no material, unless this has a better approach - if materials[i] == 'NULLMAT': + if materials[i] == "NULLMAT": # Magic number -1 represents no material, until this has a better approach material_ids += [-1] * total_polygons else: @@ -998,12 +1018,12 @@ class IfcImporter(): if merged_curve: return merged_curve # TODO: handle both curve and bmeshes combined - mesh = bpy.data.meshes.new('Native Mesh') + mesh = bpy.data.meshes.new("Native Mesh") merged_bm.to_mesh(mesh) merged_bm.free() - mesh['ios_materials'] = materials - mesh['ios_material_ids'] = material_ids - mesh['ios_items'] = representation_items + mesh["ios_materials"] = materials + mesh["ios_material_ids"] = material_ids + mesh["ios_items"] = representation_items mesh.BIMMeshProperties.is_native = True return mesh @@ -1021,7 +1041,7 @@ class IfcImporter(): def merge_curves(self, a, b): for spline in b.splines: - new_spline = a.splines.new('POLY') + new_spline = a.splines.new("POLY") is_first = True for point in spline.points: if is_first: @@ -1032,7 +1052,7 @@ class IfcImporter(): return a def merge_bmeshes(self, a, b): - mesh = bpy.data.meshes.new('x') + mesh = bpy.data.meshes.new("x") b.to_mesh(mesh) b.free() a.from_mesh(mesh) @@ -1063,71 +1083,56 @@ class IfcImporter(): return mesh def create_native_extruded_area_solid(self, item, element): - #print(shape.materials) + # print(shape.materials) subitems = [] - if item.SweptArea.is_a() == 'IfcArbitraryClosedProfileDef': + if item.SweptArea.is_a() == "IfcArbitraryClosedProfileDef": shape = ifcopenshell.geom.create_shape(self.settings_native, item.SweptArea.OuterCurve) bm = self.bmesh_from_pydata(*self.shape_to_mesh(shape)) bm.faces.new([v for v in bm.verts]) bm.faces.ensure_lookup_table() - subitems.append({ - 'name': item.SweptArea.is_a(), - 'vertices': range(0, len(bm.verts)) - }) - elif item.SweptArea.is_a() == 'IfcRectangleProfileDef': + subitems.append({"name": item.SweptArea.is_a(), "vertices": range(0, len(bm.verts))}) + elif item.SweptArea.is_a() == "IfcRectangleProfileDef": bm = self.bmesh_from_rectangle(item.SweptArea.XDim, item.SweptArea.YDim) if item.SweptArea.Position: bmesh.ops.transform(bm, matrix=self.get_axis2placement(item.SweptArea.Position), verts=bm.verts) bmesh.ops.transform(bm, matrix=mathutils.Matrix() * self.unit_scale, verts=bm.verts) - subitems.append({ - 'name': item.SweptArea.is_a(), - 'vertices': [0, 1, 2, 3] - }) - elif item.SweptArea.is_a() == 'IfcCircleProfileDef': + subitems.append({"name": item.SweptArea.is_a(), "vertices": [0, 1, 2, 3]}) + elif item.SweptArea.is_a() == "IfcCircleProfileDef": bm = self.bmesh_from_circle(item.SweptArea.Radius) if item.SweptArea.Position: bmesh.ops.transform(bm, matrix=self.get_axis2placement(item.SweptArea.Position), verts=bm.verts) bmesh.ops.transform(bm, matrix=mathutils.Matrix() * self.unit_scale, verts=bm.verts) - subitems.append({ - 'name': item.SweptArea.is_a(), - # This strange vertice offset is due to a Blender quirk - 'vertices': range(1, len(bm.verts)+1) - }) + subitems.append( + { + "name": item.SweptArea.is_a(), + # This strange vertice offset is due to a Blender quirk + "vertices": range(1, len(bm.verts) + 1), + } + ) else: # TODO: what if we can't handle it? return results = bmesh.ops.extrude_face_region(bm, geom=[bm.faces[0]]) bm.faces.ensure_lookup_table() offset = self.unit_scale * item.Depth * mathutils.Vector(item.ExtrudedDirection.DirectionRatios) - if item.SweptArea.is_a() == 'IfcCircleProfileDef': + if item.SweptArea.is_a() == "IfcCircleProfileDef": # Circle profiles have a quirk apparently in Blender - subitems.append({ - 'name': 'ExtrudedDirection', - 'vertices': [0, 1] - }) + subitems.append({"name": "ExtrudedDirection", "vertices": [0, 1]}) else: - subitems.append({ - 'name': 'ExtrudedDirection', - 'vertices': [0, len(subitems[-1]['vertices'])] - }) - for geom in results['geom']: + subitems.append({"name": "ExtrudedDirection", "vertices": [0, len(subitems[-1]["vertices"])]}) + for geom in results["geom"]: if isinstance(geom, bmesh.types.BMVert): geom.co += offset if item.Position: - bmesh.ops.transform( - bm, matrix=self.scale_matrix(self.get_axis2placement(item.Position)), verts=bm.verts) - return { - 'blender': bm, - 'raw': item, - 'subitems': subitems - } - #mesh['ios_material_ids'] = [0] * len(bm.faces) + bmesh.ops.transform(bm, matrix=self.scale_matrix(self.get_axis2placement(item.Position)), verts=bm.verts) + return {"blender": bm, "raw": item, "subitems": subitems} + # mesh['ios_material_ids'] = [0] * len(bm.faces) def bmesh_from_rectangle(self, x, y): bm = bmesh.new() - bmesh.ops.create_grid(bm, x_segments=1, y_segments=1, size=x/2) + bmesh.ops.create_grid(bm, x_segments=1, y_segments=1, size=x / 2) bm.verts.ensure_lookup_table() - diff_vector = mathutils.Vector((0., (x - y) / 2., 0.)) + diff_vector = mathutils.Vector((0.0, (x - y) / 2.0, 0.0)) bm.verts[0].co += diff_vector bm.verts[1].co += diff_vector bm.verts[2].co -= diff_vector @@ -1158,13 +1163,13 @@ class IfcImporter(): for collection in self.aggregate_collections.values(): obs = [] for i, ob in enumerate(collection.objects): - if ob.type == 'MESH': + if ob.type == "MESH": if i > 0: - global_ids_to_delete.append(ob.BIMObjectProperties.attributes.get('GlobalId').string_value) + global_ids_to_delete.append(ob.BIMObjectProperties.attributes.get("GlobalId").string_value) obs.append(ob) ctx = {} - ctx['active_object'] = obs[0] - ctx['selected_editable_objects'] = obs + ctx["active_object"] = obs[0] + ctx["selected_editable_objects"] = obs if obs[0].data.users > 1: obs[0].data = obs[0].data.copy() bpy.ops.object.join(ctx) @@ -1183,20 +1188,18 @@ class IfcImporter(): def merge_by_class(self): merge_set = {} for obj in self.added_data.values(): - if '/' not in obj.name \ - or 'IfcRelAggregates' in obj.users_collection[0].name: + if "/" not in obj.name or "IfcRelAggregates" in obj.users_collection[0].name: continue - merge_set.setdefault(obj.name.split('/')[0], []).append(obj) + merge_set.setdefault(obj.name.split("/")[0], []).append(obj) self.merge_objects(merge_set) def merge_by_material(self): merge_set = {} for obj in self.added_data.values(): - if '/' not in obj.name \ - or 'IfcRelAggregates' in obj.users_collection[0].name: + if "/" not in obj.name or "IfcRelAggregates" in obj.users_collection[0].name: continue if not obj.material_slots: - merge_set.setdefault('no-material', []).append(obj) + merge_set.setdefault("no-material", []).append(obj) else: merge_set.setdefault(obj.material_slots[0].name, []).append(obj) self.merge_objects(merge_set) @@ -1204,48 +1207,55 @@ class IfcImporter(): def merge_objects(self, merge_set): for ifc_class, objs in merge_set.items(): context_override = {} - context_override['object'] = context_override['active_object'] = objs[0] - context_override['selected_objects'] = context_override['selected_editable_objects'] = objs + context_override["object"] = context_override["active_object"] = objs[0] + context_override["selected_objects"] = context_override["selected_editable_objects"] = objs bpy.ops.object.join(context_override) def merge_materials_by_colour(self): cleaned_materials = {} for m in bpy.data.materials: - key = '-'.join([str(x) for x in m.diffuse_color]) - cleaned_materials[key] = { 'diffuse_color': m.diffuse_color } + key = "-".join([str(x) for x in m.diffuse_color]) + cleaned_materials[key] = {"diffuse_color": m.diffuse_color} for cleaned_material in cleaned_materials.values(): - cleaned_material['material'] = bpy.data.materials.new('Merged Material') - cleaned_material['material'].diffuse_color = cleaned_material['diffuse_color'] + cleaned_material["material"] = bpy.data.materials.new("Merged Material") + cleaned_material["material"].diffuse_color = cleaned_material["diffuse_color"] for obj in self.added_data.values(): - if not hasattr(obj, 'material_slots') \ - or not obj.material_slots: + if not hasattr(obj, "material_slots") or not obj.material_slots: continue for slot in obj.material_slots: m = slot.material - key = '-'.join([str(x) for x in m.diffuse_color]) - slot.material = cleaned_materials[key]['material'] + key = "-".join([str(x) for x in m.diffuse_color]) + slot.material = cleaned_materials[key]["material"] for material in self.material_creator.materials.values(): bpy.data.materials.remove(material) def add_project_to_scene(self): try: - bpy.context.scene.collection.children.link(self.project['blender']) + bpy.context.scene.collection.children.link(self.project["blender"]) except: # Occurs when reloading a project pass - for collection in bpy.context.view_layer.layer_collection.children[self.project['blender'].name].children[self.aggregate_collection.name].children: + for collection in ( + bpy.context.view_layer.layer_collection.children[self.project["blender"].name] + .children[self.aggregate_collection.name] + .children + ): collection.hide_viewport = True - bpy.context.view_layer.layer_collection.children[self.project['blender'].name].children[self.opening_collection.name].hide_viewport = True - bpy.context.view_layer.layer_collection.children[self.project['blender'].name].children[self.type_collection.name].hide_viewport = True + bpy.context.view_layer.layer_collection.children[self.project["blender"].name].children[ + self.opening_collection.name + ].hide_viewport = True + bpy.context.view_layer.layer_collection.children[self.project["blender"].name].children[ + self.type_collection.name + ].hide_viewport = True def clean_mesh(self): obj = None last_obj = None for obj in self.added_data.values(): - if obj.type == 'MESH': + if obj.type == "MESH": obj.select_set(True) last_obj = obj if not last_obj: @@ -1260,69 +1270,83 @@ class IfcImporter(): def add_product_representation_contexts(self, element, obj): subcontexts = [] - if element.is_a('IfcProduct'): + if element.is_a("IfcProduct"): if not element.Representation: return for r in element.Representation.Representations: - if r.ContextOfItems.is_a('IfcGeometricRepresentationSubContext'): - subcontexts.append('{}/{}/{}'.format( - r.ContextOfItems.ContextType or '', - r.ContextOfItems.ContextIdentifier or '', - r.ContextOfItems.TargetView or '')) + if r.ContextOfItems.is_a("IfcGeometricRepresentationSubContext"): + subcontexts.append( + "{}/{}/{}".format( + r.ContextOfItems.ContextType or "", + r.ContextOfItems.ContextIdentifier or "", + r.ContextOfItems.TargetView or "", + ) + ) else: - subcontexts.append('{}/{}/{}'.format( - r.ContextOfItems.ContextType or '', - r.ContextOfItems.ContextIdentifier or '', - '')) - elif element.is_a('IfcTypeProduct'): + subcontexts.append( + "{}/{}/{}".format( + r.ContextOfItems.ContextType or "", r.ContextOfItems.ContextIdentifier or "", "" + ) + ) + elif element.is_a("IfcTypeProduct"): if not element.RepresentationMaps: return for r in element.RepresentationMaps: - if r.MappedRepresentation.ContextOfItems.is_a('IfcGeometricRepresentationSubContext'): - subcontexts.append('{}/{}/{}'.format( - r.MappedRepresentation.ContextOfItems.ContextType or '', - r.MappedRepresentation.ContextOfItems.ContextIdentifier or '', - r.MappedRepresentation.ContextOfItems.TargetView or '')) + if r.MappedRepresentation.ContextOfItems.is_a("IfcGeometricRepresentationSubContext"): + subcontexts.append( + "{}/{}/{}".format( + r.MappedRepresentation.ContextOfItems.ContextType or "", + r.MappedRepresentation.ContextOfItems.ContextIdentifier or "", + r.MappedRepresentation.ContextOfItems.TargetView or "", + ) + ) else: - subcontexts.append('{}/{}/{}'.format( - r.MappedRepresentation.ContextOfItems.ContextType or '', - r.MappedRepresentation.ContextOfItems.ContextIdentifier or '', - '')) + subcontexts.append( + "{}/{}/{}".format( + r.MappedRepresentation.ContextOfItems.ContextType or "", + r.MappedRepresentation.ContextOfItems.ContextIdentifier or "", + "", + ) + ) subcontexts = set(subcontexts) for subcontext in subcontexts: representation_context = obj.BIMObjectProperties.representation_contexts.add() - representation_context.context, representation_context.name, representation_context.target_view = subcontext.split('/') + ( + representation_context.context, + representation_context.name, + representation_context.target_view, + ) = subcontext.split("/") def add_product_definitions(self, element, obj): - if not hasattr(element, 'IsDefinedBy') or not element.IsDefinedBy: + if not hasattr(element, "IsDefinedBy") or not element.IsDefinedBy: return for definition in element.IsDefinedBy: - if not definition.is_a('IfcRelDefinesByProperties'): + if not definition.is_a("IfcRelDefinesByProperties"): continue - if definition.RelatingPropertyDefinition.is_a('IfcPropertySet'): + if definition.RelatingPropertyDefinition.is_a("IfcPropertySet"): self.add_pset(definition.RelatingPropertyDefinition, obj) - elif definition.RelatingPropertyDefinition.is_a('IfcElementQuantity'): + elif definition.RelatingPropertyDefinition.is_a("IfcElementQuantity"): self.add_qto(definition.RelatingPropertyDefinition, obj) def add_type_product_psets(self, element, obj): - if not hasattr(element, 'HasPropertySets') or not element.HasPropertySets: + if not hasattr(element, "HasPropertySets") or not element.HasPropertySets: return for definition in element.HasPropertySets: - if definition.is_a('IfcPropertySet'): + if definition.is_a("IfcPropertySet"): self.add_pset(definition, obj) def add_pset(self, pset, obj): new_pset = obj.BIMObjectProperties.psets.add() new_pset.name = pset.Name if new_pset.name in schema.ifc.psets: - for prop_name in schema.ifc.psets[new_pset.name]['HasPropertyTemplates'].keys(): + for prop_name in schema.ifc.psets[new_pset.name]["HasPropertyTemplates"].keys(): prop = new_pset.properties.add() prop.name = prop_name # Invalid IFC, but some vendors like Solidworks do this so we accomodate it if not pset.HasProperties: return for prop in pset.HasProperties: - if prop.is_a('IfcPropertySingleValue') and prop.NominalValue: + if prop.is_a("IfcPropertySingleValue") and prop.NominalValue: index = new_pset.properties.find(prop.Name) if index >= 0: new_pset.properties[index].string_value = str(prop.NominalValue.wrappedValue) @@ -1335,12 +1359,12 @@ class IfcImporter(): new_qto = obj.BIMObjectProperties.qtos.add() new_qto.name = str(qto.Name) if new_qto.name in schema.ifc.qtos: - for prop_name in schema.ifc.qtos[new_qto.name]['HasPropertyTemplates'].keys(): + for prop_name in schema.ifc.qtos[new_qto.name]["HasPropertyTemplates"].keys(): prop = new_qto.properties.add() prop.name = prop_name for prop in qto.Quantities: - if prop.is_a('IfcPhysicalSimpleQuantity'): - value = getattr(prop, '{}Value'.format(prop.is_a()[len('IfcQuantity'):])) + if prop.is_a("IfcPhysicalSimpleQuantity"): + value = getattr(prop, "{}Value".format(prop.is_a()[len("IfcQuantity") :])) if not value: continue index = new_qto.properties.find(prop.Name) @@ -1357,126 +1381,132 @@ class IfcImporter(): obj.BIMObjectProperties.relating_type = self.type_products[related_type.GlobalId] def add_opening_relation(self, element, obj): - if not element.is_a('IfcOpeningElement'): + if not element.is_a("IfcOpeningElement"): return self.openings[element.GlobalId] = obj def load_existing_rooted_elements(self): for obj in bpy.data.objects: - if hasattr(obj, 'BIMObjectProperties') and obj.BIMObjectProperties.attributes.get('GlobalId'): - self.existing_elements[obj.BIMObjectProperties.attributes.get('GlobalId').string_value] = obj + if hasattr(obj, "BIMObjectProperties") and obj.BIMObjectProperties.attributes.get("GlobalId"): + self.existing_elements[obj.BIMObjectProperties.attributes.get("GlobalId").string_value] = obj def load_diff(self): if not self.ifc_import_settings.diff_file: return - with open(self.ifc_import_settings.diff_file, 'r') as file: + with open(self.ifc_import_settings.diff_file, "r") as file: self.diff = json.load(file) def cache_file(self): - destination = os.path.join(bpy.context.scene.BIMProperties.data_dir, 'cache', 'ifc') + destination = os.path.join(bpy.context.scene.BIMProperties.data_dir, "cache", "ifc") copythread = FileCopy(self.ifc_import_settings.input_file, destination) - bpy.context.scene.BIMProperties.ifc_cache = os.path.join(destination, - os.path.basename(self.ifc_import_settings.input_file)) + bpy.context.scene.BIMProperties.ifc_cache = os.path.join( + destination, os.path.basename(self.ifc_import_settings.input_file) + ) copythread.start() copythread.join() def load_file(self): - self.ifc_import_settings.logger.info('loading file %s', self.ifc_import_settings.input_file) - extension = self.ifc_import_settings.input_file.split('.')[-1] - if extension.lower() == 'ifczip': + self.ifc_import_settings.logger.info("loading file %s", self.ifc_import_settings.input_file) + extension = self.ifc_import_settings.input_file.split(".")[-1] + if extension.lower() == "ifczip": with tempfile.TemporaryDirectory() as unzipped_path: - with zipfile.ZipFile(self.ifc_import_settings.input_file, 'r') as zip_ref: + with zipfile.ZipFile(self.ifc_import_settings.input_file, "r") as zip_ref: zip_ref.extractall(unzipped_path) - for filename in Path(unzipped_path).glob('**/*.ifc'): + for filename in Path(unzipped_path).glob("**/*.ifc"): self.file = ifcopenshell.open(filename) break - elif extension.lower() == 'ifcxml': + elif extension.lower() == "ifcxml": self.file = ifcopenshell.file( - ifcopenshell.ifcopenshell_wrapper.parse_ifcxml(self.ifc_import_settings.input_file)) - elif extension.lower() == 'ifc': + ifcopenshell.ifcopenshell_wrapper.parse_ifcxml(self.ifc_import_settings.input_file) + ) + elif extension.lower() == "ifc": self.file = ifcopenshell.open(self.ifc_import_settings.input_file) ifc.IfcStore.file = self.file def set_ifc_file(self): bpy.context.scene.BIMProperties.ifc_file = self.ifc_import_settings.input_file - ifc.IfcStore.path = 'self.ifc_import_settings.input_file' + ifc.IfcStore.path = "self.ifc_import_settings.input_file" def calculate_unit_scale(self): - units = self.file.by_type('IfcUnitAssignment')[0] + units = self.file.by_type("IfcUnitAssignment")[0] for unit in units.Units: - if not hasattr(unit, 'UnitType') \ - or unit.UnitType != 'LENGTHUNIT': + if not hasattr(unit, "UnitType") or unit.UnitType != "LENGTHUNIT": continue - while unit.is_a('IfcConversionBasedUnit'): + while unit.is_a("IfcConversionBasedUnit"): self.unit_scale *= unit.ConversionFactor.ValueComponent.wrappedValue unit = unit.ConversionFactor.UnitComponent - if unit.is_a('IfcSIUnit'): + if unit.is_a("IfcSIUnit"): self.unit_scale *= helper.SIUnitHelper.get_prefix_multiplier(unit.Prefix) def set_units(self): - units = self.file.by_type('IfcUnitAssignment')[0] + units = self.file.by_type("IfcUnitAssignment")[0] for unit in units.Units: - if unit.is_a('IfcNamedUnit') and unit.UnitType == 'LENGTHUNIT': - if unit.is_a('IfcSIUnit'): - bpy.context.scene.unit_settings.system = 'METRIC' - if unit.Name == 'METRE': + if unit.is_a("IfcNamedUnit") and unit.UnitType == "LENGTHUNIT": + if unit.is_a("IfcSIUnit"): + bpy.context.scene.unit_settings.system = "METRIC" + if unit.Name == "METRE": if not unit.Prefix: - bpy.context.scene.unit_settings.length_unit = 'METERS' + bpy.context.scene.unit_settings.length_unit = "METERS" else: - bpy.context.scene.unit_settings.length_unit = f'{unit.Prefix}METERS' + bpy.context.scene.unit_settings.length_unit = f"{unit.Prefix}METERS" else: - bpy.context.scene.unit_settings.system = 'IMPERIAL' - if unit.Name == 'inch': - bpy.context.scene.unit_settings.length_unit = 'INCHES' - elif unit.Name == 'foot': - bpy.context.scene.unit_settings.length_unit = 'FEET' - elif unit.is_a('IfcNamedUnit') and unit.UnitType == 'AREAUNIT': - bpy.context.scene.BIMProperties.area_unit = '{}{}'.format( - unit.Prefix + '/' if hasattr(unit, 'Prefix') and unit.Prefix else '', unit.Name) - elif unit.is_a('IfcNamedUnit') and unit.UnitType == 'VOLUMEUNIT': - bpy.context.scene.BIMProperties.volume_unit = '{}{}'.format( - unit.Prefix + '/' if hasattr(unit, 'Prefix') and unit.Prefix else '', unit.Name) + bpy.context.scene.unit_settings.system = "IMPERIAL" + if unit.Name == "inch": + bpy.context.scene.unit_settings.length_unit = "INCHES" + elif unit.Name == "foot": + bpy.context.scene.unit_settings.length_unit = "FEET" + elif unit.is_a("IfcNamedUnit") and unit.UnitType == "AREAUNIT": + bpy.context.scene.BIMProperties.area_unit = "{}{}".format( + unit.Prefix + "/" if hasattr(unit, "Prefix") and unit.Prefix else "", unit.Name + ) + elif unit.is_a("IfcNamedUnit") and unit.UnitType == "VOLUMEUNIT": + bpy.context.scene.BIMProperties.volume_unit = "{}{}".format( + unit.Prefix + "/" if hasattr(unit, "Prefix") and unit.Prefix else "", unit.Name + ) def create_geometric_representation_contexts(self): bpy.context.scene.BIMProperties.has_model_context = False - for context in self.file.by_type('IfcGeometricRepresentationContext'): - if context.is_a('IfcGeometricRepresentationSubContext'): + for context in self.file.by_type("IfcGeometricRepresentationContext"): + if context.is_a("IfcGeometricRepresentationSubContext"): if not context.ContextIdentifier: # Revit creates invalid contexts, so we just ignore them continue - if context.ContextType == 'Model': + if context.ContextType == "Model": subcontexts = bpy.context.scene.BIMProperties.model_subcontexts - elif context.ContextType == 'Plan': + elif context.ContextType == "Plan": subcontexts = bpy.context.scene.BIMProperties.plan_subcontexts if subcontexts.get(context.ContextIdentifier): continue subcontext = subcontexts.add() subcontext.name = context.ContextIdentifier subcontext.target_view = context.TargetView - elif context.ContextType == 'Model': + elif context.ContextType == "Model": bpy.context.scene.BIMProperties.has_model_context = True - elif context.ContextType == 'Plan': + elif context.ContextType == "Plan": bpy.context.scene.BIMProperties.has_plan_context = True def create_project(self): - self.project = { 'ifc': self.file.by_type('IfcProject')[0] } - if self.project['ifc'].GlobalId in self.existing_elements: - self.project['blender'] = self.existing_elements[self.project['ifc'].GlobalId].users_collection[0] + self.project = {"ifc": self.file.by_type("IfcProject")[0]} + if self.project["ifc"].GlobalId in self.existing_elements: + self.project["blender"] = self.existing_elements[self.project["ifc"].GlobalId].users_collection[0] return - self.project['blender'] = bpy.data.collections.new('IfcProject/{}'.format(self.project['ifc'].Name)) - obj = self.create_product(self.project['ifc']) + self.project["blender"] = bpy.data.collections.new("IfcProject/{}".format(self.project["ifc"].Name)) + obj = self.create_product(self.project["ifc"]) if obj: - self.project['blender'].objects.link(obj) - del self.added_data[self.project['ifc'].GlobalId] + self.project["blender"].objects.link(obj) + del self.added_data[self.project["ifc"].GlobalId] def create_classifications(self): - for element in self.file.by_type('IfcClassification'): + for element in self.file.by_type("IfcClassification"): classification = bpy.context.scene.BIMProperties.classifications.add() data_map = { - 'name': 'Name', 'source': 'Source', - 'edition': 'Edition', 'edition_date': 'EditionDate', - 'description': 'Description', 'location': 'Location', - 'reference_tokens': 'ReferenceTokens' + "name": "Name", + "source": "Source", + "edition": "Edition", + "edition_date": "EditionDate", + "description": "Description", + "location": "Location", + "reference_tokens": "ReferenceTokens", } for key, value in data_map.items(): if hasattr(element, value) and getattr(element, value): @@ -1484,19 +1514,22 @@ class IfcImporter(): classification_file = ifcopenshell.file() # IFC2X3 has no references, so let's manually add them - if self.file.wrapped_data.schema == 'IFC2X3': + if self.file.wrapped_data.schema == "IFC2X3": if element.EditionDate: - edition_date = '{}-{}-{}'.format( + edition_date = "{}-{}-{}".format( element.EditionDate.YearComponent, element.EditionDate.MonthComponent, - element.EditionDate.DayComponent) + element.EditionDate.DayComponent, + ) else: edition_date = None classification_element = classification_file.createIfcClassification( - element.Source, element.Edition, edition_date, element.Name) - for reference in self.file.by_type('IfcClassificationReference'): + element.Source, element.Edition, edition_date, element.Name + ) + for reference in self.file.by_type("IfcClassificationReference"): classification_file.createIfcClassificationReference( - reference.Location, reference.ItemReference, reference.Name, classification_element) + reference.Location, reference.ItemReference, reference.Name, classification_element + ) else: references = [element] while references: @@ -1510,6 +1543,7 @@ class IfcImporter(): self.schema_dir = bpy.context.scene.BIMProperties.schema_dir from . import prop + prop.classification_enum.clear() prop.getClassifications(self, bpy.context) @@ -1520,51 +1554,50 @@ class IfcImporter(): return results def create_constraints(self): - for element in self.file.by_type('IfcObjective'): + for element in self.file.by_type("IfcObjective"): constraint = bpy.context.scene.BIMProperties.constraints.add() data_map = { - 'name': 'Name', - 'description': 'Description', - 'constraint_grade': 'ConstraintGrade', - 'constraint_source': 'ConstraintSource', - 'user_defined_grade': 'UserDefinedGrade', - 'objective_qualifier': 'ObjectiveQualifier', - 'user_defined_qualifier': 'UserDefinedQualifier', + "name": "Name", + "description": "Description", + "constraint_grade": "ConstraintGrade", + "constraint_source": "ConstraintSource", + "user_defined_grade": "UserDefinedGrade", + "objective_qualifier": "ObjectiveQualifier", + "user_defined_qualifier": "UserDefinedQualifier", } for key, value in data_map.items(): if hasattr(element, value) and getattr(element, value): setattr(constraint, key, getattr(element, value)) def create_document_information(self): - for element in self.file.by_type('IfcDocumentInformation'): + for element in self.file.by_type("IfcDocumentInformation"): info = bpy.context.scene.BIMProperties.document_information.add() data_map = { - 'name': 'Identification', - 'human_name': 'Name', - 'description': 'Description', - 'location': 'Location', - 'purpose': 'Purpose', - 'intended_use': 'IntendedUse', - 'scope': 'Scope', - 'revision': 'Revision', - 'creation_time': 'CreationTime', - 'last_revision_time': 'LastRevisionTime', - 'electronic_format': 'ElectronicFormat', - 'valid_from': 'ValidFrom', - 'valid_until': 'ValidUntil', - 'confidentiality': 'Confidentiality', - 'status': 'Status' + "name": "Identification", + "human_name": "Name", + "description": "Description", + "location": "Location", + "purpose": "Purpose", + "intended_use": "IntendedUse", + "scope": "Scope", + "revision": "Revision", + "creation_time": "CreationTime", + "last_revision_time": "LastRevisionTime", + "electronic_format": "ElectronicFormat", + "valid_from": "ValidFrom", + "valid_until": "ValidUntil", + "confidentiality": "Confidentiality", + "status": "Status", } - if self.file.schema == 'IFC2X3': - data_map['name'] = 'DocumentId' + if self.file.schema == "IFC2X3": + data_map["name"] = "DocumentId" for key, value in data_map.items(): if hasattr(element, value) and getattr(element, value): element_value = getattr(element, value) - if self.file.schema == 'IFC2X3' \ - and isinstance(element_value, ifcopenshell.entity_instance): - if element_value.is_a('IfcDateAndTime'): + if self.file.schema == "IFC2X3" and isinstance(element_value, ifcopenshell.entity_instance): + if element_value.is_a("IfcDateAndTime"): element_value = self.convert_ifc_date_and_time_to_string(element_value) - elif element_value.is_a('IfcDocumentElectronicFormat'): + elif element_value.is_a("IfcDocumentElectronicFormat"): element_value = self.convert_ifc_document_electronic_format(element_value) setattr(info, key, element_value) @@ -1577,26 +1610,26 @@ class IfcImporter(): element.TimeComponent.HourComponent, element.TimeComponent.MinuteComponent if element.TimeComponent.MinuteComponent else 0, int(element.TimeComponent.SecondComponent) if element.TimeComponent.SecondComponent else 0, - ).isoformat() + ).isoformat() def convert_ifc_document_electronic_format(self, element): if not element.MimeContentType or not element.MimeSubtype: - return '' - return '{}/{}'.format(element.MimeContentType, element.MimeSubtype) + return "" + return "{}/{}".format(element.MimeContentType, element.MimeSubtype) def create_document_references(self): - for element in self.file.by_type('IfcDocumentReference'): + for element in self.file.by_type("IfcDocumentReference"): reference = bpy.context.scene.BIMProperties.document_references.add() data_map = { - 'name': 'Identification', - 'human_name': 'Name', - 'location': 'Location', - 'description': 'Description' + "name": "Identification", + "human_name": "Name", + "location": "Location", + "description": "Description", } for key, value in data_map.items(): if hasattr(element, value) and getattr(element, value): setattr(reference, key, getattr(element, value)) - if self.file.schema == 'IFC2X3': + if self.file.schema == "IFC2X3": if element.ReferenceToDocument: reference.referenced_document = element.ReferenceToDocument[0].DocumentId else: @@ -1604,25 +1637,25 @@ class IfcImporter(): reference.referenced_document = element.ReferencedDocument.Identification def create_spatial_hierarchy(self): - if self.project['ifc'].IsDecomposedBy: - for rel_aggregate in self.project['ifc'].IsDecomposedBy: - self.add_related_objects(self.project['blender'], rel_aggregate.RelatedObjects) + if self.project["ifc"].IsDecomposedBy: + for rel_aggregate in self.project["ifc"].IsDecomposedBy: + self.add_related_objects(self.project["blender"], rel_aggregate.RelatedObjects) def add_related_objects(self, parent, related_objects): for element in related_objects: - if element.is_a('IfcSpace'): + if element.is_a("IfcSpace"): continue global_id = element.GlobalId if global_id in self.existing_elements: collection = self.existing_elements[global_id].users_collection[0] - self.spatial_structure_elements[global_id] = { 'blender': collection } + self.spatial_structure_elements[global_id] = {"blender": collection} else: collection = bpy.data.collections.new(self.get_name(element)) - self.spatial_structure_elements[global_id] = { 'blender': collection } + self.spatial_structure_elements[global_id] = {"blender": collection} parent.children.link(collection) obj = self.create_product(element) if obj: - self.spatial_structure_elements[global_id]['blender_obj'] = obj + self.spatial_structure_elements[global_id]["blender_obj"] = obj collection.objects.link(obj) del self.added_data[element.GlobalId] if element.IsDecomposedBy: @@ -1631,33 +1664,36 @@ class IfcImporter(): def create_aggregates(self): if self.ifc_import_settings.should_allow_non_element_aggregates: - if self.file.schema == 'IFC2X3': - rel_aggregates = [a for a in self.file.by_type('IfcRelAggregates') - if not a.RelatingObject.is_a('IfcSpatialStructureElement')] + if self.file.schema == "IFC2X3": + rel_aggregates = [ + a + for a in self.file.by_type("IfcRelAggregates") + if not a.RelatingObject.is_a("IfcSpatialStructureElement") + ] else: - rel_aggregates = [a for a in self.file.by_type('IfcRelAggregates') - if not a.RelatingObject.is_a('IfcSpatialElement')] + rel_aggregates = [ + a for a in self.file.by_type("IfcRelAggregates") if not a.RelatingObject.is_a("IfcSpatialElement") + ] else: - rel_aggregates = [a for a in self.file.by_type('IfcRelAggregates') - if a.RelatingObject.is_a('IfcElement')] - for collection in self.project['blender'].children: - if collection.name == 'Aggregates': + rel_aggregates = [a for a in self.file.by_type("IfcRelAggregates") if a.RelatingObject.is_a("IfcElement")] + for collection in self.project["blender"].children: + if collection.name == "Aggregates": self.aggregate_collection = collection break if not self.aggregate_collection: - self.aggregate_collection = bpy.data.collections.new('Aggregates') - self.project['blender'].children.link(self.aggregate_collection) + self.aggregate_collection = bpy.data.collections.new("Aggregates") + self.project["blender"].children.link(self.aggregate_collection) for rel_aggregate in rel_aggregates: self.create_aggregate(rel_aggregate) def create_aggregate(self, rel_aggregate): - collection = bpy.data.collections.new(f'IfcRelAggregates/{rel_aggregate.id()}') + collection = bpy.data.collections.new(f"IfcRelAggregates/{rel_aggregate.id()}") self.aggregate_collection.children.link(collection) element = rel_aggregate.RelatingObject - obj = bpy.data.objects.new('{}/{}'.format(element.is_a(), element.Name), None) - obj.instance_type = 'COLLECTION' + obj = bpy.data.objects.new("{}/{}".format(element.is_a(), element.Name), None) + obj.instance_type = "COLLECTION" obj.instance_collection = collection self.place_object_in_spatial_tree(element, obj) self.add_element_attributes(element, obj) @@ -1669,54 +1705,54 @@ class IfcImporter(): self.aggregate_collections[rel_aggregate.id()] = collection def create_openings_collection(self): - self.opening_collection = bpy.data.collections.new('IfcOpeningElements') - self.project['blender'].children.link(self.opening_collection) + self.opening_collection = bpy.data.collections.new("IfcOpeningElements") + self.project["blender"].children.link(self.opening_collection) def get_name(self, element): - return '{}/{}'.format(element.is_a(), element.Name) + return "{}/{}".format(element.is_a(), element.Name) def purge_diff(self): if not self.diff: return objects_to_purge = [] for obj in bpy.data.objects: - if 'GlobalId' not in obj.BIMObjectProperties.attributes: + if "GlobalId" not in obj.BIMObjectProperties.attributes: continue - global_id = obj.BIMObjectProperties.attributes['GlobalId'].string_value - if global_id in self.diff['deleted'] \ - or global_id in self.diff['changed'].keys(): + global_id = obj.BIMObjectProperties.attributes["GlobalId"].string_value + if global_id in self.diff["deleted"] or global_id in self.diff["changed"].keys(): objects_to_purge.append(obj) - bpy.ops.object.delete({'selected_objects': objects_to_purge}) + bpy.ops.object.delete({"selected_objects": objects_to_purge}) def create_product_legacy(self, element): - if self.diff \ - and element.GlobalId not in self.diff['added'] \ - and element.GlobalId not in self.diff['changed'].keys(): + if ( + self.diff + and element.GlobalId not in self.diff["added"] + and element.GlobalId not in self.diff["changed"].keys() + ): return - self.ifc_import_settings.logger.info('Creating object %s', element) + self.ifc_import_settings.logger.info("Creating object %s", element) self.time = time.time() - if element.is_a('IfcOpeningElement'): + if element.is_a("IfcOpeningElement"): return try: representation_id = self.get_representation_id(element) - mesh_name = 'mesh-{}'.format(representation_id) + mesh_name = "mesh-{}".format(representation_id) mesh = self.meshes.get(mesh_name) - if mesh is None \ - or representation_id is None: + if mesh is None or representation_id is None: shape = ifcopenshell.geom.create_shape(self.settings, element) - self.ifc_import_settings.logger.info('Shape was generated in %.2f', time.time() - self.time) + self.ifc_import_settings.logger.info("Shape was generated in %.2f", time.time() - self.time) self.time = time.time() mesh = self.create_mesh(element, shape) self.meshes[mesh_name] = mesh self.mesh_shapes[mesh_name] = shape else: - self.ifc_import_settings.logger.info('Mesh reused.') + self.ifc_import_settings.logger.info("Mesh reused.") except: - self.ifc_import_settings.logger.error('Failed to generate shape for %s', element) + self.ifc_import_settings.logger.error("Failed to generate shape for %s", element) return obj = bpy.data.objects.new(self.get_name(element), mesh) @@ -1731,18 +1767,17 @@ class IfcImporter(): def add_element_document_relations(self, element, obj): for association in element.HasAssociations: - if association.is_a('IfcRelAssociatesDocument'): + if association.is_a("IfcRelAssociatesDocument"): reference = obj.BIMObjectProperties.document_references.add() data_map = { - 'name': 'Identification', - 'human_name': 'Name', - 'description': 'Description', - 'location': 'Location' + "name": "Identification", + "human_name": "Name", + "description": "Description", + "location": "Location", } attributes = {} for key, value in data_map.items(): - if hasattr(association.RelatingDocument, value) \ - and getattr(association.RelatingDocument, value): + if hasattr(association.RelatingDocument, value) and getattr(association.RelatingDocument, value): setattr(reference, key, getattr(association.RelatingDocument, value)) def relate_openings(self): @@ -1751,8 +1786,8 @@ class IfcImporter(): if building_element_global_id not in self.added_data: continue building_element = self.added_data[building_element_global_id] - modifier = building_element.modifiers.new('IfcOpeningElement', 'BOOLEAN') - modifier.operation = 'DIFFERENCE' + modifier = building_element.modifiers.new("IfcOpeningElement", "BOOLEAN") + modifier.operation = "DIFFERENCE" modifier.object = opening def place_objects_in_spatial_tree(self): @@ -1760,36 +1795,39 @@ class IfcImporter(): self.place_object_in_spatial_tree(self.file.by_guid(global_id), obj) def place_object_in_spatial_tree(self, element, obj): - if hasattr(element, 'ContainedInStructure') \ - and element.ContainedInStructure \ - and element.ContainedInStructure[0].RelatingStructure: + if ( + hasattr(element, "ContainedInStructure") + and element.ContainedInStructure + and element.ContainedInStructure[0].RelatingStructure + ): container = element.ContainedInStructure[0].RelatingStructure - if container.is_a('IfcSpace'): + if container.is_a("IfcSpace"): if self.ifc_import_settings.should_import_spaces and container.GlobalId in self.added_data: obj.BIMObjectProperties.relating_structure = self.added_data[container.GlobalId] return self.place_object_in_spatial_tree(container, obj) - elif element.is_a('IfcGrid'): + elif element.is_a("IfcGrid"): grid_collection = bpy.data.collections.get(self.get_name(element)) - self.spatial_structure_elements[container.GlobalId]['blender'].children.link(grid_collection) + self.spatial_structure_elements[container.GlobalId]["blender"].children.link(grid_collection) grid_collection.objects.link(obj) else: - self.spatial_structure_elements[container.GlobalId]['blender'].objects.link(obj) - elif hasattr(element, 'Decomposes') \ - and element.Decomposes: + self.spatial_structure_elements[container.GlobalId]["blender"].objects.link(obj) + elif hasattr(element, "Decomposes") and element.Decomposes: collection = None - if element.Decomposes[0].RelatingObject.is_a('IfcProject'): - collection = self.project['blender'] - elif element.Decomposes[0].RelatingObject.is_a('IfcSpatialStructureElement'): - if element.is_a('IfcSpatialStructureElement') and not element.is_a('IfcSpace'): + if element.Decomposes[0].RelatingObject.is_a("IfcProject"): + collection = self.project["blender"] + elif element.Decomposes[0].RelatingObject.is_a("IfcSpatialStructureElement"): + if element.is_a("IfcSpatialStructureElement") and not element.is_a("IfcSpace"): global_id = element.GlobalId else: global_id = element.Decomposes[0].RelatingObject.GlobalId if global_id in self.spatial_structure_elements: - if element.is_a('IfcSpatialStructureElement') \ - and not element.is_a('IfcSpace') \ - and 'blender_obj' in self.spatial_structure_elements[global_id]: - bpy.data.objects.remove(self.spatial_structure_elements[global_id]['blender_obj']) - collection = self.spatial_structure_elements[global_id]['blender'] + if ( + element.is_a("IfcSpatialStructureElement") + and not element.is_a("IfcSpace") + and "blender_obj" in self.spatial_structure_elements[global_id] + ): + bpy.data.objects.remove(self.spatial_structure_elements[global_id]["blender_obj"]) + collection = self.spatial_structure_elements[global_id]["blender"] # This may occur if we are nesting an IfcSpace (which is special # since it does not have a collection within an IfcSpace if not collection: @@ -1801,27 +1839,25 @@ class IfcImporter(): if collection: collection.objects.link(obj) else: - self.ifc_import_settings.logger.error('An element could not be placed in the spatial tree %s', element) - elif hasattr(element, 'HasFillings') \ - and element.HasFillings: + self.ifc_import_settings.logger.error("An element could not be placed in the spatial tree %s", element) + elif hasattr(element, "HasFillings") and element.HasFillings: self.opening_collection.objects.link(obj) else: - self.ifc_import_settings.logger.warning('Warning: this object is outside the spatial hierarchy %s', element) + self.ifc_import_settings.logger.warning("Warning: this object is outside the spatial hierarchy %s", element) bpy.context.scene.collection.objects.link(obj) def add_element_attributes(self, element, obj): attributes = element.get_info() for key, value in attributes.items(): - if value is None or isinstance(value, ifcopenshell.entity_instance) \ - or key == 'id' or key == 'type': + if value is None or isinstance(value, ifcopenshell.entity_instance) or key == "id" or key == "type": continue attribute = obj.BIMObjectProperties.attributes.add() attribute.name = key - attribute.data_type = 'string' + attribute.data_type = "string" attribute.string_value = str(self.cast_edge_case_attribute(element.is_a(), key, value)) def cast_edge_case_attribute(self, ifc_class, key, value): - if key == 'RefLatitude' or key == 'RefLongitude': + if key == "RefLatitude" or key == "RefLongitude": return ifcopenshell.util.geolocation.dms2dd(*value) return value @@ -1829,25 +1865,28 @@ class IfcImporter(): if not element.HasAssociations: return for association in element.HasAssociations: - if not association.is_a('IfcRelAssociatesClassification'): + if not association.is_a("IfcRelAssociatesClassification"): continue data = association.RelatingClassification reference = obj.BIMObjectProperties.classifications.add() data_map = { - 'name': 'Identification', 'location': 'Location', 'human_name': 'Name', - 'description': 'Description', 'sort': 'Sort' + "name": "Identification", + "location": "Location", + "human_name": "Name", + "description": "Description", + "sort": "Sort", } - if self.file.schema == 'IFC2X3': - data_map['name'] = 'ItemReference' + if self.file.schema == "IFC2X3": + data_map["name"] = "ItemReference" for key, value in data_map.items(): if hasattr(data, value) and getattr(data, value): setattr(reference, key, getattr(data, value)) - if hasattr(data, 'ReferencedSource') and data.ReferencedSource: + if hasattr(data, "ReferencedSource") and data.ReferencedSource: reference.referenced_source = self.get_referenced_source_name(data.ReferencedSource) def get_referenced_source_name(self, element): - if not hasattr(element, 'ReferencedSource') or not element.ReferencedSource: - if element.is_a('IfcClassification'): + if not hasattr(element, "ReferencedSource") or not element.ReferencedSource: + if element.is_a("IfcClassification"): return element.Name else: return element.Identification @@ -1868,7 +1907,8 @@ class IfcImporter(): # from whatever shared mesh we are using, as it is not necessarily the # same as the current mesh. shared_shape_transformation = self.get_representation_cartesian_transformation( - self.file.by_id(self.mesh_shapes[mesh_name].product.id())) + self.file.by_id(self.mesh_shapes[mesh_name].product.id()) + ) if shared_shape_transformation: shared_transform = self.get_cartesiantransformationoperator(shared_shape_transformation) shared_transform.invert() @@ -1891,17 +1931,20 @@ class IfcImporter(): matrix = mathutils.Matrix() results = [] for representation in representations: - if representation.RepresentationIdentifier == 'Body' \ - and representation.RepresentationType == 'MappedRepresentation': + if ( + representation.RepresentationIdentifier == "Body" + and representation.RepresentationType == "MappedRepresentation" + ): for item in representation.Items: # TODO: Confirm if this transformation is right transform = self.get_axis2placement(item.MappingSource.MappingOrigin) if item.MappingTarget: transform = transform @ self.get_cartesiantransformationoperator(item.MappingTarget) - results.extend(self.get_body_representations([item.MappingSource.MappedRepresentation], - transform @ matrix)) - elif representation.RepresentationIdentifier == 'Body': - results.append({ 'raw': representation, 'matrix': self.scale_matrix(matrix) }) + results.extend( + self.get_body_representations([item.MappingSource.MappedRepresentation], transform @ matrix) + ) + elif representation.RepresentationIdentifier == "Body": + results.append({"raw": representation, "matrix": self.scale_matrix(matrix)}) return results def get_representation_of_context(self, representations, context): @@ -1919,40 +1962,48 @@ class IfcImporter(): if not element.Representation: return None for representation in element.Representation.Representations: - if not representation.is_a('IfcShapeRepresentation'): + if not representation.is_a("IfcShapeRepresentation"): continue - if representation.RepresentationIdentifier == 'Body' \ - and representation.RepresentationType != 'MappedRepresentation': + if ( + representation.RepresentationIdentifier == "Body" + and representation.RepresentationType != "MappedRepresentation" + ): return representation.id() - elif representation.RepresentationIdentifier == 'Body': + elif representation.RepresentationIdentifier == "Body": return representation.Items[0].MappingSource.MappedRepresentation.id() def get_representation_cartesian_transformation(self, element): if not element.Representation: return None for representation in element.Representation.Representations: - if not representation.is_a('IfcShapeRepresentation'): + if not representation.is_a("IfcShapeRepresentation"): continue - if representation.RepresentationIdentifier == 'Body' \ - and representation.RepresentationType == 'MappedRepresentation': + if ( + representation.RepresentationIdentifier == "Body" + and representation.RepresentationType == "MappedRepresentation" + ): return representation.Items[0].MappingTarget def get_geometry_type(self, element): tree = [] - if hasattr(element, 'Representation'): + if hasattr(element, "Representation"): tree = self.file.traverse(element.Representation) - elif hasattr(element, 'RepresentationMaps'): + elif hasattr(element, "RepresentationMaps"): for representation_map in element.RepresentationMaps: tree.extend(self.file.traverse(representation_map)) - representations = [e for e in tree if e.is_a('IfcRepresentation') \ - and e.RepresentationIdentifier == 'Body' \ - and e.RepresentationType != 'MappedRepresentation'] + representations = [ + e + for e in tree + if e.is_a("IfcRepresentation") + and e.RepresentationIdentifier == "Body" + and e.RepresentationType != "MappedRepresentation" + ] for representation in representations: return representation.Items[0].is_a() def create_mesh(self, element, shape, is_curve=False): try: - if hasattr(shape, 'geometry'): + if hasattr(shape, "geometry"): geometry = shape.geometry else: geometry = shape @@ -1974,23 +2025,23 @@ class IfcImporter(): if self.ifc_import_settings.should_offset_model: # Potentially, there is a smarter way to do this. See #1047 v_index = cycle((0, 1, 2)) - verts = [v+self.ifc_import_settings.model_offset_coordinates[next(v_index)] for v in geometry.verts] - mesh.vertices.foreach_set('co', verts) + verts = [ + v + self.ifc_import_settings.model_offset_coordinates[next(v_index)] for v in geometry.verts + ] + mesh.vertices.foreach_set("co", verts) else: - mesh.vertices.foreach_set('co', geometry.verts) + mesh.vertices.foreach_set("co", geometry.verts) mesh.loops.add(num_vertex_indices) - mesh.loops.foreach_set('vertex_index', geometry.faces) + mesh.loops.foreach_set("vertex_index", geometry.faces) mesh.polygons.add(num_loops) - mesh.polygons.foreach_set('loop_start', loop_start) - mesh.polygons.foreach_set('loop_total', loop_total) + mesh.polygons.foreach_set("loop_start", loop_start) + mesh.polygons.foreach_set("loop_total", loop_total) mesh.update() else: e = geometry.edges v = geometry.verts - vertices = [[v[i], v[i + 1], v[i + 2]] - for i in range(0, len(v), 3)] - edges = [[e[i], e[i + 1]] - for i in range(0, len(e), 2)] + vertices = [[v[i], v[i + 1], v[i + 2]] for i in range(0, len(v), 3)] + edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)] mesh.from_pydata(vertices, edges, []) ios_materials = [] @@ -1999,13 +2050,14 @@ class IfcImporter(): ios_materials.append(mat.original_name()) else: ios_materials.append(mat.name) - mesh['ios_materials'] = ios_materials - mesh['ios_material_ids'] = geometry.material_ids + mesh["ios_materials"] = ios_materials + mesh["ios_material_ids"] = geometry.material_ids self.store_representation_source(mesh, element, shape) return mesh except: - self.ifc_import_settings.logger.error('Could not create mesh for %s', element) + self.ifc_import_settings.logger.error("Could not create mesh for %s", element) import traceback + print(traceback.format_exc()) def store_representation_source(self, mesh, element, shape): @@ -2013,28 +2065,26 @@ class IfcImporter(): mesh.BIMMeshProperties.geometry_type = str(self.get_geometry_type(element)) if not self.ifc_import_settings.should_roundtrip_native: return - if element.is_a('IfcRepresentation'): + if element.is_a("IfcRepresentation"): representation = element else: representation = self.get_representation_of_context(element.Representation.Representations, shape.context) mesh.BIMMeshProperties.ifc_definition_id = int(representation.id()) def create_curve(self, geometry): - curve = bpy.data.curves.new(geometry.id, type='CURVE') - curve.dimensions = '3D' + curve = bpy.data.curves.new(geometry.id, type="CURVE") + curve.dimensions = "3D" curve.resolution_u = 2 - polyline = curve.splines.new('POLY') + polyline = curve.splines.new("POLY") e = geometry.edges v = geometry.verts - vertices = [[v[i], v[i + 1], v[i + 2], 1] - for i in range(0, len(v), 3)] - edges = [[e[i], e[i + 1]] - for i in range(0, len(e), 2)] + vertices = [[v[i], v[i + 1], v[i + 2], 1] for i in range(0, len(v), 3)] + edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)] v2 = None for edge in edges: v1 = vertices[edge[0]] if v1 != v2: - polyline = curve.splines.new('POLY') + polyline = curve.splines.new("POLY") polyline.points[-1].co = v1 v2 = vertices[edge[1]] polyline.points.add(1) @@ -2042,22 +2092,19 @@ class IfcImporter(): return curve def shape_to_mesh(self, shape): - if hasattr(shape, 'geometry'): + if hasattr(shape, "geometry"): geometry = shape.geometry else: geometry = shape f = geometry.faces e = geometry.edges v = geometry.verts - vertices = [[v[i], v[i + 1], v[i + 2]] - for i in range(0, len(v), 3)] - faces = [[f[i], f[i + 1], f[i + 2]] - for i in range(0, len(f), 3)] + vertices = [[v[i], v[i + 1], v[i + 2]] for i in range(0, len(v), 3)] + faces = [[f[i], f[i + 1], f[i + 2]] for i in range(0, len(f), 3)] if faces: edges = [] else: - edges = [[e[i], e[i + 1]] - for i in range(0, len(e), 2)] + edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)] return (vertices, edges, faces) def bmesh_from_pydata(self, verts=[], edges=[], faces=[]): @@ -2090,24 +2137,24 @@ class IfcImporter(): return r def get_axis2placement(self, plc): - if plc.is_a('IfcAxis2Placement3D'): - z = mathutils.Vector(plc.Axis.DirectionRatios if plc.Axis else (0,0,1)) - x = mathutils.Vector(plc.RefDirection.DirectionRatios if plc.RefDirection else (1,0,0)) + if plc.is_a("IfcAxis2Placement3D"): + z = mathutils.Vector(plc.Axis.DirectionRatios if plc.Axis else (0, 0, 1)) + x = mathutils.Vector(plc.RefDirection.DirectionRatios if plc.RefDirection else (1, 0, 0)) o = plc.Location.Coordinates else: - z = mathutils.Vector((0,0,1)) + z = mathutils.Vector((0, 0, 1)) if plc.RefDirection: x = mathutils.Vector(list(plc.RefDirection.DirectionRatios) + [0]) else: - x = mathutils.Vector((1,0,0)) + x = mathutils.Vector((1, 0, 0)) o = list(plc.Location.Coordinates) + [0] - return self.a2p(o,z,x) + return self.a2p(o, z, x) def get_cartesiantransformationoperator(self, plc): - x = mathutils.Vector(plc.Axis1.DirectionRatios if plc.Axis1 else (1,0,0)) - z = x.cross(mathutils.Vector(plc.Axis2.DirectionRatios if plc.Axis2 else (0,1,0))) + x = mathutils.Vector(plc.Axis1.DirectionRatios if plc.Axis1 else (1, 0, 0)) + z = x.cross(mathutils.Vector(plc.Axis2.DirectionRatios if plc.Axis2 else (0, 1, 0))) o = plc.LocalOrigin.Coordinates - return self.a2p(o,z,x) + return self.a2p(o, z, x) def get_local_placement(self, plc): if plc is None: @@ -2118,6 +2165,7 @@ class IfcImporter(): parent = self.get_local_placement(plc.PlacementRelTo) return parent @ self.get_axis2placement(plc.RelativePlacement) + class IfcImportSettings: def __init__(self): self.logger = None @@ -2174,7 +2222,11 @@ class IfcImportSettings: settings.should_clean_mesh = scene_bim.import_should_clean_mesh settings.should_allow_non_element_aggregates = scene_bim.import_should_allow_non_element_aggregates settings.should_offset_model = scene_bim.import_should_offset_model - settings.model_offset_coordinates = [float(o) for o in scene_bim.import_model_offset_coordinates.split(',')] if scene_bim.import_model_offset_coordinates else (0, 0, 0) + settings.model_offset_coordinates = ( + [float(o) for o in scene_bim.import_model_offset_coordinates.split(",")] + if scene_bim.import_model_offset_coordinates + else (0, 0, 0) + ) settings.deflection_tolerance = scene_bim.import_deflection_tolerance settings.angular_tolerance = scene_bim.import_angular_tolerance return settings diff --git a/src/ifcblenderexport/blenderbim/bim/module/covetool/api.py b/src/ifcblenderexport/blenderbim/bim/module/covetool/api.py index 3367430268..84ed1ca87a 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/covetool/api.py +++ b/src/ifcblenderexport/blenderbim/bim/module/covetool/api.py @@ -1,15 +1,16 @@ import json import requests + class Api: def login(self, username, password): data = { - 'username': username, - 'password': password, + "username": username, + "password": password, } - response_data = self.post_request('get-token', data, False) - if 'token' in response_data: - self.token = str(response_data['token']) + response_data = self.post_request("get-token", data, False) + if "token" in response_data: + self.token = str(response_data["token"]) return self.token def post_request(self, path, data, use_token=True): @@ -25,17 +26,16 @@ class Api: return self._handle_response(response) def _api_url(self, path): - return 'https://app.covetool.com/api/' + path + '/' + return "https://app.covetool.com/api/" + path + "/" def _headers(self, use_token): headers = {} if use_token: - headers['Authorization'] = 'Token ' + self.token + headers["Authorization"] = "Token " + self.token return headers def _handle_response(self, response): if response.ok: return response.json() else: - return {'result': 'error'} - + return {"result": "error"} diff --git a/src/ifcblenderexport/blenderbim/bim/module/covetool/operator.py b/src/ifcblenderexport/blenderbim/bim/module/covetool/operator.py index 8f0ab7a371..f06feb8230 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/covetool/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/module/covetool/operator.py @@ -6,98 +6,106 @@ from .api import Api api = Api() + class Login(bpy.types.Operator): - bl_idname = 'bim.covetool_login' - bl_label = 'Login to cove.tool' + bl_idname = "bim.covetool_login" + bl_label = "Login to cove.tool" def execute(self, context): - token = api.login( - bpy.context.scene.CoveToolProperties.username, - bpy.context.scene.CoveToolProperties.password) + token = api.login(bpy.context.scene.CoveToolProperties.username, bpy.context.scene.CoveToolProperties.password) if token: bpy.context.scene.CoveToolProperties.token = token - projects = api.get_request('projects') + projects = api.get_request("projects") for project in projects: new_project = bpy.context.scene.CoveToolProperties.projects.add() - new_project.name = project['name'] - new_project.run_set = project['run_set'][0] - new_project.url = project['url'] + new_project.name = project["name"] + new_project.run_set = project["run_set"][0] + new_project.url = project["url"] else: - self.report({'ERROR'}, 'Login failed :(') - return {'FINISHED'} + self.report({"ERROR"}, "Login failed :(") + return {"FINISHED"} class RunSimpleAnalysis(bpy.types.Operator): - bl_idname = 'bim.covetool_run_simple_analysis' - bl_label = 'Run Simple Analysis' + bl_idname = "bim.covetool_run_simple_analysis" + bl_label = "Run Simple Analysis" def execute(self, context): simple_analysis = bpy.context.scene.CoveToolProperties.simple_analysis data = { - 'run': bpy.context.scene.CoveToolProperties.projects[bpy.context.scene.CoveToolProperties.active_project_index].run_set, - 'si_units': simple_analysis.si_units, - 'building_height': simple_analysis.building_height, - 'roof_area': simple_analysis.roof_area, - 'floor_area': simple_analysis.floor_area, - 'skylight_area': simple_analysis.skylight_area, - 'wall_area_e': simple_analysis.wall_area_e, - 'wall_area_ne': simple_analysis.wall_area_ne, - 'wall_area_n': simple_analysis.wall_area_n, - 'wall_area_nw': simple_analysis.wall_area_nw, - 'wall_area_w': simple_analysis.wall_area_w, - 'wall_area_sw': simple_analysis.wall_area_sw, - 'wall_area_s': simple_analysis.wall_area_s, - 'wall_area_se': simple_analysis.wall_area_se, - 'window_area_e': simple_analysis.window_area_e, - 'window_area_ne': simple_analysis.window_area_ne, - 'window_area_n': simple_analysis.window_area_n, - 'window_area_nw': simple_analysis.window_area_nw, - 'window_area_w': simple_analysis.window_area_w, - 'window_area_sw': simple_analysis.window_area_sw, - 'window_area_s': simple_analysis.window_area_s, - 'window_area_se': simple_analysis.window_area_se, + "run": bpy.context.scene.CoveToolProperties.projects[ + bpy.context.scene.CoveToolProperties.active_project_index + ].run_set, + "si_units": simple_analysis.si_units, + "building_height": simple_analysis.building_height, + "roof_area": simple_analysis.roof_area, + "floor_area": simple_analysis.floor_area, + "skylight_area": simple_analysis.skylight_area, + "wall_area_e": simple_analysis.wall_area_e, + "wall_area_ne": simple_analysis.wall_area_ne, + "wall_area_n": simple_analysis.wall_area_n, + "wall_area_nw": simple_analysis.wall_area_nw, + "wall_area_w": simple_analysis.wall_area_w, + "wall_area_sw": simple_analysis.wall_area_sw, + "wall_area_s": simple_analysis.wall_area_s, + "wall_area_se": simple_analysis.wall_area_se, + "window_area_e": simple_analysis.window_area_e, + "window_area_ne": simple_analysis.window_area_ne, + "window_area_n": simple_analysis.window_area_n, + "window_area_nw": simple_analysis.window_area_nw, + "window_area_w": simple_analysis.window_area_w, + "window_area_sw": simple_analysis.window_area_sw, + "window_area_s": simple_analysis.window_area_s, + "window_area_se": simple_analysis.window_area_se, } - result = api.post_request('run-values', data) - covetool_results = bpy.data.texts.new('cove.tool Results') + result = api.post_request("run-values", data) + covetool_results = bpy.data.texts.new("cove.tool Results") covetool_results.write(json.dumps(result, indent=4)) - return {'FINISHED'} + return {"FINISHED"} class RunAnalysis(bpy.types.Operator): - bl_idname = 'bim.covetool_run_analysis' - bl_label = 'Run Analysis' + bl_idname = "bim.covetool_run_analysis" + bl_label = "Run Analysis" def execute(self, context): self.inputs = { - 'floors': [], - 'walls': [], - 'interior_walls': [], - 'windows': [], - 'skylights': [], - 'roofs': [], - 'shading_devices': [] + "floors": [], + "walls": [], + "interior_walls": [], + "windows": [], + "skylights": [], + "roofs": [], + "shading_devices": [], } self.parse_objects() data = { - 'run': bpy.context.scene.CoveToolProperties.projects[bpy.context.scene.CoveToolProperties.active_project_index].run_set, - 'source': 'BlenderBIM', - 'rotation_angle': self.get_rotation_angle(), - **self.inputs + "run": bpy.context.scene.CoveToolProperties.projects[ + bpy.context.scene.CoveToolProperties.active_project_index + ].run_set, + "source": "BlenderBIM", + "rotation_angle": self.get_rotation_angle(), + **self.inputs, } - result = api.post_request('run-values', data) - covetool_results = bpy.data.texts.new('cove.tool Results') + result = api.post_request("run-values", data) + covetool_results = bpy.data.texts.new("cove.tool Results") covetool_results.write(json.dumps(result, indent=4)) - return {'FINISHED'} + return {"FINISHED"} def get_rotation_angle(self): - if not bpy.context.scene.BIMProperties.has_georeferencing \ - or not bpy.context.scene.MapConversion.x_axis_abscissa \ - or not bpy.context.scene.MapConversion.x_axis_ordinate: + if ( + not bpy.context.scene.BIMProperties.has_georeferencing + or not bpy.context.scene.MapConversion.x_axis_abscissa + or not bpy.context.scene.MapConversion.x_axis_ordinate + ): return 0 - rotation = -1 * degrees(atan2( - float(bpy.context.scene.MapConversion.x_axis_ordinate), - float(bpy.context.scene.MapConversion.x_axis_abscissa))) + rotation = -1 * degrees( + atan2( + float(bpy.context.scene.MapConversion.x_axis_ordinate), + float(bpy.context.scene.MapConversion.x_axis_abscissa), + ) + ) if rotation < 0: rotation = 360 - rotation return rotation @@ -108,86 +116,82 @@ class RunAnalysis(bpy.types.Operator): if not covetool_category: continue if not self.has_triangulate_modifier(obj): - obj.modifiers.new(name='Triangulate', type='TRIANGULATE') + obj.modifiers.new(name="Triangulate", type="TRIANGULATE") mesh = obj.evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh() meshes = {} for polygon in mesh.polygons: - normal = '{}|{}|{}'.format( - round(polygon.normal[0], 2), - round(polygon.normal[1], 2), - round(polygon.normal[2], 2)) - meshes.setdefault(normal, { - 'Mesh': { - 'vertex_indices': {}, - 'Vertices': [], - 'Triangles': [] + normal = "{}|{}|{}".format( + round(polygon.normal[0], 2), round(polygon.normal[1], 2), round(polygon.normal[2], 2) + ) + meshes.setdefault( + normal, + { + "Mesh": {"vertex_indices": {}, "Vertices": [], "Triangles": []}, + "Center": {}, + "Normal": { + "X": round(polygon.normal[0], 2), + "Y": round(polygon.normal[1], 2), + "Z": round(polygon.normal[2], 2), + }, }, - 'Center': {}, - 'Normal': { - 'X': round(polygon.normal[0], 2), - 'Y': round(polygon.normal[1], 2), - 'Z': round(polygon.normal[2], 2) - } - }) + ) for vertex in polygon.vertices: global_vertex = obj.matrix_world @ mesh.vertices[vertex].co - meshes[normal]['Mesh']['vertex_indices'][vertex] = { - 'X': global_vertex[0] * 3.28084, # Covetools require feet - 'Y': global_vertex[1] * 3.28084, - 'Z': global_vertex[2] * 3.28084 + meshes[normal]["Mesh"]["vertex_indices"][vertex] = { + "X": global_vertex[0] * 3.28084, # Covetools require feet + "Y": global_vertex[1] * 3.28084, + "Z": global_vertex[2] * 3.28084, } - meshes[normal]['Mesh']['Triangles'].append([ - polygon.vertices[0], - polygon.vertices[1], - polygon.vertices[2] - ]) + meshes[normal]["Mesh"]["Triangles"].append( + [polygon.vertices[0], polygon.vertices[1], polygon.vertices[2]] + ) for normal, mesh in meshes.items(): - sorted_keys = sorted(mesh['Mesh']['vertex_indices']) - mesh['Mesh']['Vertices'] = [mesh['Mesh']['vertex_indices'][k] for k in sorted_keys] - for triangle in mesh['Mesh']['Triangles']: + sorted_keys = sorted(mesh["Mesh"]["vertex_indices"]) + mesh["Mesh"]["Vertices"] = [mesh["Mesh"]["vertex_indices"][k] for k in sorted_keys] + for triangle in mesh["Mesh"]["Triangles"]: triangle[0] = sorted_keys.index(triangle[0]) triangle[1] = sorted_keys.index(triangle[1]) triangle[2] = sorted_keys.index(triangle[2]) - mesh['Center'] = mesh['Mesh']['Vertices'][0] # Not correct, but just for now - del mesh['Mesh']['vertex_indices'] + mesh["Center"] = mesh["Mesh"]["Vertices"][0] # Not correct, but just for now + del mesh["Mesh"]["vertex_indices"] self.inputs[covetool_category].extend(meshes.values()) def has_triangulate_modifier(self, obj): for modifier in obj.modifiers: - if modifier.type == 'TRIANGULATE': + if modifier.type == "TRIANGULATE": return True def get_covetool_category(self, obj): - if not hasattr(obj, 'data') or not isinstance(obj.data, bpy.types.Mesh): + if not hasattr(obj, "data") or not isinstance(obj.data, bpy.types.Mesh): return - if 'IfcSlab' in obj.name: - return 'floors' - elif 'IfcRoof' in obj.name: - return 'roofs' - elif 'IfcWall' in obj.name: + if "IfcSlab" in obj.name: + return "floors" + elif "IfcRoof" in obj.name: + return "roofs" + elif "IfcWall" in obj.name: if self.is_wall_internal(obj): - return 'interior_walls' - return 'walls' - elif 'IfcWindow' in obj.name: + return "interior_walls" + return "walls" + elif "IfcWindow" in obj.name: if self.is_window_skylight(obj): - return 'skylights' - return 'windows' - elif 'IfcShadingDevice' in obj.name: - return 'shading_devices' + return "skylights" + return "windows" + elif "IfcShadingDevice" in obj.name: + return "shading_devices" def is_wall_internal(self, obj): - pset_wallcommon = obj.BIMObjectProperties.psets.get('Pset_WallCommon') + pset_wallcommon = obj.BIMObjectProperties.psets.get("Pset_WallCommon") if pset_wallcommon: - is_external = pset_wallcommon.properties.get('IsExternal') + is_external = pset_wallcommon.properties.get("IsExternal") if is_external: - if is_external.string_value == 'True': + if is_external.string_value == "True": return False else: return True - predefined_type = obj.BIMObjectProperties.attributes.get('PredefinedType') - if predefined_type and predefined_type.string_value in ['MOVABLE', 'PARTITIONING', 'PLUMBINGWALL']: + predefined_type = obj.BIMObjectProperties.attributes.get("PredefinedType") + if predefined_type and predefined_type.string_value in ["MOVABLE", "PARTITIONING", "PLUMBINGWALL"]: return True def is_window_skylight(self, obj): - predefined_type = obj.BIMObjectProperties.attributes.get('PredefinedType') - return predefined_type and predefined_type.string_value == 'SKYLIGHT' + predefined_type = obj.BIMObjectProperties.attributes.get("PredefinedType") + return predefined_type and predefined_type.string_value == "SKYLIGHT" diff --git a/src/ifcblenderexport/blenderbim/bim/module/covetool/prop.py b/src/ifcblenderexport/blenderbim/bim/module/covetool/prop.py index c8cfee244e..122d6b1cb1 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/covetool/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/module/covetool/prop.py @@ -3,39 +3,39 @@ import bpy.types class CoveToolProject(bpy.types.PropertyGroup): - name: bpy.props.StringProperty(name='Name') - run_set: bpy.props.StringProperty(name='Run Set') - url: bpy.props.StringProperty(name='URL') + name: bpy.props.StringProperty(name="Name") + run_set: bpy.props.StringProperty(name="Run Set") + url: bpy.props.StringProperty(name="URL") class CoveToolSimpleAnalysis(bpy.types.PropertyGroup): - si_units: bpy.props.BoolProperty(name='SI Units') - building_height: bpy.props.StringProperty(name='Building Height') - roof_area: bpy.props.StringProperty(name='Roof Area') - floor_area: bpy.props.StringProperty(name='Floor Area') - skylight_area: bpy.props.StringProperty(name='Skylight Area') - wall_area_e: bpy.props.StringProperty(name='Wall Area E') - wall_area_ne: bpy.props.StringProperty(name='Wall Area NE') - wall_area_n: bpy.props.StringProperty(name='Wall Area N') - wall_area_nw: bpy.props.StringProperty(name='Wall Area NW') - wall_area_w: bpy.props.StringProperty(name='Wall Area W') - wall_area_sw: bpy.props.StringProperty(name='Wall Area SW') - wall_area_s: bpy.props.StringProperty(name='Wall Area S') - wall_area_se: bpy.props.StringProperty(name='Wall Area SE') - window_area_e: bpy.props.StringProperty(name='Window Area E') - window_area_ne: bpy.props.StringProperty(name='Window Area NE') - window_area_n: bpy.props.StringProperty(name='Window Area N') - window_area_nw: bpy.props.StringProperty(name='Window Area NW') - window_area_w: bpy.props.StringProperty(name='Window Area W') - window_area_sw: bpy.props.StringProperty(name='Window Area SW') - window_area_s: bpy.props.StringProperty(name='Window Area S') - window_area_se: bpy.props.StringProperty(name='Window Area SE') + si_units: bpy.props.BoolProperty(name="SI Units") + building_height: bpy.props.StringProperty(name="Building Height") + roof_area: bpy.props.StringProperty(name="Roof Area") + floor_area: bpy.props.StringProperty(name="Floor Area") + skylight_area: bpy.props.StringProperty(name="Skylight Area") + wall_area_e: bpy.props.StringProperty(name="Wall Area E") + wall_area_ne: bpy.props.StringProperty(name="Wall Area NE") + wall_area_n: bpy.props.StringProperty(name="Wall Area N") + wall_area_nw: bpy.props.StringProperty(name="Wall Area NW") + wall_area_w: bpy.props.StringProperty(name="Wall Area W") + wall_area_sw: bpy.props.StringProperty(name="Wall Area SW") + wall_area_s: bpy.props.StringProperty(name="Wall Area S") + wall_area_se: bpy.props.StringProperty(name="Wall Area SE") + window_area_e: bpy.props.StringProperty(name="Window Area E") + window_area_ne: bpy.props.StringProperty(name="Window Area NE") + window_area_n: bpy.props.StringProperty(name="Window Area N") + window_area_nw: bpy.props.StringProperty(name="Window Area NW") + window_area_w: bpy.props.StringProperty(name="Window Area W") + window_area_sw: bpy.props.StringProperty(name="Window Area SW") + window_area_s: bpy.props.StringProperty(name="Window Area S") + window_area_se: bpy.props.StringProperty(name="Window Area SE") class CoveToolProperties(bpy.types.PropertyGroup): - username: bpy.props.StringProperty(name='Username') - password: bpy.props.StringProperty(name='Password') - token: bpy.props.StringProperty(name='Token') - projects: bpy.props.CollectionProperty(name='Projects', type=CoveToolProject) - active_project_index: bpy.props.IntProperty(name='Active Project Index') + username: bpy.props.StringProperty(name="Username") + password: bpy.props.StringProperty(name="Password") + token: bpy.props.StringProperty(name="Token") + projects: bpy.props.CollectionProperty(name="Projects", type=CoveToolProject) + active_project_index: bpy.props.IntProperty(name="Active Project Index") simple_analysis: bpy.props.PointerProperty(type=CoveToolSimpleAnalysis) diff --git a/src/ifcblenderexport/blenderbim/bim/module/covetool/ui.py b/src/ifcblenderexport/blenderbim/bim/module/covetool/ui.py index df9e23c42d..70b35ef6c0 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/covetool/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/module/covetool/ui.py @@ -1,11 +1,12 @@ import bpy.types + class BIM_PT_covetool(bpy.types.Panel): bl_label = "cove.tool" bl_idname = "BIM_PT_covetool" - bl_options = {'DEFAULT_CLOSED'} - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" bl_context = "scene" def draw(self, context): @@ -17,36 +18,53 @@ class BIM_PT_covetool(bpy.types.Panel): if not props.token: row = layout.row() - row.prop(props, 'username') + row.prop(props, "username") row = layout.row() - row.prop(props, 'password') + row.prop(props, "password") row = layout.row() - row.operator('bim.covetool_login') + row.operator("bim.covetool_login") return - layout.template_list('BIM_UL_covetool_projects', '', props, 'projects', props, 'active_project_index') + layout.template_list("BIM_UL_covetool_projects", "", props, "projects", props, "active_project_index") row = layout.row() - row.operator('bim.covetool_run_analysis') + row.operator("bim.covetool_run_analysis") - prop_names = [ 'si_units', 'building_height', 'roof_area', 'floor_area', - 'skylight_area', 'wall_area_e', 'wall_area_ne', 'wall_area_n', - 'wall_area_nw', 'wall_area_w', 'wall_area_sw', 'wall_area_s', - 'wall_area_se', 'window_area_e', 'window_area_ne', 'window_area_n', - 'window_area_nw', 'window_area_w', 'window_area_sw', - 'window_area_s', 'window_area_se'] + prop_names = [ + "si_units", + "building_height", + "roof_area", + "floor_area", + "skylight_area", + "wall_area_e", + "wall_area_ne", + "wall_area_n", + "wall_area_nw", + "wall_area_w", + "wall_area_sw", + "wall_area_s", + "wall_area_se", + "window_area_e", + "window_area_ne", + "window_area_n", + "window_area_nw", + "window_area_w", + "window_area_sw", + "window_area_s", + "window_area_se", + ] for prop_name in prop_names: row = layout.row() row.prop(props.simple_analysis, prop_name) row = layout.row() - row.operator('bim.covetool_run_simple_analysis') + row.operator("bim.covetool_run_simple_analysis") class BIM_UL_covetool_projects(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): ob = data if item: - layout.prop(item, 'name', text='', emboss=False) + layout.prop(item, "name", text="", emboss=False) else: - layout.label(text='', translate=False) + layout.label(text="", translate=False) diff --git a/src/ifcblenderexport/blenderbim/bim/module/model/door.py b/src/ifcblenderexport/blenderbim/bim/module/model/door.py index 20fa79337b..6771c4f50f 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/model/door.py +++ b/src/ifcblenderexport/blenderbim/bim/module/model/door.py @@ -6,42 +6,62 @@ from bpy.props import FloatVectorProperty, FloatProperty, IntProperty from bpy_extras.object_utils import AddObjectHelper, object_data_add from mathutils import Vector + def add_object(self, context): guid = ifcopenshell.guid.new() - leaf_width = self.overall_width-0.045-0.045 + leaf_width = self.overall_width - 0.045 - 0.045 verts = [ # Left lining Vector((0, 0, 0)), Vector((0, self.depth, 0)), - Vector((.04, self.depth, 0)), - Vector((.04, self.depth-.04, 0)), - Vector((.065, self.depth-.04, 0)), - Vector((.065, 0, 0)), + Vector((0.04, self.depth, 0)), + Vector((0.04, self.depth - 0.04, 0)), + Vector((0.065, self.depth - 0.04, 0)), + Vector((0.065, 0, 0)), # Right lining Vector((self.overall_width, 0, 0)), Vector((self.overall_width, self.depth, 0)), - Vector((self.overall_width-.04, self.depth, 0)), - Vector((self.overall_width-.04, self.depth-.04, 0)), - Vector((self.overall_width-.065, self.depth-.04, 0)), - Vector((self.overall_width-.065, 0, 0)), + Vector((self.overall_width - 0.04, self.depth, 0)), + Vector((self.overall_width - 0.04, self.depth - 0.04, 0)), + Vector((self.overall_width - 0.065, self.depth - 0.04, 0)), + Vector((self.overall_width - 0.065, 0, 0)), # Door panel - Vector((.045, self.depth, 0)), - Vector((.045, self.depth+leaf_width, 0)), - Vector((.080, self.depth+leaf_width, 0)), - Vector((.080, self.depth, 0)), + Vector((0.045, self.depth, 0)), + Vector((0.045, self.depth + leaf_width, 0)), + Vector((0.080, self.depth + leaf_width, 0)), + Vector((0.080, self.depth, 0)), ] edges = [ - [0, 1], [1, 2], [2, 3], [3, 4], [4, 5], # Left lining - [6, 7], [7, 8], [8, 9], [9, 10], [10, 11], # Right lining - [12, 13], [13, 14], [14, 15], [15, 12], # Door panel + [0, 1], + [1, 2], + [2, 3], + [3, 4], + [4, 5], # Left lining + [6, 7], + [7, 8], + [8, 9], + [9, 10], + [10, 11], # Right lining + [12, 13], + [13, 14], + [14, 15], + [15, 12], # Door panel ] # Door swing for i in range(0, 9): - verts.append(Vector((0.045+(leaf_width*math.cos((math.pi/2)/8*i)), self.depth+(leaf_width*math.sin((math.pi/2)/8*i)), 0))) - edges.append([16+i, 17+i]) + verts.append( + Vector( + ( + 0.045 + (leaf_width * math.cos((math.pi / 2) / 8 * i)), + self.depth + (leaf_width * math.sin((math.pi / 2) / 8 * i)), + 0, + ) + ) + ) + edges.append([16 + i, 17 + i]) edges.pop() faces = [] - mesh = bpy.data.meshes.new(name='Plan/Annotation/PLAN_VIEW/' + guid) + mesh = bpy.data.meshes.new(name="Plan/Annotation/PLAN_VIEW/" + guid) mesh.use_fake_user = True mesh.from_pydata(verts, edges, faces) @@ -49,17 +69,17 @@ def add_object(self, context): verts = [ Vector((0, 0, 0)), Vector((0, -self.depth, 0)), - Vector((.04, -self.depth, 0)), - Vector((.04, -self.depth+.04, 0)), - Vector((.065, -self.depth+.04, 0)), - Vector((.065, 0, 0)), + Vector((0.04, -self.depth, 0)), + Vector((0.04, -self.depth + 0.04, 0)), + Vector((0.065, -self.depth + 0.04, 0)), + Vector((0.065, 0, 0)), ] edges = [] faces = [[0, 1, 2, 3, 4, 5]] mesh = bpy.data.meshes.new(name="Dumb Door Profile") mesh.from_pydata(verts, edges, faces) obj = object_data_add(context, mesh, operator=self) - bpy.ops.object.convert(target='CURVE') + bpy.ops.object.convert(target="CURVE") # Door lining sweep verts = [ @@ -70,99 +90,97 @@ def add_object(self, context): ] edges = [[0, 1], [1, 2], [2, 3]] faces = [] - mesh = bpy.data.meshes.new(name='Dumb Door') + mesh = bpy.data.meshes.new(name="Dumb Door") mesh.from_pydata(verts, edges, faces) obj2 = object_data_add(context, mesh, operator=self) - bpy.ops.object.convert(target='CURVE') + bpy.ops.object.convert(target="CURVE") - obj2.data.dimensions = '2D' + obj2.data.dimensions = "2D" obj2.data.bevel_object = obj - obj2.rotation_euler[0] = math.pi/2 - bpy.ops.object.convert(target='MESH') + obj2.rotation_euler[0] = math.pi / 2 + bpy.ops.object.convert(target="MESH") bpy.ops.object.transform_apply(location=False) bpy.data.objects.remove(obj, do_unlink=True) # Door panel verts = [ - Vector((.045, self.depth, 0)), - Vector((.045, self.depth-.035, 0)), - Vector((self.overall_width-.045, self.depth-.035, 0)), - Vector((self.overall_width-.045, self.depth, 0)), + Vector((0.045, self.depth, 0)), + Vector((0.045, self.depth - 0.035, 0)), + Vector((self.overall_width - 0.045, self.depth - 0.035, 0)), + Vector((self.overall_width - 0.045, self.depth, 0)), ] edges = [] faces = [[0, 1, 2, 3]] mesh = bpy.data.meshes.new(name="Dumb Door Panel") mesh.from_pydata(verts, edges, faces) obj3 = object_data_add(context, mesh, operator=self) - modifier = obj3.modifiers.new('Panel Height', 'SOLIDIFY') + modifier = obj3.modifiers.new("Panel Height", "SOLIDIFY") modifier.offset = 1 - modifier.thickness = self.overall_height-.045 - bpy.ops.object.convert(target='MESH') + modifier.thickness = self.overall_height - 0.045 + bpy.ops.object.convert(target="MESH") ctx = bpy.context.copy() - ctx['active_object'] = obj2 - ctx['selected_editable_objects'] = [obj2, obj3] + ctx["active_object"] = obj2 + ctx["selected_editable_objects"] = [obj2, obj3] bpy.ops.object.join(ctx) # Door Opening verts = [ - Vector((0, -.1, -.1)), - Vector((0, self.depth+.1, -.1)), - Vector((self.overall_width, self.depth+.1, -.1)), - Vector((self.overall_width, -.1, -.1)), + Vector((0, -0.1, -0.1)), + Vector((0, self.depth + 0.1, -0.1)), + Vector((self.overall_width, self.depth + 0.1, -0.1)), + Vector((self.overall_width, -0.1, -0.1)), ] edges = [] faces = [[0, 1, 2, 3]] mesh = bpy.data.meshes.new(name="Dumb Door Opening") mesh.from_pydata(verts, edges, faces) obj4 = object_data_add(context, mesh, operator=self) - modifier = obj4.modifiers.new('Panel Height', 'SOLIDIFY') + modifier = obj4.modifiers.new("Panel Height", "SOLIDIFY") modifier.offset = -1 - modifier.thickness = self.overall_height+.1 - bpy.ops.object.convert(target='MESH') - obj4.display_type = 'WIRE' + modifier.thickness = self.overall_height + 0.1 + bpy.ops.object.convert(target="MESH") + obj4.display_type = "WIRE" obj4.parent = obj2 obj4.matrix_parent_inverse = obj2.matrix_world.inverted() obj4.hide_render = True - obj4.name = 'IfcOpeningElement/Dumb Door Opening' + obj4.name = "IfcOpeningElement/Dumb Door Opening" attribute = obj4.BIMObjectProperties.attributes.add() - attribute.name = 'PredefinedType' - attribute.string_value = 'OPENING' + attribute.name = "PredefinedType" + attribute.string_value = "OPENING" - obj2.name = 'IfcDoor/Dumb Door' + obj2.name = "IfcDoor/Dumb Door" attribute = obj2.BIMObjectProperties.attributes.add() - attribute.name = 'PredefinedType' - attribute.string_value = 'DOOR' - obj2.data.name = 'Model/Body/MODEL_VIEW/' + guid + attribute.name = "PredefinedType" + attribute.string_value = "DOOR" + obj2.data.name = "Model/Body/MODEL_VIEW/" + guid obj2.data.use_fake_user = True rep = obj2.BIMObjectProperties.representation_contexts.add() - rep.context = 'Model' - rep.name = 'Body' - rep.target_view = 'MODEL_VIEW' + rep.context = "Model" + rep.name = "Body" + rep.target_view = "MODEL_VIEW" rep = obj2.BIMObjectProperties.representation_contexts.add() - rep.context = 'Plan' - rep.name = 'Annotation' - rep.target_view = 'PLAN_VIEW' + rep.context = "Plan" + rep.name = "Annotation" + rep.target_view = "PLAN_VIEW" class BIM_OT_add_object(Operator, AddObjectHelper): bl_idname = "mesh.add_door" bl_label = "Dumb Door" - bl_options = {'REGISTER', 'UNDO'} + bl_options = {"REGISTER", "UNDO"} - overall_width: FloatProperty(name='Overall Width', default=.85) - overall_height: FloatProperty(name='Overall Height', default=2.1) - depth: FloatProperty(name='Depth', default=.2) + overall_width: FloatProperty(name="Overall Width", default=0.85) + overall_height: FloatProperty(name="Overall Height", default=2.1) + depth: FloatProperty(name="Depth", default=0.2) def execute(self, context): add_object(self, context) - return {'FINISHED'} + return {"FINISHED"} def add_object_button(self, context): - self.layout.operator( - BIM_OT_add_object.bl_idname, - icon='PLUGIN') + self.layout.operator(BIM_OT_add_object.bl_idname, icon="PLUGIN") diff --git a/src/ifcblenderexport/blenderbim/bim/module/model/grid.py b/src/ifcblenderexport/blenderbim/bim/module/model/grid.py index 4e33e69962..053a861f91 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/model/grid.py +++ b/src/ifcblenderexport/blenderbim/bim/module/model/grid.py @@ -5,70 +5,74 @@ from bpy.props import FloatVectorProperty, FloatProperty, IntProperty from bpy_extras.object_utils import AddObjectHelper, object_data_add from mathutils import Vector + def add_object(self, context): obj = object_data_add(context, None, operator=self) - obj.name = 'IfcGrid/Grid' - name = obj.name.split('/')[1] + obj.name = "IfcGrid/Grid" + name = obj.name.split("/")[1] default_collection = obj.users_collection[0] default_collection.objects.unlink(obj) - collection = bpy.data.collections.new('IfcGrid/' + name) + collection = bpy.data.collections.new("IfcGrid/" + name) bpy.context.view_layer.active_layer_collection.collection.children.link(collection) collection.objects.link(obj) - axes_collection = bpy.data.collections.new('UAxes') + axes_collection = bpy.data.collections.new("UAxes") collection.children.link(axes_collection) for i in range(0, self.total_u): - verts = [Vector((-2, i*self.u_spacing, 0)), Vector((((self.total_v-1)*self.v_spacing)+2, i*self.u_spacing, 0))] + verts = [ + Vector((-2, i * self.u_spacing, 0)), + Vector((((self.total_v - 1) * self.v_spacing) + 2, i * self.u_spacing, 0)), + ] edges = [[0, 1]] faces = [] mesh = bpy.data.meshes.new(name="Grid Axis") mesh.from_pydata(verts, edges, faces) obj = object_data_add(context, mesh, operator=self) - tag = chr(ord('A')+i) - obj.name = 'IfcGridAxis/' + tag + tag = chr(ord("A") + i) + obj.name = "IfcGridAxis/" + tag default_collection.objects.unlink(obj) axes_collection.objects.link(obj) attribute = obj.BIMObjectProperties.attributes.add() - attribute.name = 'AxisTag' + attribute.name = "AxisTag" attribute.string_value = tag - axes_collection = bpy.data.collections.new('VAxes') + axes_collection = bpy.data.collections.new("VAxes") collection.children.link(axes_collection) for i in range(0, self.total_v): - verts = [Vector((i*self.v_spacing, -2, 0)), Vector((i*self.v_spacing, ((self.total_u-1)*self.u_spacing)+2, 0))] + verts = [ + Vector((i * self.v_spacing, -2, 0)), + Vector((i * self.v_spacing, ((self.total_u - 1) * self.u_spacing) + 2, 0)), + ] edges = [[0, 1]] faces = [] mesh = bpy.data.meshes.new(name="Grid Axis") mesh.from_pydata(verts, edges, faces) obj = object_data_add(context, mesh, operator=self) - tag = str(i+1).zfill(2) - obj.name = 'IfcGridAxis/' + tag + tag = str(i + 1).zfill(2) + obj.name = "IfcGridAxis/" + tag default_collection.objects.unlink(obj) axes_collection.objects.link(obj) attribute = obj.BIMObjectProperties.attributes.add() - attribute.name = 'AxisTag' + attribute.name = "AxisTag" attribute.string_value = tag - class BIM_OT_add_object(Operator, AddObjectHelper): bl_idname = "mesh.add_grid" bl_label = "Grid" - bl_options = {'REGISTER', 'UNDO'} + bl_options = {"REGISTER", "UNDO"} - u_spacing: FloatProperty(name='U Spacing', default=10) - total_u: IntProperty(name='Number of U Grids', default=3) - v_spacing: FloatProperty(name='V Spacing', default=10) - total_v: IntProperty(name='Number of V Grids', default=3) + u_spacing: FloatProperty(name="U Spacing", default=10) + total_u: IntProperty(name="Number of U Grids", default=3) + v_spacing: FloatProperty(name="V Spacing", default=10) + total_v: IntProperty(name="Number of V Grids", default=3) def execute(self, context): add_object(self, context) - return {'FINISHED'} + return {"FINISHED"} def add_object_button(self, context): - self.layout.operator( - BIM_OT_add_object.bl_idname, - icon='PLUGIN') + self.layout.operator(BIM_OT_add_object.bl_idname, icon="PLUGIN") diff --git a/src/ifcblenderexport/blenderbim/bim/module/model/opening.py b/src/ifcblenderexport/blenderbim/bim/module/model/opening.py index fb7066b91f..74b78161ee 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/model/opening.py +++ b/src/ifcblenderexport/blenderbim/bim/module/model/opening.py @@ -5,6 +5,7 @@ from bpy.props import FloatVectorProperty, FloatProperty, IntProperty from bpy_extras.object_utils import AddObjectHelper, object_data_add from mathutils import Vector + def add_object(self, context): bm = bmesh.new() bmesh.ops.create_cube(bm, size=self.size) @@ -15,26 +16,24 @@ def add_object(self, context): bm.to_mesh(mesh) bm.free() obj = object_data_add(context, mesh, operator=self) - obj.name = 'IfcOpening/Dumb Opening' - obj.display_type = 'WIRE' + obj.name = "IfcOpening/Dumb Opening" + obj.display_type = "WIRE" attribute = obj.BIMObjectProperties.attributes.add() - attribute.name = 'PredefinedType' - attribute.string_value = 'OPENING' + attribute.name = "PredefinedType" + attribute.string_value = "OPENING" class BIM_OT_add_object(Operator, AddObjectHelper): bl_idname = "mesh.add_opening" bl_label = "Dumb Opening" - bl_options = {'REGISTER', 'UNDO'} + bl_options = {"REGISTER", "UNDO"} - size: FloatProperty(name='Size', default=2) + size: FloatProperty(name="Size", default=2) def execute(self, context): add_object(self, context) - return {'FINISHED'} + return {"FINISHED"} def add_object_button(self, context): - self.layout.operator( - BIM_OT_add_object.bl_idname, - icon='PLUGIN') + self.layout.operator(BIM_OT_add_object.bl_idname, icon="PLUGIN") diff --git a/src/ifcblenderexport/blenderbim/bim/module/model/slab.py b/src/ifcblenderexport/blenderbim/bim/module/model/slab.py index bed7b64257..bd37f2130f 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/model/slab.py +++ b/src/ifcblenderexport/blenderbim/bim/module/model/slab.py @@ -4,6 +4,7 @@ from bpy.props import FloatVectorProperty, FloatProperty, IntProperty from bpy_extras.object_utils import AddObjectHelper, object_data_add from mathutils import Vector + def add_object(self, context): verts = [ Vector((0, 0, 0)), @@ -17,31 +18,29 @@ def add_object(self, context): mesh = bpy.data.meshes.new(name="Dumb Slab") mesh.from_pydata(verts, edges, faces) obj = object_data_add(context, mesh, operator=self) - modifier = obj.modifiers.new('Slab Depth', 'SOLIDIFY') + modifier = obj.modifiers.new("Slab Depth", "SOLIDIFY") modifier.use_even_offset = True modifier.offset = 1 modifier.thickness = self.depth - obj.name = 'IfcSlab/Dumb Slab' + obj.name = "IfcSlab/Dumb Slab" attribute = obj.BIMObjectProperties.attributes.add() - attribute.name = 'PredefinedType' - attribute.string_value = 'FLOOR' + attribute.name = "PredefinedType" + attribute.string_value = "FLOOR" class BIM_OT_add_object(Operator, AddObjectHelper): bl_idname = "mesh.add_slab" bl_label = "Dumb Slab" - bl_options = {'REGISTER', 'UNDO'} + bl_options = {"REGISTER", "UNDO"} - length: FloatProperty(name='Length', default=2) - width: FloatProperty(name='Width', default=2) - depth: FloatProperty(name='Depth', default=.2) + length: FloatProperty(name="Length", default=2) + width: FloatProperty(name="Width", default=2) + depth: FloatProperty(name="Depth", default=0.2) def execute(self, context): add_object(self, context) - return {'FINISHED'} + return {"FINISHED"} def add_object_button(self, context): - self.layout.operator( - BIM_OT_add_object.bl_idname, - icon='PLUGIN') + self.layout.operator(BIM_OT_add_object.bl_idname, icon="PLUGIN") diff --git a/src/ifcblenderexport/blenderbim/bim/module/model/stair.py b/src/ifcblenderexport/blenderbim/bim/module/model/stair.py index 0c51836e47..3d3fc07db2 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/model/stair.py +++ b/src/ifcblenderexport/blenderbim/bim/module/model/stair.py @@ -4,6 +4,7 @@ from bpy.props import FloatVectorProperty, FloatProperty, IntProperty from bpy_extras.object_utils import AddObjectHelper, object_data_add from mathutils import Vector + def add_object(self, context): if self.number_of_treads <= 0: self.number_of_treads = 1 @@ -19,11 +20,11 @@ def add_object(self, context): mesh = bpy.data.meshes.new(name="Dumb Stair") mesh.from_pydata(verts, edges, faces) obj = object_data_add(context, mesh, operator=self) - modifier = obj.modifiers.new('Stair Width', 'SOLIDIFY') + modifier = obj.modifiers.new("Stair Width", "SOLIDIFY") modifier.use_even_offset = True modifier.offset = 1 modifier.thickness = self.tread_depth - modifier = obj.modifiers.new('Stair Treads', 'ARRAY') + modifier = obj.modifiers.new("Stair Treads", "ARRAY") modifier.relative_offset_displace[0] = 0 modifier.relative_offset_displace[1] = 1 modifier.use_constant_offset = True @@ -31,31 +32,29 @@ def add_object(self, context): modifier.count = self.number_of_treads self.riser_height = self.height / self.number_of_treads self.length = self.number_of_treads * self.tread_length - obj.name = 'IfcStairFlight/Dumb Stair' + obj.name = "IfcStairFlight/Dumb Stair" attribute = obj.BIMObjectProperties.attributes.add() - attribute.name = 'PredefinedType' - attribute.string_value = 'STRAIGHT' + attribute.name = "PredefinedType" + attribute.string_value = "STRAIGHT" class BIM_OT_add_object(Operator, AddObjectHelper): bl_idname = "mesh.add_stair" bl_label = "Dumb Stair" - bl_options = {'REGISTER', 'UNDO'} + bl_options = {"REGISTER", "UNDO"} - width: FloatProperty(name='Width', default=1.1) - height: FloatProperty(name='Height', default=1) - tread_depth: FloatProperty(name='Tread Depth', default=.2) - number_of_treads: IntProperty(name='Number of Treads (Goings)', default=6) - tread_length: FloatProperty(name='Tread Length (Going)', default=.25) - riser_height: FloatProperty(name='*Calculated* Riser Height') - length: FloatProperty(name='*Calculated* Length') + width: FloatProperty(name="Width", default=1.1) + height: FloatProperty(name="Height", default=1) + tread_depth: FloatProperty(name="Tread Depth", default=0.2) + number_of_treads: IntProperty(name="Number of Treads (Goings)", default=6) + tread_length: FloatProperty(name="Tread Length (Going)", default=0.25) + riser_height: FloatProperty(name="*Calculated* Riser Height") + length: FloatProperty(name="*Calculated* Length") def execute(self, context): add_object(self, context) - return {'FINISHED'} + return {"FINISHED"} def add_object_button(self, context): - self.layout.operator( - BIM_OT_add_object.bl_idname, - icon='PLUGIN') + self.layout.operator(BIM_OT_add_object.bl_idname, icon="PLUGIN") diff --git a/src/ifcblenderexport/blenderbim/bim/module/model/wall.py b/src/ifcblenderexport/blenderbim/bim/module/model/wall.py index 98e9624964..bcb2717878 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/model/wall.py +++ b/src/ifcblenderexport/blenderbim/bim/module/model/wall.py @@ -4,6 +4,7 @@ from bpy.props import FloatVectorProperty, FloatProperty, BoolProperty from bpy_extras.object_utils import AddObjectHelper, object_data_add from mathutils import Vector + def add_object(self, context): if self.use_plane: verts = [ @@ -26,7 +27,7 @@ def add_object(self, context): mesh.from_pydata(verts, edges, faces) obj = object_data_add(context, mesh, operator=self) if not self.use_plane: - modifier = obj.modifiers.new('Wall Height', 'SCREW') + modifier = obj.modifiers.new("Wall Height", "SCREW") modifier.angle = 0 modifier.screw_offset = self.height modifier.use_smooth_shade = False @@ -34,31 +35,29 @@ def add_object(self, context): modifier.use_normal_flip = True modifier.steps = 1 modifier.render_steps = 1 - modifier = obj.modifiers.new('Wall Width', 'SOLIDIFY') + modifier = obj.modifiers.new("Wall Width", "SOLIDIFY") modifier.use_even_offset = True modifier.thickness = self.width - obj.name = 'IfcWall/Dumb Wall' + obj.name = "IfcWall/Dumb Wall" attribute = obj.BIMObjectProperties.attributes.add() - attribute.name = 'PredefinedType' - attribute.string_value = 'STANDARD' + attribute.name = "PredefinedType" + attribute.string_value = "STANDARD" class BIM_OT_add_object(Operator, AddObjectHelper): bl_idname = "mesh.add_wall" bl_label = "Dumb Wall" - bl_options = {'REGISTER', 'UNDO'} + bl_options = {"REGISTER", "UNDO"} - height: FloatProperty(name='Height', default=3) - length: FloatProperty(name='Length', default=1) - width: FloatProperty(name='Width', default=.2) - use_plane: BoolProperty(name='Use Plane', default=False) + height: FloatProperty(name="Height", default=3) + length: FloatProperty(name="Length", default=1) + width: FloatProperty(name="Width", default=0.2) + use_plane: BoolProperty(name="Use Plane", default=False) def execute(self, context): add_object(self, context) - return {'FINISHED'} + return {"FINISHED"} def add_object_button(self, context): - self.layout.operator( - BIM_OT_add_object.bl_idname, - icon='PLUGIN') + self.layout.operator(BIM_OT_add_object.bl_idname, icon="PLUGIN") diff --git a/src/ifcblenderexport/blenderbim/bim/module/model/window.py b/src/ifcblenderexport/blenderbim/bim/module/model/window.py index caeecb9eaf..b43bf70574 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/model/window.py +++ b/src/ifcblenderexport/blenderbim/bim/module/model/window.py @@ -6,39 +6,50 @@ from bpy.props import FloatVectorProperty, FloatProperty, IntProperty from bpy_extras.object_utils import AddObjectHelper, object_data_add from mathutils import Vector + def add_object(self, context): guid = ifcopenshell.guid.new() - leaf_width = self.overall_width-0.045-0.045 + leaf_width = self.overall_width - 0.045 - 0.045 verts = [ # Left lining Vector((0, 0, 0)), Vector((0, self.depth, 0)), - Vector((.04, self.depth, 0)), - Vector((.04, 0, 0)), + Vector((0.04, self.depth, 0)), + Vector((0.04, 0, 0)), # Right lining Vector((self.overall_width, 0, 0)), Vector((self.overall_width, self.depth, 0)), - Vector((self.overall_width-.04, self.depth, 0)), - Vector((self.overall_width-.04, 0, 0)), + Vector((self.overall_width - 0.04, self.depth, 0)), + Vector((self.overall_width - 0.04, 0, 0)), # Bottom lining Vector((0, 0, 0)), Vector((self.overall_width, 0, 0)), Vector((0, self.depth, 0)), Vector((self.overall_width, self.depth, 0)), # Window panel - Vector((.04, (self.depth/2)+.005, 0)), - Vector((.04, (self.depth/2)-.005, 0)), - Vector((self.overall_width-.04, (self.depth/2)-.005, 0)), - Vector((self.overall_width-.04, (self.depth/2)+.005, 0)), + Vector((0.04, (self.depth / 2) + 0.005, 0)), + Vector((0.04, (self.depth / 2) - 0.005, 0)), + Vector((self.overall_width - 0.04, (self.depth / 2) - 0.005, 0)), + Vector((self.overall_width - 0.04, (self.depth / 2) + 0.005, 0)), ] edges = [ - [0, 1], [1, 2], [2, 3], [3, 0], # Left lining - [4, 5], [5, 6], [6, 7], [7, 4], # Right lining - [8, 9], [10, 11], # Bottom lining - [12, 13], [13, 14], [14, 15], [15, 12], # Window panel + [0, 1], + [1, 2], + [2, 3], + [3, 0], # Left lining + [4, 5], + [5, 6], + [6, 7], + [7, 4], # Right lining + [8, 9], + [10, 11], # Bottom lining + [12, 13], + [13, 14], + [14, 15], + [15, 12], # Window panel ] faces = [] - mesh = bpy.data.meshes.new(name='Plan/Annotation/PLAN_VIEW/' + guid) + mesh = bpy.data.meshes.new(name="Plan/Annotation/PLAN_VIEW/" + guid) mesh.use_fake_user = True mesh.from_pydata(verts, edges, faces) @@ -46,15 +57,15 @@ def add_object(self, context): verts = [ Vector((0, 0, 0)), Vector((0, -self.depth, 0)), - Vector((.04, -self.depth, 0)), - Vector((.04, 0, 0)), + Vector((0.04, -self.depth, 0)), + Vector((0.04, 0, 0)), ] edges = [] faces = [[0, 1, 2, 3]] mesh = bpy.data.meshes.new(name="Dumb Window Profile") mesh.from_pydata(verts, edges, faces) obj = object_data_add(context, mesh, operator=self) - bpy.ops.object.convert(target='CURVE') + bpy.ops.object.convert(target="CURVE") # Window lining sweep verts = [ @@ -68,97 +79,95 @@ def add_object(self, context): mesh = bpy.data.meshes.new(name="Dumb Window") mesh.from_pydata(verts, edges, faces) obj2 = object_data_add(context, mesh, operator=self) - bpy.ops.object.convert(target='CURVE') + bpy.ops.object.convert(target="CURVE") obj2.data.splines[0].use_cyclic_u = True - obj2.data.dimensions = '2D' + obj2.data.dimensions = "2D" obj2.data.bevel_object = obj - obj2.rotation_euler[0] = math.pi/2 - bpy.ops.object.convert(target='MESH') + obj2.rotation_euler[0] = math.pi / 2 + bpy.ops.object.convert(target="MESH") bpy.ops.object.transform_apply(location=False) bpy.data.objects.remove(obj, do_unlink=True) # Window panel verts = [ - Vector((.04, (self.depth/2)+.005, .04)), - Vector((.04, (self.depth/2)-.005, .04)), - Vector((self.overall_width-.04, (self.depth/2)-.005, .04)), - Vector((self.overall_width-.04, (self.depth/2)+.005, .04)), + Vector((0.04, (self.depth / 2) + 0.005, 0.04)), + Vector((0.04, (self.depth / 2) - 0.005, 0.04)), + Vector((self.overall_width - 0.04, (self.depth / 2) - 0.005, 0.04)), + Vector((self.overall_width - 0.04, (self.depth / 2) + 0.005, 0.04)), ] edges = [] faces = [[0, 1, 2, 3]] mesh = bpy.data.meshes.new(name="Dumb Window Panel") mesh.from_pydata(verts, edges, faces) obj3 = object_data_add(context, mesh, operator=self) - modifier = obj3.modifiers.new('Panel Height', 'SOLIDIFY') + modifier = obj3.modifiers.new("Panel Height", "SOLIDIFY") modifier.offset = 1 - modifier.thickness = self.overall_height-.08 - bpy.ops.object.convert(target='MESH') + modifier.thickness = self.overall_height - 0.08 + bpy.ops.object.convert(target="MESH") ctx = bpy.context.copy() - ctx['active_object'] = obj2 - ctx['selected_editable_objects'] = [obj2, obj3] + ctx["active_object"] = obj2 + ctx["selected_editable_objects"] = [obj2, obj3] bpy.ops.object.join(ctx) # Window Opening verts = [ - Vector((0, -.1, 0)), - Vector((0, self.depth+.1, 0)), - Vector((self.overall_width, self.depth+.1, 0)), - Vector((self.overall_width, -.1, 0)), + Vector((0, -0.1, 0)), + Vector((0, self.depth + 0.1, 0)), + Vector((self.overall_width, self.depth + 0.1, 0)), + Vector((self.overall_width, -0.1, 0)), ] edges = [] faces = [[0, 1, 2, 3]] mesh = bpy.data.meshes.new(name="Dumb Window Opening") mesh.from_pydata(verts, edges, faces) obj4 = object_data_add(context, mesh, operator=self) - modifier = obj4.modifiers.new('Panel Height', 'SOLIDIFY') + modifier = obj4.modifiers.new("Panel Height", "SOLIDIFY") modifier.offset = -1 modifier.thickness = self.overall_height - bpy.ops.object.convert(target='MESH') - obj4.display_type = 'WIRE' + bpy.ops.object.convert(target="MESH") + obj4.display_type = "WIRE" obj4.parent = obj2 obj4.matrix_parent_inverse = obj2.matrix_world.inverted() obj4.hide_render = True - obj4.name = 'IfcOpeningElement/Dumb Window Opening' + obj4.name = "IfcOpeningElement/Dumb Window Opening" attribute = obj4.BIMObjectProperties.attributes.add() - attribute.name = 'PredefinedType' - attribute.string_value = 'OPENING' + attribute.name = "PredefinedType" + attribute.string_value = "OPENING" - obj2.name = 'IfcWindow/Dumb Window' + obj2.name = "IfcWindow/Dumb Window" attribute = obj2.BIMObjectProperties.attributes.add() - attribute.name = 'PredefinedType' - attribute.string_value = 'WINDOW' - obj2.data.name = 'Model/Body/MODEL_VIEW/' + guid + attribute.name = "PredefinedType" + attribute.string_value = "WINDOW" + obj2.data.name = "Model/Body/MODEL_VIEW/" + guid obj2.data.use_fake_user = True rep = obj2.BIMObjectProperties.representation_contexts.add() - rep.context = 'Model' - rep.name = 'Body' - rep.target_view = 'MODEL_VIEW' + rep.context = "Model" + rep.name = "Body" + rep.target_view = "MODEL_VIEW" rep = obj2.BIMObjectProperties.representation_contexts.add() - rep.context = 'Plan' - rep.name = 'Annotation' - rep.target_view = 'PLAN_VIEW' + rep.context = "Plan" + rep.name = "Annotation" + rep.target_view = "PLAN_VIEW" class BIM_OT_add_object(Operator, AddObjectHelper): bl_idname = "mesh.add_window" bl_label = "Dumb Window" - bl_options = {'REGISTER', 'UNDO'} + bl_options = {"REGISTER", "UNDO"} - overall_width: FloatProperty(name='Overall Width', default=.7) - overall_height: FloatProperty(name='Overall Height', default=1) - depth: FloatProperty(name='Depth', default=.1) + overall_width: FloatProperty(name="Overall Width", default=0.7) + overall_height: FloatProperty(name="Overall Height", default=1) + depth: FloatProperty(name="Depth", default=0.1) def execute(self, context): add_object(self, context) - return {'FINISHED'} + return {"FINISHED"} def add_object_button(self, context): - self.layout.operator( - BIM_OT_add_object.bl_idname, - icon='PLUGIN') + self.layout.operator(BIM_OT_add_object.bl_idname, icon="PLUGIN") diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 96f2bd498a..5a21e09955 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -30,18 +30,19 @@ from pathlib import Path from bpy.app.handlers import persistent colour_list = [ - (.651, .81, .892, 1), - (.121, .471, .706, 1), - (.699, .876, .54, 1), - (.199, .629, .174, 1), - (.983, .605, .602, 1), - (.89, .101, .112, 1), - (.989, .751, .427, 1), - (.986, .497, .1, 1), - (.792, .699, .839, 1), - (.414, .239, .603, 1), - (.993, .999, .6, 1), - (.693, .349, .157, 1)] + (0.651, 0.81, 0.892, 1), + (0.121, 0.471, 0.706, 1), + (0.699, 0.876, 0.54, 1), + (0.199, 0.629, 0.174, 1), + (0.983, 0.605, 0.602, 1), + (0.89, 0.101, 0.112, 1), + (0.989, 0.751, 0.427, 1), + (0.986, 0.497, 0.1, 1), + (0.792, 0.699, 0.839, 1), + (0.414, 0.239, 0.603, 1), + (0.993, 0.999, 0.6, 1), + (0.693, 0.349, 0.157, 1), +] @persistent @@ -50,17 +51,17 @@ def depsgraph_update_pre_handler(scene): def set_active_camera_resolution(scene): - if not scene.camera \ - or '/' not in scene.camera.name \ - or not scene.DocProperties.drawings: + if not scene.camera or "/" not in scene.camera.name or not scene.DocProperties.drawings: return - if scene.render.resolution_x != scene.camera.data.BIMCameraProperties.raster_x \ - or scene.render.resolution_y != scene.camera.data.BIMCameraProperties.raster_y: + if ( + scene.render.resolution_x != scene.camera.data.BIMCameraProperties.raster_x + or scene.render.resolution_y != scene.camera.data.BIMCameraProperties.raster_y + ): scene.render.resolution_x = scene.camera.data.BIMCameraProperties.raster_x scene.render.resolution_y = scene.camera.data.BIMCameraProperties.raster_y current_drawing = scene.DocProperties.drawings[scene.DocProperties.current_drawing_index] if scene.camera != current_drawing.camera: - scene.DocProperties.current_drawing_index = scene.DocProperties.drawings.find(scene.camera.name.split('/')[1]) + scene.DocProperties.current_drawing_index = scene.DocProperties.drawings.find(scene.camera.name.split("/")[1]) bpy.ops.bim.activate_view(drawing_index=scene.DocProperties.current_drawing_index) @@ -70,115 +71,122 @@ def open_with_user_command(user_command, path): for command in commands: subprocess.run(command) else: - webbrowser.open('file://' + path) + webbrowser.open("file://" + path) class ExportIFC(bpy.types.Operator): bl_idname = "export_ifc.bim" bl_label = "Export IFC" filename_ext = ".ifc" - filepath: bpy.props.StringProperty(subtype='FILE_PATH') + filepath: bpy.props.StringProperty(subtype="FILE_PATH") def invoke(self, context, event): if not self.filepath: self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".ifc") WindowManager = context.window_manager WindowManager.fileselect_add(self) - return {'RUNNING_MODAL'} + return {"RUNNING_MODAL"} def execute(self, context): start = time.time() - logger = logging.getLogger('ExportIFC') + logger = logging.getLogger("ExportIFC") logging.basicConfig( - filename=context.scene.BIMProperties.data_dir + 'process.log', - filemode='a', level=logging.DEBUG) - extension = self.filepath.split('.')[-1] - if extension == 'ifczip': - output_file = bpy.path.ensure_ext(self.filepath, '.ifczip') - elif extension == 'ifcjson': - output_file = bpy.path.ensure_ext(self.filepath, '.ifcjson') + filename=context.scene.BIMProperties.data_dir + "process.log", filemode="a", level=logging.DEBUG + ) + extension = self.filepath.split(".")[-1] + if extension == "ifczip": + output_file = bpy.path.ensure_ext(self.filepath, ".ifczip") + elif extension == "ifcjson": + output_file = bpy.path.ensure_ext(self.filepath, ".ifcjson") else: - output_file = bpy.path.ensure_ext(self.filepath, '.ifc') + output_file = bpy.path.ensure_ext(self.filepath, ".ifc") ifc_export_settings = export_ifc.IfcExportSettings.factory(context, output_file, logger) qto_calculator = qto.QtoCalculator() ifc_parser = export_ifc.IfcParser(ifc_export_settings, qto_calculator) ifc_exporter = export_ifc.IfcExporter(ifc_export_settings, ifc_parser) - ifc_export_settings.logger.info('Starting export') + ifc_export_settings.logger.info("Starting export") ifc_exporter.export(context.selected_objects) - ifc_export_settings.logger.info('Export finished in {:.2f} seconds'.format(time.time() - start)) + ifc_export_settings.logger.info("Export finished in {:.2f} seconds".format(time.time() - start)) if not bpy.context.scene.DocProperties.ifc_files: new = bpy.context.scene.DocProperties.ifc_files.add() new.name = output_file if not bpy.context.scene.BIMProperties.ifc_file: bpy.context.scene.BIMProperties.ifc_file = output_file - return {'FINISHED'} + return {"FINISHED"} + class ImportIFC(bpy.types.Operator, ImportHelper): bl_idname = "import_ifc.bim" bl_label = "Import IFC" filename_ext = ".ifc" - filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={'HIDDEN'}) + filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"}) def execute(self, context): start = time.time() - logger = logging.getLogger('ImportIFC') + logger = logging.getLogger("ImportIFC") logging.basicConfig( - filename=bpy.context.scene.BIMProperties.data_dir + 'process.log', - filemode='a', level=logging.DEBUG) + filename=bpy.context.scene.BIMProperties.data_dir + "process.log", filemode="a", level=logging.DEBUG + ) ifc_import_settings = import_ifc.IfcImportSettings.factory(context, self.filepath, logger) - ifc_import_settings.logger.info('Starting import') + ifc_import_settings.logger.info("Starting import") ifc_importer = import_ifc.IfcImporter(ifc_import_settings) ifc_importer.execute() - ifc_import_settings.logger.info('Import finished in {:.2f} seconds'.format(time.time() - start)) - print('Import finished in {:.2f} seconds'.format(time.time() - start)) - return {'FINISHED'} + ifc_import_settings.logger.info("Import finished in {:.2f} seconds".format(time.time() - start)) + print("Import finished in {:.2f} seconds".format(time.time() - start)) + return {"FINISHED"} + class SelectGlobalId(bpy.types.Operator): - bl_idname = 'bim.select_global_id' - bl_label = 'Select GlobalId' + bl_idname = "bim.select_global_id" + bl_label = "Select GlobalId" def execute(self, context): for obj in bpy.context.visible_objects: - index = obj.BIMObjectProperties.attributes.find('GlobalId') - if index != -1 \ - and obj.BIMObjectProperties.attributes[index].string_value == bpy.context.scene.BIMProperties.global_id: + index = obj.BIMObjectProperties.attributes.find("GlobalId") + if ( + index != -1 + and obj.BIMObjectProperties.attributes[index].string_value == bpy.context.scene.BIMProperties.global_id + ): obj.select_set(True) break - return {'FINISHED'} + return {"FINISHED"} + class SelectAttribute(bpy.types.Operator): - bl_idname = 'bim.select_attribute' - bl_label = 'Select Attribute' + bl_idname = "bim.select_attribute" + bl_label = "Select Attribute" def execute(self, context): import re + search_value = bpy.context.scene.BIMProperties.search_attribute_value for object in bpy.context.visible_objects: index = object.BIMObjectProperties.attributes.find(bpy.context.scene.BIMProperties.search_attribute_name) if index == -1: continue value = object.BIMObjectProperties.attributes[index].string_value - if bpy.context.scene.BIMProperties.search_regex \ - and bpy.context.scene.BIMProperties.search_ignorecase \ - and re.search(search_value, value, flags=re.IGNORECASE): + if ( + bpy.context.scene.BIMProperties.search_regex + and bpy.context.scene.BIMProperties.search_ignorecase + and re.search(search_value, value, flags=re.IGNORECASE) + ): object.select_set(True) - elif bpy.context.scene.BIMProperties.search_regex \ - and re.search(search_value, value): + elif bpy.context.scene.BIMProperties.search_regex and re.search(search_value, value): object.select_set(True) - elif bpy.context.scene.BIMProperties.search_ignorecase \ - and value.lower() == search_value.lower(): + elif bpy.context.scene.BIMProperties.search_ignorecase and value.lower() == search_value.lower(): object.select_set(True) elif value == search_value: object.select_set(True) - return {'FINISHED'} + return {"FINISHED"} class SelectPset(bpy.types.Operator): - bl_idname = 'bim.select_pset' - bl_label = 'Select Pset' + bl_idname = "bim.select_pset" + bl_label = "Select Pset" def execute(self, context): import re + search_pset_name = bpy.context.scene.BIMProperties.search_pset_name search_prop_name = bpy.context.scene.BIMProperties.search_prop_name search_value = bpy.context.scene.BIMProperties.search_pset_value @@ -190,24 +198,24 @@ class SelectPset(bpy.types.Operator): if prop_index == -1: continue value = object.BIMObjectProperties.psets[pset_index].properties[prop_index].string_value - if bpy.context.scene.BIMProperties.search_regex \ - and bpy.context.scene.BIMProperties.search_ignorecase \ - and re.search(search_value, value, flags=re.IGNORECASE): + if ( + bpy.context.scene.BIMProperties.search_regex + and bpy.context.scene.BIMProperties.search_ignorecase + and re.search(search_value, value, flags=re.IGNORECASE) + ): object.select_set(True) - elif bpy.context.scene.BIMProperties.search_regex \ - and re.search(search_value, value): + elif bpy.context.scene.BIMProperties.search_regex and re.search(search_value, value): object.select_set(True) - elif bpy.context.scene.BIMProperties.search_ignorecase \ - and value.lower() == search_value.lower(): + elif bpy.context.scene.BIMProperties.search_ignorecase and value.lower() == search_value.lower(): object.select_set(True) elif value == search_value: object.select_set(True) - return {'FINISHED'} + return {"FINISHED"} class AssignClass(bpy.types.Operator): - bl_idname = 'bim.assign_class' - bl_label = 'Assign IFC Class' + bl_idname = "bim.assign_class" + bl_label = "Assign IFC Class" object_name: bpy.props.StringProperty() def execute(self, context): @@ -217,48 +225,45 @@ class AssignClass(bpy.types.Operator): objects = bpy.context.selected_objects for obj in objects: existing_class = None - if '/' in obj.name \ - and obj.name[0:3] == 'Ifc': - existing_class = obj.name.split('/')[0] + if "/" in obj.name and obj.name[0:3] == "Ifc": + existing_class = obj.name.split("/")[0] if existing_class: - obj.name = '{}/{}'.format( - bpy.context.scene.BIMProperties.ifc_class, - obj.name.split('/')[1]) + obj.name = "{}/{}".format(bpy.context.scene.BIMProperties.ifc_class, obj.name.split("/")[1]) else: - obj.name = '{}/{}'.format( - bpy.context.scene.BIMProperties.ifc_class, - obj.name) - predefined_type_index = obj.BIMObjectProperties.attributes.find('PredefinedType') + obj.name = "{}/{}".format(bpy.context.scene.BIMProperties.ifc_class, obj.name) + predefined_type_index = obj.BIMObjectProperties.attributes.find("PredefinedType") if predefined_type_index >= 0: obj.BIMObjectProperties.attributes.remove(predefined_type_index) - object_type_index = obj.BIMObjectProperties.attributes.find('ObjectType') + object_type_index = obj.BIMObjectProperties.attributes.find("ObjectType") if object_type_index >= 0: obj.BIMObjectProperties.attributes.remove(object_type_index) if bpy.context.scene.BIMProperties.ifc_predefined_type: predefined_type = obj.BIMObjectProperties.attributes.add() - predefined_type.name = 'PredefinedType' - predefined_type.string_value = bpy.context.scene.BIMProperties.ifc_predefined_type # TODO: make it an enum - if bpy.context.scene.BIMProperties.ifc_predefined_type == 'USERDEFINED': + predefined_type.name = "PredefinedType" + predefined_type.string_value = ( + bpy.context.scene.BIMProperties.ifc_predefined_type + ) # TODO: make it an enum + if bpy.context.scene.BIMProperties.ifc_predefined_type == "USERDEFINED": object_type = obj.BIMObjectProperties.attributes.add() - object_type.name = 'ObjectType' + object_type.name = "ObjectType" object_type.string_value = bpy.context.scene.BIMProperties.ifc_userdefined_type - if bpy.context.scene.BIMProperties.ifc_product == 'IfcElementType': - for project in [c for c in bpy.context.view_layer.layer_collection.children if 'IfcProject' in c.name]: - if not [c for c in project.children if 'Types' in c.name]: - types = bpy.data.collections.new('Types') + if bpy.context.scene.BIMProperties.ifc_product == "IfcElementType": + for project in [c for c in bpy.context.view_layer.layer_collection.children if "IfcProject" in c.name]: + if not [c for c in project.children if "Types" in c.name]: + types = bpy.data.collections.new("Types") project.collection.children.link(types) - for collection in [c for c in project.children if 'Types' in c.name]: + for collection in [c for c in project.children if "Types" in c.name]: for user_collection in obj.users_collection: user_collection.objects.unlink(obj) collection.collection.objects.link(obj) break break - return {'FINISHED'} + return {"FINISHED"} class UnassignClass(bpy.types.Operator): - bl_idname = 'bim.unassign_class' - bl_label = 'Unassign IFC Class' + bl_idname = "bim.unassign_class" + bl_label = "Unassign IFC Class" object_name: bpy.props.StringProperty() def execute(self, context): @@ -268,64 +273,73 @@ class UnassignClass(bpy.types.Operator): objects = bpy.context.selected_objects for obj in objects: existing_class = None - if '/' in obj.name \ - and obj.name[0:3] == 'Ifc': - obj.name = '/'.join(obj.name.split('/')[1:]) - return {'FINISHED'} + if "/" in obj.name and obj.name[0:3] == "Ifc": + obj.name = "/".join(obj.name.split("/")[1:]) + return {"FINISHED"} class SelectClass(bpy.types.Operator): - bl_idname = 'bim.select_class' - bl_label = 'Select IFC Class' + bl_idname = "bim.select_class" + bl_label = "Select IFC Class" def execute(self, context): for object in bpy.context.visible_objects: - if '/' in object.name \ - and object.name[0:3] == 'Ifc' \ - and object.name.split('/')[0] == bpy.context.scene.BIMProperties.ifc_class: + if ( + "/" in object.name + and object.name[0:3] == "Ifc" + and object.name.split("/")[0] == bpy.context.scene.BIMProperties.ifc_class + ): object.select_set(True) - return {'FINISHED'} + return {"FINISHED"} + class SelectType(bpy.types.Operator): - bl_idname = 'bim.select_type' - bl_label = 'Select IFC Type' + bl_idname = "bim.select_type" + bl_label = "Select IFC Type" def execute(self, context): for object in bpy.context.visible_objects: - if '/' in object.name \ - and object.name[0:3] == 'Ifc' \ - and object.name.split('/')[0] == bpy.context.scene.BIMProperties.ifc_class \ - and 'PredefinedType' in object.BIMObjectProperties.attributes \ - and object.BIMObjectProperties.attributes['PredefinedType'].string_value == bpy.context.scene.BIMProperties.ifc_predefined_type: - if bpy.context.scene.BIMProperties.ifc_predefined_type != 'USERDEFINED': + if ( + "/" in object.name + and object.name[0:3] == "Ifc" + and object.name.split("/")[0] == bpy.context.scene.BIMProperties.ifc_class + and "PredefinedType" in object.BIMObjectProperties.attributes + and object.BIMObjectProperties.attributes["PredefinedType"].string_value + == bpy.context.scene.BIMProperties.ifc_predefined_type + ): + if bpy.context.scene.BIMProperties.ifc_predefined_type != "USERDEFINED": object.select_set(True) - elif 'ObjectType' in object.BIMObjectProperties.attributes \ - and object.BIMObjectProperties.attributes['ObjectType'].string_value == bpy.context.scene.BIMProperties.ifc_userdefined_type: + elif ( + "ObjectType" in object.BIMObjectProperties.attributes + and object.BIMObjectProperties.attributes["ObjectType"].string_value + == bpy.context.scene.BIMProperties.ifc_userdefined_type + ): object.select_set(True) - return {'FINISHED'} + return {"FINISHED"} + class ColourByClass(bpy.types.Operator): - bl_idname = 'bim.colour_by_class' - bl_label = 'Colour by Class' + bl_idname = "bim.colour_by_class" + bl_label = "Colour by Class" def execute(self, context): colours = cycle(colour_list) ifc_classes = {} for obj in bpy.context.visible_objects: - if '/' not in obj.name: + if "/" not in obj.name: continue - ifc_class = obj.name.split('/')[0] + ifc_class = obj.name.split("/")[0] if ifc_class not in ifc_classes: ifc_classes[ifc_class] = next(colours) obj.color = ifc_classes[ifc_class] - area = next(area for area in bpy.context.screen.areas if area.type == 'VIEW_3D') - area.spaces[0].shading.color_type = 'OBJECT' - return {'FINISHED'} + area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") + area.spaces[0].shading.color_type = "OBJECT" + return {"FINISHED"} class ColourByAttribute(bpy.types.Operator): - bl_idname = 'bim.colour_by_attribute' - bl_label = 'Colour by Attribute' + bl_idname = "bim.colour_by_attribute" + bl_label = "Colour by Attribute" def execute(self, context): colours = cycle(colour_list) @@ -339,14 +353,14 @@ class ColourByAttribute(bpy.types.Operator): if value not in values: values[value] = next(colours) obj.color = values[value] - area = next(area for area in bpy.context.screen.areas if area.type == 'VIEW_3D') - area.spaces[0].shading.color_type = 'OBJECT' - return {'FINISHED'} + area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") + area.spaces[0].shading.color_type = "OBJECT" + return {"FINISHED"} class ColourByPset(bpy.types.Operator): - bl_idname = 'bim.colour_by_pset' - bl_label = 'Colour by Pset' + bl_idname = "bim.colour_by_pset" + bl_label = "Colour by Pset" def execute(self, context): colours = cycle(colour_list) @@ -364,97 +378,110 @@ class ColourByPset(bpy.types.Operator): if value not in values: values[value] = next(colours) obj.color = values[value] - area = next(area for area in bpy.context.screen.areas if area.type == 'VIEW_3D') - area.spaces[0].shading.color_type = 'OBJECT' - return {'FINISHED'} + area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") + area.spaces[0].shading.color_type = "OBJECT" + return {"FINISHED"} class ResetObjectColours(bpy.types.Operator): - bl_idname = 'bim.reset_object_colours' - bl_label = 'Reset Colours' + bl_idname = "bim.reset_object_colours" + bl_label = "Reset Colours" def execute(self, context): for object in bpy.context.selected_objects: object.color = (1, 1, 1, 1) - return {'FINISHED'} + return {"FINISHED"} -class QAHelper(): +class QAHelper: @classmethod def append_to_scenario(cls, lines): filename = os.path.join( - bpy.context.scene.BIMProperties.features_dir, - bpy.context.scene.BIMProperties.features_file + '.feature') - if os.path.exists(filename+'~'): - os.remove(filename+'~') - os.rename(filename, filename+'~') - with open(filename, 'w') as destination: - with open(filename+'~', 'r') as source: + bpy.context.scene.BIMProperties.features_dir, bpy.context.scene.BIMProperties.features_file + ".feature" + ) + if os.path.exists(filename + "~"): + os.remove(filename + "~") + os.rename(filename, filename + "~") + with open(filename, "w") as destination: + with open(filename + "~", "r") as source: is_in_scenario = False for source_line in source: - if 'Scenario: 'in source_line \ - and bpy.context.scene.BIMProperties.scenario == source_line.strip()[len('Scenario: '):]: + if ( + "Scenario: " in source_line + and bpy.context.scene.BIMProperties.scenario == source_line.strip()[len("Scenario: ") :] + ): is_in_scenario = True elif is_in_scenario: for line in lines: - destination.write(line + '\n') + destination.write(line + "\n") is_in_scenario = False destination.write(source_line) - os.remove(filename+'~') + os.remove(filename + "~") class ApproveClass(bpy.types.Operator): - bl_idname = 'bim.approve_class' - bl_label = 'Approve Class' + bl_idname = "bim.approve_class" + bl_label = "Approve Class" def execute(self, context): lines = [] for object in bpy.context.selected_objects: - index = object.BIMObjectProperties.attributes.find('GlobalId') + index = object.BIMObjectProperties.attributes.find("GlobalId") if index != -1: - lines.append(' * The element {} is an {}'.format( - object.BIMObjectProperties.attributes[index].string_value, - object.name.split('/')[0])) + lines.append( + " * The element {} is an {}".format( + object.BIMObjectProperties.attributes[index].string_value, object.name.split("/")[0] + ) + ) QAHelper.append_to_scenario(lines) - return {'FINISHED'} + return {"FINISHED"} class RejectClass(bpy.types.Operator): - bl_idname = 'bim.reject_class' - bl_label = 'Reject Class' + bl_idname = "bim.reject_class" + bl_label = "Reject Class" def execute(self, context): lines = [] for object in bpy.context.selected_objects: - lines.append(' * The element {} is an {}'.format( - object.BIMObjectProperties.attributes[ - object.BIMObjectProperties.attributes.find('GlobalId')].string_value, - bpy.context.scene.BIMProperties.audit_ifc_class)) + lines.append( + " * The element {} is an {}".format( + object.BIMObjectProperties.attributes[ + object.BIMObjectProperties.attributes.find("GlobalId") + ].string_value, + bpy.context.scene.BIMProperties.audit_ifc_class, + ) + ) QAHelper.append_to_scenario(lines) - return {'FINISHED'} + return {"FINISHED"} class RejectElement(bpy.types.Operator): - bl_idname = 'bim.reject_element' - bl_label = 'Reject Element' + bl_idname = "bim.reject_element" + bl_label = "Reject Element" def execute(self, context): lines = [] for object in bpy.context.selected_objects: - lines.append(' * The element {} should not exist because {}'.format( - object.BIMObjectProperties.attributes[ - object.BIMObjectProperties.attributes.find('GlobalId')].string_value, - bpy.context.scene.BIMProperties.qa_reject_element_reason)) + lines.append( + " * The element {} should not exist because {}".format( + object.BIMObjectProperties.attributes[ + object.BIMObjectProperties.attributes.find("GlobalId") + ].string_value, + bpy.context.scene.BIMProperties.qa_reject_element_reason, + ) + ) QAHelper.append_to_scenario(lines) - return {'FINISHED'} + return {"FINISHED"} class GetBcfTopics(bpy.types.Operator): - bl_idname = 'bim.get_bcf_topics' - bl_label = 'Get BCF Topics' + bl_idname = "bim.get_bcf_topics" + bl_label = "Get BCF Topics" def execute(self, context): import bcfplugin + bcfplugin.openProject(bpy.context.scene.BCFProperties.bcf_file) bcf.BcfStore.topics = bcfplugin.getTopics() while len(bpy.context.scene.BCFProperties.topics) > 0: @@ -462,41 +489,41 @@ class GetBcfTopics(bpy.types.Operator): for topic in bcf.BcfStore.topics: new = bpy.context.scene.BCFProperties.topics.add() new.name = topic[0] - return {'FINISHED'} + return {"FINISHED"} class ViewBcfTopic(bpy.types.Operator): - bl_idname = 'bim.view_bcf_topic' - bl_label = 'Get BCF Topic' + bl_idname = "bim.view_bcf_topic" + bl_label = "Get BCF Topic" topic_guid: bpy.props.StringProperty() def execute(self, context): for index, topic in enumerate(bcf.BcfStore.topics): if str(topic[1].xmlId) == self.topic_guid: bpy.context.scene.BCFProperties.active_topic_index = index - return {'FINISHED'} + return {"FINISHED"} class ActivateBcfViewpoint(bpy.types.Operator): - bl_idname = 'bim.activate_bcf_viewpoint' - bl_label = 'Activate BCF Viewpoint' + bl_idname = "bim.activate_bcf_viewpoint" + bl_label = "Activate BCF Viewpoint" def execute(self, context): import bcfplugin topics = bcf.BcfStore.topics if not topics: - return {'FINISHED'} + return {"FINISHED"} topic = topics[bpy.context.scene.BCFProperties.active_topic_index][1] viewpoints = bcf.BcfStore.viewpoints if not viewpoints: - return {'FINISHED'} + return {"FINISHED"} viewpoint_reference = viewpoints[int(bpy.context.scene.BCFProperties.viewpoints)][1] viewpoint = viewpoint_reference.viewpoint - obj = bpy.data.objects.get('Viewpoint') + obj = bpy.data.objects.get("Viewpoint") if not obj: - obj = bpy.data.objects.new('Viewpoint', bpy.data.cameras.new('Viewpoint')) + obj = bpy.data.objects.new("Viewpoint", bpy.data.cameras.new("Viewpoint")) bpy.context.scene.collection.objects.link(obj) bpy.context.scene.camera = obj @@ -509,39 +536,37 @@ class ActivateBcfViewpoint(bpy.types.Operator): while len(obj.data.background_images) > 0: obj.data.background_images.remove(obj.data.background_images[0]) background = obj.data.background_images.new() - background.image = bpy.data.images.load(os.path.join( - bcfplugin.util.getBcfDir(), - str(topic.xmlId), - viewpoint_reference.snapshot.uri - )) + background.image = bpy.data.images.load( + os.path.join(bcfplugin.util.getBcfDir(), str(topic.xmlId), viewpoint_reference.snapshot.uri) + ) src_width = background.image.size[0] src_height = background.image.size[1] src_aspect = src_width / src_height if src_aspect > cam_aspect: - background.frame_method = 'FIT' + background.frame_method = "FIT" else: - background.frame_method = 'CROP' - background.display_depth = 'FRONT' - area = next(area for area in bpy.context.screen.areas if area.type == 'VIEW_3D') - area.spaces[0].region_3d.view_perspective = 'CAMERA' + background.frame_method = "CROP" + background.display_depth = "FRONT" + area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") + area.spaces[0].region_3d.view_perspective = "CAMERA" if viewpoint.oCamera: camera = viewpoint.oCamera - obj.data.type = 'ORTHO' + obj.data.type = "ORTHO" obj.data.ortho_scale = viewpoint.oCamera.viewWorldScale elif viewpoint.pCamera: camera = viewpoint.pCamera - obj.data.type = 'PERSP' + obj.data.type = "PERSP" if cam_aspect >= 1: obj.data.angle = radians(camera.fieldOfView) else: # https://blender.stackexchange.com/questions/23431/how-to-set-camera-horizontal-and-vertical-fov - obj.data.angle = 2 * atan((0.5 * cam_height) / (0.5 * cam_width / tan(radians(camera.fieldOfView)/2))) + obj.data.angle = 2 * atan((0.5 * cam_height) / (0.5 * cam_width / tan(radians(camera.fieldOfView) / 2))) self.set_viewpoint_components(viewpoint) - gp = bpy.data.grease_pencils.get('BCF') + gp = bpy.data.grease_pencils.get("BCF") if gp: bpy.data.grease_pencils.remove(gp) if viewpoint.lines: @@ -563,7 +588,7 @@ class ActivateBcfViewpoint(bpy.types.Operator): location = Vector((camera.viewPoint.x, camera.viewPoint.y, camera.viewPoint.z)) obj.matrix_world = rotation.to_4x4() obj.location = location - return {'FINISHED'} + return {"FINISHED"} def set_viewpoint_components(self, viewpoint): selected_global_ids = [s.ifcId for s in viewpoint.components.selection] @@ -574,7 +599,7 @@ class ActivateBcfViewpoint(bpy.types.Operator): global_id_colours.setdefault(component.ifcId, colouring.colour) for obj in bpy.data.objects: - global_id = obj.BIMObjectProperties.attributes.get('GlobalId') + global_id = obj.BIMObjectProperties.attributes.get("GlobalId") if not global_id: continue global_id = global_id.string_value @@ -584,9 +609,9 @@ class ActivateBcfViewpoint(bpy.types.Operator): if not is_visible: obj.hide_set(True) continue - if 'IfcSpace' in obj.name: + if "IfcSpace" in obj.name: is_visible = viewpoint.components.viewSetuphints.spacesVisible - elif 'IfcOpeningElement' in obj.name: + elif "IfcOpeningElement" in obj.name: is_visible = viewpoint.components.viewSetuphints.openingsVisible obj.hide_set(not is_visible) if not is_visible: @@ -596,39 +621,39 @@ class ActivateBcfViewpoint(bpy.types.Operator): obj.color = self.hex_to_rgb(global_id_colours[global_id]) def draw_lines(self, viewpoint): - gp = bpy.data.grease_pencils.new('BCF') + gp = bpy.data.grease_pencils.new("BCF") scene = bpy.context.scene scene.grease_pencil = gp scene.frame_set(1) - layer = gp.layers.new('BCF Annotation', set_active=True) + layer = gp.layers.new("BCF Annotation", set_active=True) layer.thickness = 3 layer.color = (1, 0, 0) frame = layer.frames.new(1) stroke = frame.strokes.new() - stroke.display_mode = '3DSPACE' - stroke.points.add(len(viewpoint.lines)*2) + stroke.display_mode = "3DSPACE" + stroke.points.add(len(viewpoint.lines) * 2) coords = [] for l in viewpoint.lines: coords.extend([l.start.x, l.start.y, l.start.z, l.end.x, l.end.y, l.end.z]) - stroke.points.foreach_set('co', coords) + stroke.points.foreach_set("co", coords) def create_clipping_planes(self, viewpoint): n = 0 for plane in viewpoint.clippingPlanes: bpy.ops.bim.add_section_plane() if n == 0: - obj = bpy.data.objects['Section'] + obj = bpy.data.objects["Section"] else: - obj = bpy.data.objects['Section.{:03d}'.format(n)] + obj = bpy.data.objects["Section.{:03d}".format(n)] obj.location = (plane.location.x, plane.location.y, plane.location.z) - obj.rotation_mode = 'QUATERNION' - obj.rotation_quaternion = Vector( - (plane.direction.x, plane.direction.y, plane.direction.z) - ).to_track_quat('Z', 'Y') + obj.rotation_mode = "QUATERNION" + obj.rotation_quaternion = Vector((plane.direction.x, plane.direction.y, plane.direction.z)).to_track_quat( + "Z", "Y" + ) n += 1 def delete_clipping_planes(self): - collection = bpy.data.collections.get('Sections') + collection = bpy.data.collections.get("Sections") if not collection: return for section in collection.objects: @@ -636,28 +661,25 @@ class ActivateBcfViewpoint(bpy.types.Operator): bpy.ops.bim.remove_section_plane() def delete_bitmaps(self): - collection = bpy.data.collections.get('Bitmaps') + collection = bpy.data.collections.get("Bitmaps") if not collection: - collection = bpy.data.collections.new('Bitmaps') + collection = bpy.data.collections.new("Bitmaps") bpy.context.scene.collection.children.link(collection) for bitmap in collection.objects: bpy.data.objects.remove(bitmap) def create_bitmaps(self, viewpoint): import bcfplugin + topics = bcf.BcfStore.topics topic = topics[bpy.context.scene.BCFProperties.active_topic_index][1] - collection = bpy.data.collections.get('Bitmaps') + collection = bpy.data.collections.get("Bitmaps") if not collection: - collection = bpy.data.collections.new('Bitmaps') + collection = bpy.data.collections.new("Bitmaps") for bitmap in viewpoint.bitmaps: - obj = bpy.data.objects.new('Bitmap', None) - obj.empty_display_type = 'IMAGE' - image = bpy.data.images.load(os.path.join( - bcfplugin.util.getBcfDir(), - str(topic.xmlId), - bitmap.reference - )) + obj = bpy.data.objects.new("Bitmap", None) + obj.empty_display_type = "IMAGE" + image = bpy.data.images.load(os.path.join(bcfplugin.util.getBcfDir(), str(topic.xmlId), bitmap.reference)) src_width = image.size[0] src_height = image.size[1] if src_height > src_width: @@ -668,104 +690,103 @@ class ActivateBcfViewpoint(bpy.types.Operator): y = Vector((bitmap.upVector.x, bitmap.upVector.y, bitmap.upVector.z)) z = Vector((bitmap.normal.x, bitmap.normal.y, bitmap.normal.z)) x = y.cross(z) - obj.matrix_world = Matrix([ - [x[0], y[0], z[0], 0], - [x[1], y[1], z[1], 0], - [x[2], y[2], z[2], 0], - [0, 0, 0, 1] - ]) + obj.matrix_world = Matrix( + [[x[0], y[0], z[0], 0], [x[1], y[1], z[1], 0], [x[2], y[2], z[2], 0], [0, 0, 0, 1]] + ) obj.location = (bitmap.location.x, bitmap.location.y, bitmap.location.z) collection.objects.link(obj) def hex_to_rgb(self, value): - value = value.lstrip('#') + value = value.lstrip("#") lv = len(value) - t = tuple(int(value[i:i+lv//3], 16) for i in range(0, lv, lv//3)) - return [t[0]/255., t[1]/255., t[2]/255., 1] + t = tuple(int(value[i : i + lv // 3], 16) for i in range(0, lv, lv // 3)) + return [t[0] / 255.0, t[1] / 255.0, t[2] / 255.0, 1] + class OpenBcfFileReference(bpy.types.Operator): - bl_idname = 'bim.open_bcf_file_reference' - bl_label = 'Open BCF File Reference' + bl_idname = "bim.open_bcf_file_reference" + bl_label = "Open BCF File Reference" data: bpy.props.StringProperty() def execute(self, context): - if '/' not in self.data: + if "/" not in self.data: webbrowser.open(bpy.context.scene.BCFProperties.topic_files[int(self.data)].reference) - return {'FINISHED'} + return {"FINISHED"} import bcfplugin - topic_guid, index = self.data.split('/') - path = os.path.join( - bcfplugin.util.getBcfDir(), topic_guid) + + topic_guid, index = self.data.split("/") + path = os.path.join(bcfplugin.util.getBcfDir(), topic_guid) # bpy.context.scene.BCFProperties.topic_files[int(index)].reference) # TODO - maybe allow immediate importing? webbrowser.open(path) - return {'FINISHED'} + return {"FINISHED"} class OpenBcfReferenceLink(bpy.types.Operator): - bl_idname = 'bim.open_bcf_reference_link' - bl_label = 'Open BCF Reference Link' + bl_idname = "bim.open_bcf_reference_link" + bl_label = "Open BCF Reference Link" index: bpy.props.IntProperty() def execute(self, context): webbrowser.open(bpy.context.scene.BCFProperties.topic_links[self.index].name) - return {'FINISHED'} + return {"FINISHED"} class OpenBcfBimSnippetSchema(bpy.types.Operator): - bl_idname = 'bim.open_bcf_bim_snippet_schema' - bl_label = 'Open BCF BIM Snippet Schema' + bl_idname = "bim.open_bcf_bim_snippet_schema" + bl_label = "Open BCF BIM Snippet Schema" def execute(self, context): webbrowser.open(bpy.context.scene.BCFProperties.topic_snippet_schema) - return {'FINISHED'} + return {"FINISHED"} class OpenBcfBimSnippetReference(bpy.types.Operator): - bl_idname = 'bim.open_bcf_bim_snippet_reference' - bl_label = 'Open BCF BIM Snippet Reference' + bl_idname = "bim.open_bcf_bim_snippet_reference" + bl_label = "Open BCF BIM Snippet Reference" topic_guid: bpy.props.StringProperty() def execute(self, context): import bcfplugin + if bpy.context.scene.BCFProperties.topic_snippet_is_external: webbrowser.open(bpy.context.scene.BCFProperties.topic_snippet_reference) - return {'FINISHED'} - webbrowser.open('file://' + os.path.join( - bcfplugin.util.getBcfDir(), - self.topic_guid, - bpy.context.scene.BCFProperties.topic_snippet_reference - )) - return {'FINISHED'} + return {"FINISHED"} + webbrowser.open( + "file://" + + os.path.join( + bcfplugin.util.getBcfDir(), self.topic_guid, bpy.context.scene.BCFProperties.topic_snippet_reference + ) + ) + return {"FINISHED"} class OpenBcfDocumentReference(bpy.types.Operator): - bl_idname = 'bim.open_bcf_document_reference' - bl_label = 'Open BCF Document Reference' + bl_idname = "bim.open_bcf_document_reference" + bl_label = "Open BCF Document Reference" data: bpy.props.StringProperty() def execute(self, context): import bcfplugin - topic_guid, index = self.data.split('/') + + topic_guid, index = self.data.split("/") doc = bpy.context.scene.BCFProperties.topic_document_references[int(index)] uri = doc.name if doc.is_external: webbrowser.open(uri) - return {'FINISHED'} - webbrowser.open('file://' + os.path.join( - bcfplugin.util.getBcfDir(), - topic_guid, uri)) - return {'FINISHED'} + return {"FINISHED"} + webbrowser.open("file://" + os.path.join(bcfplugin.util.getBcfDir(), topic_guid, uri)) + return {"FINISHED"} class SelectAudited(bpy.types.Operator): - bl_idname = 'bim.select_audited' - bl_label = 'Select Audited' + bl_idname = "bim.select_audited" + bl_label = "Select Audited" def execute(self, context): audited_global_ids = [] - for filename in Path(bpy.context.scene.BIMProperties.features_dir).glob('*.feature'): - with open(filename, 'r') as feature_file: + for filename in Path(bpy.context.scene.BIMProperties.features_dir).glob("*.feature"): + with open(filename, "r") as feature_file: lines = feature_file.readlines() for line in lines: words = line.strip().split() @@ -773,29 +794,29 @@ class SelectAudited(bpy.types.Operator): if self.is_a_global_id(word): audited_global_ids.append(word) for object in bpy.context.visible_objects: - index = object.BIMObjectProperties.attributes.find('GlobalId') - if index != -1 \ - and object.BIMObjectProperties.attributes[index].string_value in audited_global_ids: + index = object.BIMObjectProperties.attributes.find("GlobalId") + if index != -1 and object.BIMObjectProperties.attributes[index].string_value in audited_global_ids: object.select_set(True) - return {'FINISHED'} + return {"FINISHED"} def is_a_global_id(self, word): - return word[0] in ['0', '1', '2', '3'] and len(word) == 22 + return word[0] in ["0", "1", "2", "3"] and len(word) == 22 + class QuickProjectSetup(bpy.types.Operator): - bl_idname = 'bim.quick_project_setup' - bl_label = 'Quick Project Setup' + bl_idname = "bim.quick_project_setup" + bl_label = "Quick Project Setup" def execute(self, context): - project = bpy.data.collections.new('IfcProject/My Project') - site = bpy.data.collections.new('IfcSite/My Site') - building = bpy.data.collections.new('IfcBuilding/My Building') - building_storey = bpy.data.collections.new('IfcBuildingStorey/Ground Floor') + project = bpy.data.collections.new("IfcProject/My Project") + site = bpy.data.collections.new("IfcSite/My Site") + building = bpy.data.collections.new("IfcBuilding/My Building") + building_storey = bpy.data.collections.new("IfcBuildingStorey/Ground Floor") - project_obj = bpy.data.objects.new('IfcProject/My Project', None) - site_obj = bpy.data.objects.new('IfcSite/My Site', None) - building_obj = bpy.data.objects.new('IfcBuilding/My Building', None) - building_storey_obj = bpy.data.objects.new('IfcBuildingStorey/Ground Floor', None) + project_obj = bpy.data.objects.new("IfcProject/My Project", None) + site_obj = bpy.data.objects.new("IfcSite/My Site", None) + building_obj = bpy.data.objects.new("IfcBuilding/My Building", None) + building_storey_obj = bpy.data.objects.new("IfcBuildingStorey/Ground Floor", None) bpy.context.scene.collection.children.link(project) project.children.link(site) @@ -806,279 +827,297 @@ class QuickProjectSetup(bpy.types.Operator): site.objects.link(site_obj) building.objects.link(building_obj) building_storey.objects.link(building_storey_obj) - return {'FINISHED'} + return {"FINISHED"} class AddQto(bpy.types.Operator): - bl_idname = 'bim.add_qto' - bl_label = 'Add Qto' + bl_idname = "bim.add_qto" + bl_label = "Add Qto" def execute(self, context): name = bpy.context.active_object.BIMObjectProperties.qto_name if name not in schema.ifc.qtos: - return {'FINISHED'} + return {"FINISHED"} qto = bpy.context.active_object.BIMObjectProperties.qtos.add() qto.name = name - for prop_name in schema.ifc.qtos[name]['HasPropertyTemplates'].keys(): + for prop_name in schema.ifc.qtos[name]["HasPropertyTemplates"].keys(): prop = qto.properties.add() prop.name = prop_name - return {'FINISHED'} + return {"FINISHED"} class AddPset(bpy.types.Operator): - bl_idname = 'bim.add_pset' - bl_label = 'Add Pset' + bl_idname = "bim.add_pset" + bl_label = "Add Pset" def execute(self, context): pset_name = bpy.context.active_object.BIMObjectProperties.pset_name if pset_name not in schema.ifc.psets: - return {'FINISHED'} + return {"FINISHED"} pset = bpy.context.active_object.BIMObjectProperties.psets.add() pset.name = pset_name - for prop_name in schema.ifc.psets[pset_name]['HasPropertyTemplates'].keys(): + for prop_name in schema.ifc.psets[pset_name]["HasPropertyTemplates"].keys(): prop = pset.properties.add() prop.name = prop_name - return {'FINISHED'} + return {"FINISHED"} class RemovePset(bpy.types.Operator): - bl_idname = 'bim.remove_pset' - bl_label = 'Remove Pset' + bl_idname = "bim.remove_pset" + bl_label = "Remove Pset" pset_index: bpy.props.IntProperty() def execute(self, context): bpy.context.active_object.BIMObjectProperties.psets.remove(self.pset_index) - return {'FINISHED'} + return {"FINISHED"} class RemoveQto(bpy.types.Operator): - bl_idname = 'bim.remove_qto' - bl_label = 'Remove Qto' + bl_idname = "bim.remove_qto" + bl_label = "Remove Qto" index: bpy.props.IntProperty() def execute(self, context): bpy.context.active_object.BIMObjectProperties.qtos.remove(self.index) - return {'FINISHED'} + return {"FINISHED"} class AddMaterialPset(bpy.types.Operator): - bl_idname = 'bim.add_material_pset' - bl_label = 'Add Material Pset' + bl_idname = "bim.add_material_pset" + bl_label = "Add Material Pset" def execute(self, context): pset = bpy.context.active_object.active_material.BIMMaterialProperties.psets.add() pset.name = bpy.context.active_object.active_material.BIMMaterialProperties.available_material_psets - return {'FINISHED'} + return {"FINISHED"} class RemoveMaterialPset(bpy.types.Operator): - bl_idname = 'bim.remove_material_pset' - bl_label = 'Remove Pset' + bl_idname = "bim.remove_material_pset" + bl_label = "Remove Pset" pset_index: bpy.props.IntProperty() def execute(self, context): bpy.context.active_object.active_material.BIMMaterialProperties.psets.remove(self.pset_index) - return {'FINISHED'} + return {"FINISHED"} class AddConstraint(bpy.types.Operator): - bl_idname = 'bim.add_constraint' - bl_label = 'Add Constraint' + bl_idname = "bim.add_constraint" + bl_label = "Add Constraint" def execute(self, context): constraint = bpy.context.scene.BIMProperties.constraints.add() - constraint.name = 'New Constraint' - return {'FINISHED'} + constraint.name = "New Constraint" + return {"FINISHED"} class RemoveConstraint(bpy.types.Operator): - bl_idname = 'bim.remove_constraint' - bl_label = 'Remove Constraint' + bl_idname = "bim.remove_constraint" + bl_label = "Remove Constraint" index: bpy.props.IntProperty() def execute(self, context): bpy.context.scene.BIMProperties.constraints.remove(self.index) - return {'FINISHED'} + return {"FINISHED"} class AssignConstraint(bpy.types.Operator): - bl_idname = 'bim.assign_constraint' - bl_label = 'Assign Constraint' + bl_idname = "bim.assign_constraint" + bl_label = "Assign Constraint" def execute(self, context): - identification = bpy.context.scene.BIMProperties.constraints[bpy.context.scene.BIMProperties.active_constraint_index].name + identification = bpy.context.scene.BIMProperties.constraints[ + bpy.context.scene.BIMProperties.active_constraint_index + ].name for obj in bpy.context.selected_objects: if obj.BIMObjectProperties.constraints.get(identification): continue constraint = obj.BIMObjectProperties.constraints.add() constraint.name = identification - return {'FINISHED'} + return {"FINISHED"} class UnassignConstraint(bpy.types.Operator): - bl_idname = 'bim.unassign_constraint' - bl_label = 'Unassign Constraint' + bl_idname = "bim.unassign_constraint" + bl_label = "Unassign Constraint" def execute(self, context): - identification = bpy.context.scene.BIMProperties.constraints[bpy.context.scene.BIMProperties.active_constraint_index].name + identification = bpy.context.scene.BIMProperties.constraints[ + bpy.context.scene.BIMProperties.active_constraint_index + ].name for obj in bpy.context.selected_objects: index = obj.BIMObjectProperties.constraints.find(identification) if index >= 0: obj.BIMObjectProperties.constraints.remove(index) - return {'FINISHED'} + return {"FINISHED"} class RemoveObjectConstraint(bpy.types.Operator): - bl_idname = 'bim.remove_object_constraint' - bl_label = 'Remove Object Constraint' + bl_idname = "bim.remove_object_constraint" + bl_label = "Remove Object Constraint" index: bpy.props.IntProperty() def execute(self, context): bpy.context.active_object.BIMObjectProperties.constraints.remove(self.index) - return {'FINISHED'} + return {"FINISHED"} class AddPerson(bpy.types.Operator): - bl_idname = 'bim.add_person' - bl_label = 'Add Person' + bl_idname = "bim.add_person" + bl_label = "Add Person" def execute(self, context): new = bpy.context.scene.BIMProperties.people.add() - new.name = 'New Person' - return {'FINISHED'} + new.name = "New Person" + return {"FINISHED"} class RemovePerson(bpy.types.Operator): - bl_idname = 'bim.remove_person' - bl_label = 'Remove Person' + bl_idname = "bim.remove_person" + bl_label = "Remove Person" index: bpy.props.IntProperty() def execute(self, context): bpy.context.scene.BIMProperties.people.remove(self.index) - return {'FINISHED'} + return {"FINISHED"} class AddPersonAddress(bpy.types.Operator): - bl_idname = 'bim.add_person_address' - bl_label = 'Add Person Address' + bl_idname = "bim.add_person_address" + bl_label = "Add Person Address" def execute(self, context): - new = bpy.context.scene.BIMProperties.people[bpy.context.scene.BIMProperties.active_person_index].addresses.add() - new.name = 'IfcPostalAddress' - return {'FINISHED'} + new = bpy.context.scene.BIMProperties.people[ + bpy.context.scene.BIMProperties.active_person_index + ].addresses.add() + new.name = "IfcPostalAddress" + return {"FINISHED"} class RemovePersonAddress(bpy.types.Operator): - bl_idname = 'bim.remove_person_address' - bl_label = 'Remove Person Address' + bl_idname = "bim.remove_person_address" + bl_label = "Remove Person Address" index: bpy.props.IntProperty() def execute(self, context): - bpy.context.scene.BIMProperties.people[bpy.context.scene.BIMProperties.active_person_index].addresses.remove(self.index) - return {'FINISHED'} + bpy.context.scene.BIMProperties.people[bpy.context.scene.BIMProperties.active_person_index].addresses.remove( + self.index + ) + return {"FINISHED"} class AddPersonRole(bpy.types.Operator): - bl_idname = 'bim.add_person_role' - bl_label = 'Add Person Role' + bl_idname = "bim.add_person_role" + bl_label = "Add Person Role" def execute(self, context): new = bpy.context.scene.BIMProperties.people[bpy.context.scene.BIMProperties.active_person_index].roles.add() - return {'FINISHED'} + return {"FINISHED"} class RemovePersonRole(bpy.types.Operator): - bl_idname = 'bim.remove_person_role' - bl_label = 'Remove Person Role' + bl_idname = "bim.remove_person_role" + bl_label = "Remove Person Role" index: bpy.props.IntProperty() def execute(self, context): - bpy.context.scene.BIMProperties.people[bpy.context.scene.BIMProperties.active_person_index].roles.remove(self.index) - return {'FINISHED'} + bpy.context.scene.BIMProperties.people[bpy.context.scene.BIMProperties.active_person_index].roles.remove( + self.index + ) + return {"FINISHED"} class AddOrganisation(bpy.types.Operator): - bl_idname = 'bim.add_organisation' - bl_label = 'Add Organisation' + bl_idname = "bim.add_organisation" + bl_label = "Add Organisation" def execute(self, context): new = bpy.context.scene.BIMProperties.organisations.add() - new.name = 'New Organisation' - return {'FINISHED'} + new.name = "New Organisation" + return {"FINISHED"} class RemoveOrganisation(bpy.types.Operator): - bl_idname = 'bim.remove_organisation' - bl_label = 'Remove Organisation' + bl_idname = "bim.remove_organisation" + bl_label = "Remove Organisation" index: bpy.props.IntProperty() def execute(self, context): bpy.context.scene.BIMProperties.organisations.remove(self.index) - return {'FINISHED'} + return {"FINISHED"} class AddOrganisationAddress(bpy.types.Operator): - bl_idname = 'bim.add_organisation_address' - bl_label = 'Add Organisation Address' + bl_idname = "bim.add_organisation_address" + bl_label = "Add Organisation Address" def execute(self, context): - new = bpy.context.scene.BIMProperties.organisations[bpy.context.scene.BIMProperties.active_organisation_index].addresses.add() - new.name = 'IfcPostalAddress' - return {'FINISHED'} + new = bpy.context.scene.BIMProperties.organisations[ + bpy.context.scene.BIMProperties.active_organisation_index + ].addresses.add() + new.name = "IfcPostalAddress" + return {"FINISHED"} class RemoveOrganisationAddress(bpy.types.Operator): - bl_idname = 'bim.remove_organisation_address' - bl_label = 'Remove Organisation Address' + bl_idname = "bim.remove_organisation_address" + bl_label = "Remove Organisation Address" index: bpy.props.IntProperty() def execute(self, context): - bpy.context.scene.BIMProperties.organisations[bpy.context.scene.BIMProperties.active_organisation_index].addresses.remove(self.index) - return {'FINISHED'} + bpy.context.scene.BIMProperties.organisations[ + bpy.context.scene.BIMProperties.active_organisation_index + ].addresses.remove(self.index) + return {"FINISHED"} class AddOrganisationRole(bpy.types.Operator): - bl_idname = 'bim.add_organisation_role' - bl_label = 'Add Organisation Role' + bl_idname = "bim.add_organisation_role" + bl_label = "Add Organisation Role" def execute(self, context): - new = bpy.context.scene.BIMProperties.organisations[bpy.context.scene.BIMProperties.active_organisation_index].roles.add() - return {'FINISHED'} + new = bpy.context.scene.BIMProperties.organisations[ + bpy.context.scene.BIMProperties.active_organisation_index + ].roles.add() + return {"FINISHED"} class RemoveOrganisationRole(bpy.types.Operator): - bl_idname = 'bim.remove_organisation_role' - bl_label = 'Remove Organisation Role' + bl_idname = "bim.remove_organisation_role" + bl_label = "Remove Organisation Role" index: bpy.props.IntProperty() def execute(self, context): - bpy.context.scene.BIMProperties.organisations[bpy.context.scene.BIMProperties.active_organisation_index].roles.remove(self.index) - return {'FINISHED'} + bpy.context.scene.BIMProperties.organisations[ + bpy.context.scene.BIMProperties.active_organisation_index + ].roles.remove(self.index) + return {"FINISHED"} class AddDocumentInformation(bpy.types.Operator): - bl_idname = 'bim.add_document_information' - bl_label = 'Add Document Information' + bl_idname = "bim.add_document_information" + bl_label = "Add Document Information" def execute(self, context): info = bpy.context.scene.BIMProperties.document_information.add() - info.name = 'New Document ID' - return {'FINISHED'} + info.name = "New Document ID" + return {"FINISHED"} class RemoveDocumentInformation(bpy.types.Operator): - bl_idname = 'bim.remove_document_information' - bl_label = 'Remove Document Information' + bl_idname = "bim.remove_document_information" + bl_label = "Remove Document Information" index: bpy.props.IntProperty() def execute(self, context): bpy.context.scene.BIMProperties.document_information.remove(self.index) - return {'FINISHED'} + return {"FINISHED"} class AssignDocumentInformation(bpy.types.Operator): - bl_idname = 'bim.assign_document_information' - bl_label = 'Assign Document Information' + bl_idname = "bim.assign_document_information" + bl_label = "Assign Document Information" index: bpy.props.IntProperty() def execute(self, context): @@ -1087,195 +1126,205 @@ class AssignDocumentInformation(bpy.types.Operator): info = bpy.context.scene.BIMProperties.document_information if index < len(info): reference.referenced_document = info[index].name - return {'FINISHED'} + return {"FINISHED"} class AddDocumentReference(bpy.types.Operator): - bl_idname = 'bim.add_document_reference' - bl_label = 'Add Document Reference' + bl_idname = "bim.add_document_reference" + bl_label = "Add Document Reference" def execute(self, context): document = bpy.context.scene.BIMProperties.document_references.add() - document.name = 'New Document Reference ID' - return {'FINISHED'} + document.name = "New Document Reference ID" + return {"FINISHED"} class RemoveDocumentReference(bpy.types.Operator): - bl_idname = 'bim.remove_document_reference' - bl_label = 'Remove Document Reference' + bl_idname = "bim.remove_document_reference" + bl_label = "Remove Document Reference" index: bpy.props.IntProperty() def execute(self, context): bpy.context.scene.BIMProperties.document_references.remove(self.index) - return {'FINISHED'} + return {"FINISHED"} class RemoveObjectDocumentReference(bpy.types.Operator): - bl_idname = 'bim.remove_object_document_reference' - bl_label = 'Remove Object Document Reference' + bl_idname = "bim.remove_object_document_reference" + bl_label = "Remove Object Document Reference" index: bpy.props.IntProperty() def execute(self, context): bpy.context.active_object.BIMObjectProperties.document_references.remove(self.index) - return {'FINISHED'} + return {"FINISHED"} class AssignDocumentReference(bpy.types.Operator): - bl_idname = 'bim.assign_document_reference' - bl_label = 'Assign Document Reference' + bl_idname = "bim.assign_document_reference" + bl_label = "Assign Document Reference" def execute(self, context): - identification = bpy.context.scene.BIMProperties.document_references[bpy.context.scene.BIMProperties.active_document_reference_index].name + identification = bpy.context.scene.BIMProperties.document_references[ + bpy.context.scene.BIMProperties.active_document_reference_index + ].name for obj in bpy.context.selected_objects: if obj.BIMObjectProperties.document_references.get(identification): continue reference = obj.BIMObjectProperties.document_references.add() reference.name = identification - return {'FINISHED'} + return {"FINISHED"} class UnassignDocumentReference(bpy.types.Operator): - bl_idname = 'bim.unassign_document_reference' - bl_label = 'Unassign Document Reference' + bl_idname = "bim.unassign_document_reference" + bl_label = "Unassign Document Reference" def execute(self, context): - identification = bpy.context.scene.BIMProperties.document_references[bpy.context.scene.BIMProperties.active_document_reference_index].name + identification = bpy.context.scene.BIMProperties.document_references[ + bpy.context.scene.BIMProperties.active_document_reference_index + ].name for obj in bpy.context.selected_objects: index = obj.BIMObjectProperties.document_references.find(identification) if index >= 0: obj.BIMObjectProperties.document_references.remove(index) - return {'FINISHED'} + return {"FINISHED"} class RemoveObjectDocumentReference(bpy.types.Operator): - bl_idname = 'bim.remove_object_document_reference' - bl_label = 'Remove Object Document Reference' + bl_idname = "bim.remove_object_document_reference" + bl_label = "Remove Object Document Reference" index: bpy.props.IntProperty() def execute(self, context): bpy.context.active_object.BIMObjectProperties.document_references.remove(self.index) - return {'FINISHED'} + return {"FINISHED"} + class GenerateGlobalId(bpy.types.Operator): - bl_idname = 'bim.generate_global_id' - bl_label = 'Regenerate GlobalId' + bl_idname = "bim.generate_global_id" + bl_label = "Regenerate GlobalId" def execute(self, context): - index = bpy.context.active_object.BIMObjectProperties.attributes.find('GlobalId') + index = bpy.context.active_object.BIMObjectProperties.attributes.find("GlobalId") if index >= 0: global_id = bpy.context.active_object.BIMObjectProperties.attributes[index] else: global_id = bpy.context.active_object.BIMObjectProperties.attributes.add() - global_id.name = 'GlobalId' - global_id.data_type = 'string' + global_id.name = "GlobalId" + global_id.data_type = "string" global_id.string_value = ifcopenshell.guid.new() - return {'FINISHED'} + return {"FINISHED"} + class AddAttribute(bpy.types.Operator): - bl_idname = 'bim.add_attribute' - bl_label = 'Add Attribute' + bl_idname = "bim.add_attribute" + bl_label = "Add Attribute" def execute(self, context): if bpy.context.active_object.BIMObjectProperties.applicable_attributes: attribute = bpy.context.active_object.BIMObjectProperties.attributes.add() attribute.name = bpy.context.active_object.BIMObjectProperties.applicable_attributes - if attribute.name == 'GlobalId': + if attribute.name == "GlobalId": attribute.string_value = ifcopenshell.guid.new() - return {'FINISHED'} + return {"FINISHED"} class AddMaterialAttribute(bpy.types.Operator): - bl_idname = 'bim.add_material_attribute' - bl_label = 'Add Material Attribute' + bl_idname = "bim.add_material_attribute" + bl_label = "Add Material Attribute" def execute(self, context): if bpy.context.active_object.active_material.BIMMaterialProperties.applicable_attributes: attribute = bpy.context.active_object.active_material.BIMMaterialProperties.attributes.add() attribute.name = bpy.context.active_object.active_material.BIMMaterialProperties.applicable_attributes - return {'FINISHED'} + return {"FINISHED"} class RemoveAttribute(bpy.types.Operator): - bl_idname = 'bim.remove_attribute' - bl_label = 'Remove Attribute' + bl_idname = "bim.remove_attribute" + bl_label = "Remove Attribute" attribute_index: bpy.props.IntProperty() def execute(self, context): bpy.context.active_object.BIMObjectProperties.attributes.remove(self.attribute_index) - return {'FINISHED'} + return {"FINISHED"} class RemoveMaterialAttribute(bpy.types.Operator): - bl_idname = 'bim.remove_material_attribute' - bl_label = 'Remove Material Attribute' + bl_idname = "bim.remove_material_attribute" + bl_label = "Remove Material Attribute" attribute_index: bpy.props.IntProperty() def execute(self, context): bpy.context.active_object.active_material.BIMMaterialProperties.attributes.remove(self.attribute_index) - return {'FINISHED'} + return {"FINISHED"} class AddSweptSolid(bpy.types.Operator): - bl_idname = 'bim.add_swept_solid' - bl_label = 'Add Swept Solid' + bl_idname = "bim.add_swept_solid" + bl_label = "Add Swept Solid" def execute(self, context): swept_solids = bpy.context.active_object.data.BIMMeshProperties.swept_solids swept_solid = swept_solids.add() - swept_solid.name = 'Swept Solid {}'.format(len(swept_solids)) - return {'FINISHED'} + swept_solid.name = "Swept Solid {}".format(len(swept_solids)) + return {"FINISHED"} + class RemoveSweptSolid(bpy.types.Operator): - bl_idname = 'bim.remove_swept_solid' - bl_label = 'Remove Swept Solid' + bl_idname = "bim.remove_swept_solid" + bl_label = "Remove Swept Solid" index: bpy.props.IntProperty() def execute(self, context): bpy.context.active_object.data.BIMMeshProperties.swept_solids.remove(self.index) - return {'FINISHED'} + return {"FINISHED"} + class AssignSweptSolidOuterCurve(bpy.types.Operator): - bl_idname = 'bim.assign_swept_solid_outer_curve' - bl_label = 'Assign Outer Curve' + bl_idname = "bim.assign_swept_solid_outer_curve" + bl_label = "Assign Outer Curve" index: bpy.props.IntProperty() def execute(self, context): - if bpy.context.mode != 'EDIT_MESH': - return {'FINISHED'} - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.mode_set(mode='EDIT') + if bpy.context.mode != "EDIT_MESH": + return {"FINISHED"} + bpy.ops.object.mode_set(mode="OBJECT") + bpy.ops.object.mode_set(mode="EDIT") vertices = [v.index for v in bpy.context.active_object.data.vertices if v.select == True] bpy.context.active_object.data.BIMMeshProperties.swept_solids[self.index].outer_curve = json.dumps(vertices) - return {'FINISHED'} + return {"FINISHED"} + class SelectSweptSolidOuterCurve(bpy.types.Operator): - bl_idname = 'bim.select_swept_solid_outer_curve' - bl_label = 'Select Outer Curve' + bl_idname = "bim.select_swept_solid_outer_curve" + bl_label = "Select Outer Curve" index: bpy.props.IntProperty() def execute(self, context): - if bpy.context.mode != 'EDIT_MESH': - return {'FINISHED'} - bpy.ops.object.mode_set(mode='OBJECT') + if bpy.context.mode != "EDIT_MESH": + return {"FINISHED"} + bpy.ops.object.mode_set(mode="OBJECT") outer_curve = bpy.context.active_object.data.BIMMeshProperties.swept_solids[self.index].outer_curve if not outer_curve: - return {'FINISHED'} + return {"FINISHED"} indices = json.loads(outer_curve) for index in indices: bpy.context.active_object.data.vertices[index].select = True - bpy.ops.object.mode_set(mode='EDIT') - return {'FINISHED'} + bpy.ops.object.mode_set(mode="EDIT") + return {"FINISHED"} + class AddSweptSolidInnerCurve(bpy.types.Operator): - bl_idname = 'bim.add_swept_solid_inner_curve' - bl_label = 'Add Inner Curve' + bl_idname = "bim.add_swept_solid_inner_curve" + bl_label = "Add Inner Curve" index: bpy.props.IntProperty() def execute(self, context): - if bpy.context.mode != 'EDIT_MESH': - return {'FINISHED'} - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.mode_set(mode='EDIT') + if bpy.context.mode != "EDIT_MESH": + return {"FINISHED"} + bpy.ops.object.mode_set(mode="OBJECT") + bpy.ops.object.mode_set(mode="EDIT") vertices = [v.index for v in bpy.context.active_object.data.vertices if v.select == True] swept_solid = bpy.context.active_object.data.BIMMeshProperties.swept_solids[self.index] if swept_solid.inner_curves: @@ -1284,58 +1333,62 @@ class AddSweptSolidInnerCurve(bpy.types.Operator): curves = [] curves.append(vertices) swept_solid.inner_curves = json.dumps(curves) - return {'FINISHED'} + return {"FINISHED"} + class SelectSweptSolidInnerCurves(bpy.types.Operator): - bl_idname = 'bim.select_swept_solid_inner_curves' - bl_label = 'Select Inner Curves' + bl_idname = "bim.select_swept_solid_inner_curves" + bl_label = "Select Inner Curves" index: bpy.props.IntProperty() def execute(self, context): - if bpy.context.mode != 'EDIT_MESH': - return {'FINISHED'} - bpy.ops.object.mode_set(mode='OBJECT') + if bpy.context.mode != "EDIT_MESH": + return {"FINISHED"} + bpy.ops.object.mode_set(mode="OBJECT") inner_curves = bpy.context.active_object.data.BIMMeshProperties.swept_solids[self.index].inner_curves if not inner_curves: - return {'FINISHED'} + return {"FINISHED"} curves = json.loads(inner_curves) for curve in curves: for index in curve: bpy.context.active_object.data.vertices[index].select = True - bpy.ops.object.mode_set(mode='EDIT') - return {'FINISHED'} + bpy.ops.object.mode_set(mode="EDIT") + return {"FINISHED"} + class AssignSweptSolidExtrusion(bpy.types.Operator): - bl_idname = 'bim.assign_swept_solid_extrusion' - bl_label = 'Assign Extrusion' + bl_idname = "bim.assign_swept_solid_extrusion" + bl_label = "Assign Extrusion" index: bpy.props.IntProperty() def execute(self, context): - if bpy.context.mode != 'EDIT_MESH': - return {'FINISHED'} - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.mode_set(mode='EDIT') + if bpy.context.mode != "EDIT_MESH": + return {"FINISHED"} + bpy.ops.object.mode_set(mode="OBJECT") + bpy.ops.object.mode_set(mode="EDIT") vertices = [v.index for v in bpy.context.active_object.data.vertices if v.select == True] bpy.context.active_object.data.BIMMeshProperties.swept_solids[self.index].extrusion = json.dumps(vertices) - return {'FINISHED'} + return {"FINISHED"} + class SelectSweptSolidExtrusion(bpy.types.Operator): - bl_idname = 'bim.select_swept_solid_extrusion' - bl_label = 'Select Extrusion' + bl_idname = "bim.select_swept_solid_extrusion" + bl_label = "Select Extrusion" index: bpy.props.IntProperty() def execute(self, context): - if bpy.context.mode != 'EDIT_MESH': - return {'FINISHED'} - bpy.ops.object.mode_set(mode='OBJECT') + if bpy.context.mode != "EDIT_MESH": + return {"FINISHED"} + bpy.ops.object.mode_set(mode="OBJECT") extrusion = bpy.context.active_object.data.BIMMeshProperties.swept_solids[self.index].extrusion if not extrusion: - return {'FINISHED'} + return {"FINISHED"} indices = json.loads(extrusion) for index in indices: bpy.context.active_object.data.vertices[index].select = True - bpy.ops.object.mode_set(mode='EDIT') - return {'FINISHED'} + bpy.ops.object.mode_set(mode="EDIT") + return {"FINISHED"} + class SelectExternalMaterialDir(bpy.types.Operator): bl_idname = "bim.select_external_material_dir" @@ -1344,11 +1397,11 @@ class SelectExternalMaterialDir(bpy.types.Operator): def execute(self, context): bpy.context.active_object.active_material.BIMMaterialProperties.location = self.filepath - return {'FINISHED'} + return {"FINISHED"} def invoke(self, context, event): context.window_manager.fileselect_add(self) - return {'RUNNING_MODAL'} + return {"RUNNING_MODAL"} class SelectCobieIfcFile(bpy.types.Operator): @@ -1358,11 +1411,11 @@ class SelectCobieIfcFile(bpy.types.Operator): def execute(self, context): bpy.context.scene.BIMProperties.cobie_ifc_file = self.filepath - return {'FINISHED'} + return {"FINISHED"} def invoke(self, context, event): context.window_manager.fileselect_add(self) - return {'RUNNING_MODAL'} + return {"RUNNING_MODAL"} class SelectCobieJsonFile(bpy.types.Operator): @@ -1372,31 +1425,32 @@ class SelectCobieJsonFile(bpy.types.Operator): def execute(self, context): bpy.context.scene.BIMProperties.cobie_json_file = self.filepath - return {'FINISHED'} + return {"FINISHED"} def invoke(self, context, event): context.window_manager.fileselect_add(self) - return {'RUNNING_MODAL'} + return {"RUNNING_MODAL"} class ExecuteIfcCobie(bpy.types.Operator): - bl_idname = 'bim.execute_ifc_cobie' - bl_label = 'Execute IFCCOBie' + bl_idname = "bim.execute_ifc_cobie" + bl_label = "Execute IFCCOBie" file_format: bpy.props.StringProperty() def execute(self, context): from cobie import IfcCobieParser + output_dir = os.path.dirname(bpy.context.scene.BIMProperties.cobie_ifc_file) - output = os.path.join(output_dir, 'output') - logger = logging.getLogger('IFCtoCOBie') - fh = logging.FileHandler(os.path.join(output_dir, 'cobie.log')) + output = os.path.join(output_dir, "output") + logger = logging.getLogger("IFCtoCOBie") + fh = logging.FileHandler(os.path.join(output_dir, "cobie.log")) fh.setLevel(logging.DEBUG) - fh.setFormatter(logging.Formatter('%(asctime)s : %(levelname)s : %(message)s')) - logger = logging.getLogger('IFCtoCOBie') + fh.setFormatter(logging.Formatter("%(asctime)s : %(levelname)s : %(message)s")) + logger = logging.getLogger("IFCtoCOBie") logger.addHandler(fh) selector = ifcopenshell.util.selector.Selector() if bpy.context.scene.BIMProperties.cobie_json_file: - with open(bpy.context.scene.BIMProperties.cobie_json_file, 'r') as f: + with open(bpy.context.scene.BIMProperties.cobie_json_file, "r") as f: custom_data = json.load(f) else: custom_data = {} @@ -1405,41 +1459,48 @@ class ExecuteIfcCobie(bpy.types.Operator): bpy.context.scene.BIMProperties.cobie_ifc_file, bpy.context.scene.BIMProperties.cobie_types, bpy.context.scene.BIMProperties.cobie_components, - custom_data) - if self.file_format == 'xlsx': + custom_data, + ) + if self.file_format == "xlsx": from cobie import CobieXlsWriter + writer = CobieXlsWriter(parser, output) writer.write() - webbrowser.open('file://' + output + '.' + self.file_format) - elif self.file_format == 'ods': + webbrowser.open("file://" + output + "." + self.file_format) + elif self.file_format == "ods": from cobie import CobieOdsWriter + writer = CobieOdsWriter(parser, output) writer.write() - webbrowser.open('file://' + output + '.' + self.file_format) + webbrowser.open("file://" + output + "." + self.file_format) else: from cobie import CobieCsvWriter + writer = CobieCsvWriter(parser, output_dir) writer.write() - webbrowser.open('file://' + output_dir) - webbrowser.open('file://' + output_dir + '/cobie.log') - return {'FINISHED'} + webbrowser.open("file://" + output_dir) + webbrowser.open("file://" + output_dir + "/cobie.log") + return {"FINISHED"} class ExecuteIfcPatch(bpy.types.Operator): - bl_idname = 'bim.execute_ifc_patch' - bl_label = 'Execute IFCPatch' + bl_idname = "bim.execute_ifc_patch" + bl_label = "Execute IFCPatch" file_format: bpy.props.StringProperty() def execute(self, context): import ifcpatch - ifcpatch.execute({ - 'input': bpy.context.scene.BIMProperties.ifc_patch_input, - 'output': bpy.context.scene.BIMProperties.ifc_patch_output, - 'recipe': bpy.context.scene.BIMProperties.ifc_patch_recipes, - 'arguments': json.loads('[' + bpy.context.scene.BIMProperties.ifc_patch_args + ']'), - 'log': bpy.context.scene.BIMProperties.data_dir + 'process.log' - }) - return {'FINISHED'} + + ifcpatch.execute( + { + "input": bpy.context.scene.BIMProperties.ifc_patch_input, + "output": bpy.context.scene.BIMProperties.ifc_patch_output, + "recipe": bpy.context.scene.BIMProperties.ifc_patch_recipes, + "arguments": json.loads("[" + bpy.context.scene.BIMProperties.ifc_patch_args + "]"), + "log": bpy.context.scene.BIMProperties.data_dir + "process.log", + } + ) + return {"FINISHED"} class SelectDiffJsonFile(bpy.types.Operator): @@ -1449,34 +1510,34 @@ class SelectDiffJsonFile(bpy.types.Operator): def execute(self, context): bpy.context.scene.BIMProperties.diff_json_file = self.filepath - return {'FINISHED'} + return {"FINISHED"} def invoke(self, context, event): context.window_manager.fileselect_add(self) - return {'RUNNING_MODAL'} + return {"RUNNING_MODAL"} class VisualiseDiff(bpy.types.Operator): - bl_idname = 'bim.visualise_diff' - bl_label = 'Visualise Diff' + bl_idname = "bim.visualise_diff" + bl_label = "Visualise Diff" def execute(self, context): - with open(bpy.context.scene.BIMProperties.diff_json_file, 'r') as file: + with open(bpy.context.scene.BIMProperties.diff_json_file, "r") as file: diff = json.load(file) for obj in bpy.context.visible_objects: - obj.color = (1., 1., 1., .2) - global_id = obj.BIMObjectProperties.attributes.get('GlobalId') + obj.color = (1.0, 1.0, 1.0, 0.2) + global_id = obj.BIMObjectProperties.attributes.get("GlobalId") if not global_id: continue - if global_id.string_value in diff['deleted']: - obj.color = (1., 0., 0., .2) - elif global_id.string_value in diff['added']: - obj.color = (0., 1., 0., .2) - elif global_id.string_value in diff['changed']: - obj.color = (0., 0., 1., .2) - area = next(area for area in bpy.context.screen.areas if area.type == 'VIEW_3D') - area.spaces[0].shading.color_type = 'OBJECT' - return {'FINISHED'} + if global_id.string_value in diff["deleted"]: + obj.color = (1.0, 0.0, 0.0, 0.2) + elif global_id.string_value in diff["added"]: + obj.color = (0.0, 1.0, 0.0, 0.2) + elif global_id.string_value in diff["changed"]: + obj.color = (0.0, 0.0, 1.0, 0.2) + area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") + area.spaces[0].shading.color_type = "OBJECT" + return {"FINISHED"} class SelectDiffOldFile(bpy.types.Operator): @@ -1486,11 +1547,11 @@ class SelectDiffOldFile(bpy.types.Operator): def execute(self, context): bpy.context.scene.BIMProperties.diff_old_file = self.filepath - return {'FINISHED'} + return {"FINISHED"} def invoke(self, context, event): context.window_manager.fileselect_add(self) - return {'RUNNING_MODAL'} + return {"RUNNING_MODAL"} class SelectDiffNewFile(bpy.types.Operator): @@ -1500,152 +1561,148 @@ class SelectDiffNewFile(bpy.types.Operator): def execute(self, context): bpy.context.scene.BIMProperties.diff_new_file = self.filepath - return {'FINISHED'} + return {"FINISHED"} def invoke(self, context, event): context.window_manager.fileselect_add(self) - return {'RUNNING_MODAL'} + return {"RUNNING_MODAL"} class ExecuteIfcDiff(bpy.types.Operator): - bl_idname = 'bim.execute_ifc_diff' - bl_label = 'Execute IFC Diff' - filename_ext = '.json' - filepath: bpy.props.StringProperty(subtype='FILE_PATH') + bl_idname = "bim.execute_ifc_diff" + bl_label = "Execute IFC Diff" + filename_ext = ".json" + filepath: bpy.props.StringProperty(subtype="FILE_PATH") def invoke(self, context, event): - self.filepath = bpy.path.ensure_ext(bpy.data.filepath, '.json') + self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json") WindowManager = context.window_manager WindowManager.fileselect_add(self) - return {'RUNNING_MODAL'} + return {"RUNNING_MODAL"} def execute(self, context): import ifcdiff + ifc_diff = ifcdiff.IfcDiff( bpy.context.scene.BIMProperties.diff_old_file, bpy.context.scene.BIMProperties.diff_new_file, self.filepath, - bpy.context.scene.BIMProperties.diff_relationships.split() + bpy.context.scene.BIMProperties.diff_relationships.split(), ) ifc_diff.diff() ifc_diff.export() bpy.context.scene.BIMProperties.diff_json_file = self.filepath - return {'FINISHED'} + return {"FINISHED"} class ExportClashSets(bpy.types.Operator): - bl_idname = 'bim.export_clash_sets' - bl_label = 'Export Clash Sets' - filename_ext = '.json' - filepath: bpy.props.StringProperty(subtype='FILE_PATH') + bl_idname = "bim.export_clash_sets" + bl_label = "Export Clash Sets" + filename_ext = ".json" + filepath: bpy.props.StringProperty(subtype="FILE_PATH") def invoke(self, context, event): - self.filepath = bpy.path.ensure_ext(bpy.data.filepath, '.json') + self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json") WindowManager = context.window_manager WindowManager.fileselect_add(self) - return {'RUNNING_MODAL'} + return {"RUNNING_MODAL"} def execute(self, context): - self.filepath = bpy.path.ensure_ext(self.filepath, '.json') + self.filepath = bpy.path.ensure_ext(self.filepath, ".json") clash_sets = [] for clash_set in bpy.context.scene.BIMProperties.clash_sets: self.a = [] self.b = [] - for ab in ['a', 'b']: + for ab in ["a", "b"]: for data in getattr(clash_set, ab): - clash_source = { 'file': data.name } + clash_source = {"file": data.name} if data.selector: - clash_source['selector'] = data.selector - clash_source['mode'] = data.mode + clash_source["selector"] = data.selector + clash_source["mode"] = data.mode getattr(self, ab).append(clash_source) - clash_sets.append({ - 'name': clash_set.name, - 'tolerance': clash_set.tolerance, - 'a': self.a, - 'b': self.b - }) - with open(self.filepath, 'w') as destination: + clash_sets.append({"name": clash_set.name, "tolerance": clash_set.tolerance, "a": self.a, "b": self.b}) + with open(self.filepath, "w") as destination: destination.write(json.dumps(clash_sets, indent=4)) - return {'FINISHED'} + return {"FINISHED"} class ImportClashSets(bpy.types.Operator): - bl_idname = 'bim.import_clash_sets' - bl_label = 'Import Clash Sets' - filename_ext = '.json' - filepath: bpy.props.StringProperty(subtype='FILE_PATH') + bl_idname = "bim.import_clash_sets" + bl_label = "Import Clash Sets" + filename_ext = ".json" + filepath: bpy.props.StringProperty(subtype="FILE_PATH") def invoke(self, context, event): - self.filepath = bpy.path.ensure_ext(bpy.data.filepath, '.json') + self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json") WindowManager = context.window_manager WindowManager.fileselect_add(self) - return {'RUNNING_MODAL'} + return {"RUNNING_MODAL"} def execute(self, context): with open(self.filepath) as f: clash_sets = json.load(f) for clash_set in clash_sets: new = bpy.context.scene.BIMProperties.clash_sets.add() - new.name = clash_set['name'] - new.tolerance = clash_set['tolerance'] - for clash_source in clash_set['a']: + new.name = clash_set["name"] + new.tolerance = clash_set["tolerance"] + for clash_source in clash_set["a"]: new_source = new.a.add() - new_source.name = clash_source['file'] - if 'selector' in clash_source: - new_source.selector = clash_source['selector'] - new_source.mode = clash_source['mode'] - if clash_set['b']: - for clash_source in clash_set['b']: + new_source.name = clash_source["file"] + if "selector" in clash_source: + new_source.selector = clash_source["selector"] + new_source.mode = clash_source["mode"] + if clash_set["b"]: + for clash_source in clash_set["b"]: new_source = new.b.add() - new_source.name = clash_source['file'] - if 'selector' in clash_source: - new_source.selector = clash_source['selector'] - new_source.mode = clash_source['mode'] - return {'FINISHED'} + new_source.name = clash_source["file"] + if "selector" in clash_source: + new_source.selector = clash_source["selector"] + new_source.mode = clash_source["mode"] + return {"FINISHED"} class AddClashSet(bpy.types.Operator): - bl_idname = 'bim.add_clash_set' - bl_label = 'Add Clash Set' + bl_idname = "bim.add_clash_set" + bl_label = "Add Clash Set" def execute(self, context): new = bpy.context.scene.BIMProperties.clash_sets.add() - new.name = 'New Clash Set' + new.name = "New Clash Set" new.tolerance = 0.01 - return {'FINISHED'} + return {"FINISHED"} class RemoveClashSet(bpy.types.Operator): - bl_idname = 'bim.remove_clash_set' - bl_label = 'Remove Clash Set' + bl_idname = "bim.remove_clash_set" + bl_label = "Remove Clash Set" index: bpy.props.IntProperty() def execute(self, context): bpy.context.scene.BIMProperties.clash_sets.remove(self.index) - return {'FINISHED'} + return {"FINISHED"} class AddClashSource(bpy.types.Operator): - bl_idname = 'bim.add_clash_source' - bl_label = 'Add Clash Source' + bl_idname = "bim.add_clash_source" + bl_label = "Add Clash Source" group: bpy.props.StringProperty() def execute(self, context): clash_set = bpy.context.scene.BIMProperties.clash_sets[bpy.context.scene.BIMProperties.active_clash_set_index] source = getattr(clash_set, self.group).add() - return {'FINISHED'} + return {"FINISHED"} class RemoveClashSource(bpy.types.Operator): - bl_idname = 'bim.remove_clash_source' - bl_label = 'Remove Clash Source' + bl_idname = "bim.remove_clash_source" + bl_label = "Remove Clash Source" index: bpy.props.IntProperty() group: bpy.props.StringProperty() def execute(self, context): clash_set = bpy.context.scene.BIMProperties.clash_sets[bpy.context.scene.BIMProperties.active_clash_set_index] getattr(clash_set, self.group).remove(self.index) - return {'FINISHED'} + return {"FINISHED"} class SelectClashSource(bpy.types.Operator): @@ -1658,84 +1715,83 @@ class SelectClashSource(bpy.types.Operator): def execute(self, context): clash_set = bpy.context.scene.BIMProperties.clash_sets[bpy.context.scene.BIMProperties.active_clash_set_index] getattr(clash_set, self.group)[self.index].name = self.filepath - return {'FINISHED'} + return {"FINISHED"} def invoke(self, context, event): context.window_manager.fileselect_add(self) - return {'RUNNING_MODAL'} + return {"RUNNING_MODAL"} class ExecuteIfcClash(bpy.types.Operator): - bl_idname = 'bim.execute_ifc_clash' - bl_label = 'Execute IFC Clash' - filename_ext = '.json' - filepath: bpy.props.StringProperty(subtype='FILE_PATH') + bl_idname = "bim.execute_ifc_clash" + bl_label = "Execute IFC Clash" + filename_ext = ".json" + filepath: bpy.props.StringProperty(subtype="FILE_PATH") def invoke(self, context, event): - self.filepath = bpy.path.ensure_ext(bpy.data.filepath, '.json') + self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json") WindowManager = context.window_manager WindowManager.fileselect_add(self) - return {'RUNNING_MODAL'} + return {"RUNNING_MODAL"} def execute(self, context): import ifcclash + settings = ifcclash.IfcClashSettings() - self.filepath = bpy.path.ensure_ext(self.filepath, '.json') + self.filepath = bpy.path.ensure_ext(self.filepath, ".json") settings.output = self.filepath - settings.logger = logging.getLogger('Clash') + settings.logger = logging.getLogger("Clash") settings.logger.setLevel(logging.DEBUG) ifc_clasher = ifcclash.IfcClasher(settings) ifc_clasher.clash_sets = [] for clash_set in bpy.context.scene.BIMProperties.clash_sets: self.a = [] self.b = [] - for ab in ['a', 'b']: + for ab in ["a", "b"]: for data in getattr(clash_set, ab): - clash_source = { 'file': data.name } + clash_source = {"file": data.name} if data.selector: - clash_source['selector'] = data.selector - clash_source['mode'] = data.mode + clash_source["selector"] = data.selector + clash_source["mode"] = data.mode getattr(self, ab).append(clash_source) - ifc_clasher.clash_sets.append({ - 'name': clash_set.name, - 'tolerance': clash_set.tolerance, - 'a': self.a, - 'b': self.b - }) + ifc_clasher.clash_sets.append( + {"name": clash_set.name, "tolerance": clash_set.tolerance, "a": self.a, "b": self.b} + ) ifc_clasher.clash() ifc_clasher.export() - return {'FINISHED'} + return {"FINISHED"} class SelectIfcClashResults(bpy.types.Operator): - bl_idname = 'bim.select_ifc_clash_results' - bl_label = 'Select IFC Clash Results' - filename_ext = '.json' - filepath: bpy.props.StringProperty(subtype='FILE_PATH') + bl_idname = "bim.select_ifc_clash_results" + bl_label = "Select IFC Clash Results" + filename_ext = ".json" + filepath: bpy.props.StringProperty(subtype="FILE_PATH") def invoke(self, context, event): - self.filepath = bpy.path.ensure_ext(bpy.data.filepath, '.json') + self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json") WindowManager = context.window_manager WindowManager.fileselect_add(self) - return {'RUNNING_MODAL'} + return {"RUNNING_MODAL"} def execute(self, context): - self.filepath = bpy.path.ensure_ext(self.filepath, '.json') + self.filepath = bpy.path.ensure_ext(self.filepath, ".json") with open(self.filepath) as f: clash_sets = json.load(f) clash_set_name = bpy.context.scene.BIMProperties.clash_sets[ - bpy.context.scene.BIMProperties.active_clash_set_index].name + bpy.context.scene.BIMProperties.active_clash_set_index + ].name global_ids = [] for clash_set in clash_sets: - if clash_set['name'] != clash_set_name: + if clash_set["name"] != clash_set_name: continue - for clash in clash_set['clashes'].values(): - global_ids.extend([clash['a_global_id'], clash['b_global_id']]) + for clash in clash_set["clashes"].values(): + global_ids.extend([clash["a_global_id"], clash["b_global_id"]]) for obj in bpy.context.visible_objects: - global_id = obj.BIMObjectProperties.attributes.get('GlobalId') + global_id = obj.BIMObjectProperties.attributes.get("GlobalId") if global_id and global_id.string_value in global_ids: obj.select_set(True) - return {'FINISHED'} + return {"FINISHED"} class SelectBcfFile(bpy.types.Operator): @@ -1745,11 +1801,11 @@ class SelectBcfFile(bpy.types.Operator): def execute(self, context): bpy.context.scene.BCFProperties.bcf_file = self.filepath - return {'FINISHED'} + return {"FINISHED"} def invoke(self, context, event): context.window_manager.fileselect_add(self) - return {'RUNNING_MODAL'} + return {"RUNNING_MODAL"} class SelectFeaturesDir(bpy.types.Operator): @@ -1758,12 +1814,14 @@ class SelectFeaturesDir(bpy.types.Operator): filepath: bpy.props.StringProperty(subtype="FILE_PATH") def execute(self, context): - bpy.context.scene.BIMProperties.features_dir = os.path.dirname(os.path.abspath(self.filepath)) if '.' in self.filepath else self.filepath - return {'FINISHED'} + bpy.context.scene.BIMProperties.features_dir = ( + os.path.dirname(os.path.abspath(self.filepath)) if "." in self.filepath else self.filepath + ) + return {"FINISHED"} def invoke(self, context, event): context.window_manager.fileselect_add(self) - return {'RUNNING_MODAL'} + return {"RUNNING_MODAL"} class SelectIfcFile(bpy.types.Operator): @@ -1773,23 +1831,24 @@ class SelectIfcFile(bpy.types.Operator): def execute(self, context): bpy.context.scene.BIMProperties.ifc_file = self.filepath - return {'FINISHED'} + return {"FINISHED"} def invoke(self, context, event): context.window_manager.fileselect_add(self) - return {'RUNNING_MODAL'} + return {"RUNNING_MODAL"} class ValidateIfcFile(bpy.types.Operator): - bl_idname = 'bim.validate_ifc_file' - bl_label = 'Validate IFC File' + bl_idname = "bim.validate_ifc_file" + bl_label = "Validate IFC File" def execute(self, context): import ifcopenshell.validate - logger = logging.getLogger('validate') + + logger = logging.getLogger("validate") logger.setLevel(logging.DEBUG) ifcopenshell.validate.validate(ifc.IfcStore.get_file(), logger) - return {'FINISHED'} + return {"FINISHED"} class SelectDataDir(bpy.types.Operator): @@ -1799,11 +1858,11 @@ class SelectDataDir(bpy.types.Operator): def execute(self, context): bpy.context.scene.BIMProperties.data_dir = self.filepath - return {'FINISHED'} + return {"FINISHED"} def invoke(self, context, event): context.window_manager.fileselect_add(self) - return {'RUNNING_MODAL'} + return {"RUNNING_MODAL"} class SelectSchemaDir(bpy.types.Operator): @@ -1813,36 +1872,38 @@ class SelectSchemaDir(bpy.types.Operator): def execute(self, context): bpy.context.scene.BIMProperties.schema_dir = self.filepath - return {'FINISHED'} + return {"FINISHED"} def invoke(self, context, event): context.window_manager.fileselect_add(self) - return {'RUNNING_MODAL'} + return {"RUNNING_MODAL"} + class CreateAggregate(bpy.types.Operator): - bl_idname = 'bim.create_aggregate' - bl_label = 'Create Aggregate' + bl_idname = "bim.create_aggregate" + bl_label = "Create Aggregate" def execute(self, context): spatial_container = None for obj in bpy.context.selected_objects: if obj.instance_collection: - return {'FINISHED'} + return {"FINISHED"} for collection in obj.users_collection: - if 'IfcRelAggregates' in collection.name: - return {'FINISHED'} - elif collection.name[0:3] == 'Ifc': + if "IfcRelAggregates" in collection.name: + return {"FINISHED"} + elif collection.name[0:3] == "Ifc": spatial_container = collection if not spatial_container: - return {'FINISHED'} + return {"FINISHED"} - aggregate = bpy.data.collections.new('IfcRelAggregates/{}'.format( - bpy.context.scene.BIMProperties.aggregate_class)) - for project in [c for c in bpy.context.view_layer.layer_collection.children if 'IfcProject' in c.name]: - if not [c for c in project.children if 'Aggregates' in c.name]: - aggregates = bpy.data.collections.new('Aggregates') + aggregate = bpy.data.collections.new( + "IfcRelAggregates/{}".format(bpy.context.scene.BIMProperties.aggregate_class) + ) + for project in [c for c in bpy.context.view_layer.layer_collection.children if "IfcProject" in c.name]: + if not [c for c in project.children if "Aggregates" in c.name]: + aggregates = bpy.data.collections.new("Aggregates") project.collection.children.link(aggregates) - for aggregate_collection in [c for c in project.children if 'Aggregates' in c.name]: + for aggregate_collection in [c for c in project.children if "Aggregates" in c.name]: aggregate_collection.collection.children.link(aggregate) aggregate_collection.children[aggregate.name].hide_viewport = True break @@ -1852,63 +1913,65 @@ class CreateAggregate(bpy.types.Operator): collection.objects.unlink(obj) aggregate.objects.link(obj) - instance = bpy.data.objects.new('{}/{}'.format( - bpy.context.scene.BIMProperties.aggregate_class, - bpy.context.scene.BIMProperties.aggregate_name), - None) - instance.instance_type = 'COLLECTION' + instance = bpy.data.objects.new( + "{}/{}".format( + bpy.context.scene.BIMProperties.aggregate_class, bpy.context.scene.BIMProperties.aggregate_name + ), + None, + ) + instance.instance_type = "COLLECTION" instance.instance_collection = aggregate spatial_container.objects.link(instance) - return {'FINISHED'} + return {"FINISHED"} + class EditAggregate(bpy.types.Operator): - bl_idname = 'bim.edit_aggregate' - bl_label = 'Edit Aggregate' + bl_idname = "bim.edit_aggregate" + bl_label = "Edit Aggregate" def execute(self, context): obj = bpy.context.active_object - if obj.instance_type != 'COLLECTION' \ - or 'IfcRelAggregates' not in obj.instance_collection.name: - return {'FINISHED'} + if obj.instance_type != "COLLECTION" or "IfcRelAggregates" not in obj.instance_collection.name: + return {"FINISHED"} bpy.context.view_layer.objects[obj.name].hide_viewport = True - for project in [c for c in bpy.context.view_layer.layer_collection.children if 'IfcProject' in c.name]: - for aggregate_collection in [c for c in project.children if 'Aggregates' in c.name]: + for project in [c for c in bpy.context.view_layer.layer_collection.children if "IfcProject" in c.name]: + for aggregate_collection in [c for c in project.children if "Aggregates" in c.name]: for aggregate in [c for c in aggregate_collection.children if c.name == obj.instance_collection.name]: aggregate.hide_viewport = False break - return {'FINISHED'} + return {"FINISHED"} + class SaveAggregate(bpy.types.Operator): - bl_idname = 'bim.save_aggregate' - bl_label = 'Save Aggregate' + bl_idname = "bim.save_aggregate" + bl_label = "Save Aggregate" def execute(self, context): obj = bpy.context.active_object aggregate = None names = [c.name for c in obj.users_collection] - for project in [c for c in bpy.context.view_layer.layer_collection.children if 'IfcProject' in c.name]: - for aggregate_collection in [c for c in project.children if 'Aggregates' in c.name]: + for project in [c for c in bpy.context.view_layer.layer_collection.children if "IfcProject" in c.name]: + for aggregate_collection in [c for c in project.children if "Aggregates" in c.name]: for collection in [c for c in aggregate_collection.children if c.name in names]: collection.hide_viewport = True aggregate = collection.collection break if not aggregate: - return {'FINISHED'} + return {"FINISHED"} for obj in bpy.context.view_layer.objects: if obj.instance_collection == aggregate: obj.hide_viewport = False - return {'FINISHED'} + return {"FINISHED"} class ExplodeAggregate(bpy.types.Operator): - bl_idname = 'bim.explode_aggregate' - bl_label = 'Explode Aggregate' + bl_idname = "bim.explode_aggregate" + bl_label = "Explode Aggregate" def execute(self, context): obj = bpy.context.active_object - if obj.instance_type != 'COLLECTION' \ - or 'IfcRelAggregates' not in obj.instance_collection.name: - return {'FINISHED'} + if obj.instance_type != "COLLECTION" or "IfcRelAggregates" not in obj.instance_collection.name: + return {"FINISHED"} aggregate_collection = bpy.data.collections.get(obj.instance_collection.name) spatial_collection = obj.users_collection[0] for part in aggregate_collection.objects: @@ -1916,83 +1979,88 @@ class ExplodeAggregate(bpy.types.Operator): aggregate_collection.objects.unlink(part) bpy.data.objects.remove(obj, do_unlink=True) bpy.data.collections.remove(aggregate_collection, do_unlink=True) - return {'FINISHED'} + return {"FINISHED"} class LoadClassification(bpy.types.Operator): - bl_idname = 'bim.load_classification' - bl_label = 'Load Classification' + bl_idname = "bim.load_classification" + bl_label = "Load Classification" is_file: bpy.props.BoolProperty() classification_index: bpy.props.IntProperty() def execute(self, context): from . import prop + if self.is_file: prop.ClassificationView.raw_data = schema.ifc.load_classification( - context.scene.BIMProperties.classification) + context.scene.BIMProperties.classification + ) else: prop.ClassificationView.raw_data = schema.ifc.load_classification( - context.scene.BIMProperties.classifications[self.classification_index].name, - self.classification_index) - context.scene.BIMProperties.classification_references.root = '' - return {'FINISHED'} + context.scene.BIMProperties.classifications[self.classification_index].name, self.classification_index + ) + context.scene.BIMProperties.classification_references.root = "" + return {"FINISHED"} class AddClassification(bpy.types.Operator): - bl_idname = 'bim.add_classification' - bl_label = 'Add Classification' + bl_idname = "bim.add_classification" + bl_label = "Add Classification" def execute(self, context): if context.scene.BIMProperties.classification not in schema.ifc.classifications: - return {'FINISHED'} + return {"FINISHED"} data = schema.ifc.classifications[context.scene.BIMProperties.classification] classification = context.scene.BIMProperties.classifications.add() data_map = { - 'name': 'Name', 'source': 'Source', - 'edition': 'Edition', 'edition_date': 'EditionDate', - 'description': 'Description', 'location': 'Location', - 'reference_tokens': 'ReferenceTokens' + "name": "Name", + "source": "Source", + "edition": "Edition", + "edition_date": "EditionDate", + "description": "Description", + "location": "Location", + "reference_tokens": "ReferenceTokens", } for key, value in data_map.items(): if hasattr(data, value) and getattr(data, value): setattr(classification, key, str(getattr(data, value))) classification.data = schema.ifc.classification_files[context.scene.BIMProperties.classification].to_string() - return {'FINISHED'} + return {"FINISHED"} class RemoveClassification(bpy.types.Operator): - bl_idname = 'bim.remove_classification' - bl_label = 'Remove Classification' + bl_idname = "bim.remove_classification" + bl_label = "Remove Classification" classification_index: bpy.props.IntProperty() def execute(self, context): bpy.context.scene.BIMProperties.classifications.remove(self.classification_index) - return {'FINISHED'} + return {"FINISHED"} class AssignClassification(bpy.types.Operator): - bl_idname = 'bim.assign_classification' - bl_label = 'Assign Classification' + bl_idname = "bim.assign_classification" + bl_label = "Assign Classification" def execute(self, context): for obj in bpy.context.selected_objects: classification = obj.BIMObjectProperties.classifications.add() refs = bpy.context.scene.BIMProperties.classification_references - data = refs.root['children'][refs.children[refs.active_index].name] - if data['identification']: - classification.name = data['identification'] - if data['name']: - classification.human_name = data['name'] - for key in ['location', 'description']: + data = refs.root["children"][refs.children[refs.active_index].name] + if data["identification"]: + classification.name = data["identification"] + if data["name"]: + classification.human_name = data["name"] + for key in ["location", "description"]: if data[key]: setattr(classification, key, data[key]) classification.referenced_source = bpy.context.scene.BIMProperties.active_classification_name - return {'FINISHED'} + return {"FINISHED"} class UnassignClassification(bpy.types.Operator): - bl_idname = 'bim.unassign_classification' - bl_label = 'Unassign Classification' + bl_idname = "bim.unassign_classification" + bl_label = "Unassign Classification" def execute(self, context): refs = bpy.context.scene.BIMProperties.classification_references @@ -2002,138 +2070,130 @@ class UnassignClassification(bpy.types.Operator): if index != -1: obj.BIMObjectProperties.classifications.remove(index) - obj.BIMObjectProperties.classification = '' - return {'FINISHED'} + obj.BIMObjectProperties.classification = "" + return {"FINISHED"} class RemoveClassificationReference(bpy.types.Operator): - bl_idname = 'bim.remove_classification_reference' - bl_label = 'Remove Classification Reference' + bl_idname = "bim.remove_classification_reference" + bl_label = "Remove Classification Reference" classification_index: bpy.props.IntProperty() def execute(self, context): bpy.context.active_object.BIMObjectProperties.classifications.remove(self.classification_index) - return {'FINISHED'} + return {"FINISHED"} class FetchExternalMaterial(bpy.types.Operator): - bl_idname = 'bim.fetch_external_material' - bl_label = 'Fetch External Material' + bl_idname = "bim.fetch_external_material" + bl_label = "Fetch External Material" def execute(self, context): location = bpy.context.active_object.active_material.BIMMaterialProperties.location - if location[-6:] != '.mpass': - return {'FINISHED'} + if location[-6:] != ".mpass": + return {"FINISHED"} if not os.path.isabs(location): - location = os.path.join(os.path.join( - bpy.context.scene.BIMProperties.data_dir, location)) + location = os.path.join(os.path.join(bpy.context.scene.BIMProperties.data_dir, location)) with open(location) as f: self.material_pass = json.load(f) - if bpy.context.scene.render.engine == 'BLENDER_EEVEE' \ - and 'eevee' in self.material_pass: - self.fetch_eevee_or_cycles('eevee') - elif bpy.context.scene.render.engine == 'CYCLES' \ - and 'cycles' in self.material_pass: - self.fetch_eevee_or_cycles('cycles') - return {'FINISHED'} + if bpy.context.scene.render.engine == "BLENDER_EEVEE" and "eevee" in self.material_pass: + self.fetch_eevee_or_cycles("eevee") + elif bpy.context.scene.render.engine == "CYCLES" and "cycles" in self.material_pass: + self.fetch_eevee_or_cycles("cycles") + return {"FINISHED"} def fetch_eevee_or_cycles(self, name): identification = bpy.context.active_object.active_material.BIMMaterialProperties.identification - uri = self.material_pass[name]['uri'] + uri = self.material_pass[name]["uri"] if not os.path.isabs(uri): - uri = os.path.join(os.path.join( - bpy.context.scene.BIMProperties.data_dir, uri)) - bpy.ops.wm.link( - filename=identification, - directory=os.path.join(uri, 'Material') - ) + uri = os.path.join(os.path.join(bpy.context.scene.BIMProperties.data_dir, uri)) + bpy.ops.wm.link(filename=identification, directory=os.path.join(uri, "Material")) for material in bpy.data.materials: - if material.name == identification \ - and material.library: + if material.name == identification and material.library: bpy.context.active_object.material_slots[0].material = material return class FetchLibraryInformation(bpy.types.Operator): - bl_idname = 'bim.fetch_library_information' - bl_label = 'Fetch Library Information' + bl_idname = "bim.fetch_library_information" + bl_label = "Fetch Library Information" def execute(self, context): # TODO - return {'FINISHED'} + return {"FINISHED"} class FetchObjectPassport(bpy.types.Operator): - bl_idname = 'bim.fetch_object_passport' - bl_label = 'Fetch Object Passport' + bl_idname = "bim.fetch_object_passport" + bl_label = "Fetch Object Passport" def execute(self, context): for reference in bpy.context.active_object.BIMObjectProperties.document_references: reference = bpy.context.scene.BIMProperties.document_references[reference.name] - if reference.location[-6:] == '.blend': + if reference.location[-6:] == ".blend": self.fetch_blender(reference) - return {'FINISHED'} + return {"FINISHED"} def fetch_blender(self, reference): - bpy.ops.wm.link( - filename=reference.name, - directory=os.path.join(reference.location, 'Mesh') - ) + bpy.ops.wm.link(filename=reference.name, directory=os.path.join(reference.location, "Mesh")) bpy.context.active_object.data = bpy.data.meshes[reference.name] class AddSubcontext(bpy.types.Operator): - bl_idname = 'bim.add_subcontext' - bl_label = 'Add Subcontext' + bl_idname = "bim.add_subcontext" + bl_label = "Add Subcontext" context: bpy.props.StringProperty() def execute(self, context): props = bpy.context.scene.BIMProperties - subcontext = getattr(bpy.context.scene.BIMProperties, '{}_subcontexts'.format(self.context)).add() + subcontext = getattr(bpy.context.scene.BIMProperties, "{}_subcontexts".format(self.context)).add() subcontext.name = bpy.context.scene.BIMProperties.available_subcontexts subcontext.target_view = bpy.context.scene.BIMProperties.available_target_views - return {'FINISHED'} + return {"FINISHED"} class RemoveSubcontext(bpy.types.Operator): - bl_idname = 'bim.remove_subcontext' - bl_label = 'Remove Context' + bl_idname = "bim.remove_subcontext" + bl_label = "Remove Context" indexes: bpy.props.StringProperty() def execute(self, context): - context, subcontext_index = self.indexes.split('-') + context, subcontext_index = self.indexes.split("-") subcontext_index = int(subcontext_index) - getattr(bpy.context.scene.BIMProperties, '{}_subcontexts'.format(context)).remove(subcontext_index) - return {'FINISHED'} + getattr(bpy.context.scene.BIMProperties, "{}_subcontexts".format(context)).remove(subcontext_index) + return {"FINISHED"} class OpenView(bpy.types.Operator): - bl_idname = 'bim.open_view' - bl_label = 'Open View' + bl_idname = "bim.open_view" + bl_label = "Open View" view: bpy.props.StringProperty() def execute(self, context): open_with_user_command( - bpy.context.preferences.addons['blenderbim'].preferences.svg_command, - os.path.join(bpy.context.scene.BIMProperties.data_dir, 'diagrams', self.view + '.svg')) - return {'FINISHED'} + bpy.context.preferences.addons["blenderbim"].preferences.svg_command, + os.path.join(bpy.context.scene.BIMProperties.data_dir, "diagrams", self.view + ".svg"), + ) + return {"FINISHED"} class CutSection(bpy.types.Operator): - bl_idname = 'bim.cut_section' - bl_label = 'Cut Section' + bl_idname = "bim.cut_section" + bl_label = "Cut Section" def execute(self, context): camera = bpy.context.scene.camera - if not (camera.type == 'CAMERA' and camera.data.type == 'ORTHO'): - return {'FINISHED'} - bpy.ops.bim.activate_view(drawing_index=bpy.context.scene.DocProperties.drawings.find(camera.name.split('/')[1])) - drawing_style = bpy.context.scene.DocProperties.drawing_styles[camera.data.BIMCameraProperties.active_drawing_style_index] - self.diagram_name = camera.name.split('/')[1] + if not (camera.type == "CAMERA" and camera.data.type == "ORTHO"): + return {"FINISHED"} + bpy.ops.bim.activate_view( + drawing_index=bpy.context.scene.DocProperties.drawings.find(camera.name.split("/")[1]) + ) + drawing_style = bpy.context.scene.DocProperties.drawing_styles[ + camera.data.BIMCameraProperties.active_drawing_style_index + ] + self.diagram_name = camera.name.split("/")[1] bpy.context.scene.render.filepath = os.path.join( - bpy.context.scene.BIMProperties.data_dir, - 'diagrams', - '{}.png'.format(self.diagram_name) + bpy.context.scene.BIMProperties.data_dir, "diagrams", "{}.png".format(self.diagram_name) ) self.create_raster(camera, drawing_style) location = camera.location @@ -2151,12 +2211,13 @@ class CutSection(bpy.types.Operator): top_left_corner = location - (width / 2 * x_axis) - (height / 2 * y_axis) ifc_cutter = cut_ifc.IfcCutter() import ifccsv + ifc_cutter.ifc_filenames = [i.name for i in bpy.context.scene.DocProperties.ifc_files] ifc_cutter.data_dir = bpy.context.scene.BIMProperties.data_dir ifc_cutter.vector_style = drawing_style.vector_style ifc_cutter.diagram_name = self.diagram_name ifc_cutter.background_image = bpy.context.scene.render.filepath - if camera.data.BIMCameraProperties.cut_objects == 'CUSTOM': + if camera.data.BIMCameraProperties.cut_objects == "CUSTOM": ifc_cutter.cut_objects = camera.data.BIMCameraProperties.cut_objects_custom else: ifc_cutter.cut_objects = camera.data.BIMCameraProperties.cut_objects @@ -2174,84 +2235,86 @@ class CutSection(bpy.types.Operator): ifc_cutter.misc_objs = [] ifc_cutter.attributes = [a.name for a in drawing_style.attributes] for obj in camera.users_collection[0].objects: - if 'IfcGrid' in obj.name: + if "IfcGrid" in obj.name: ifc_cutter.grid_objs.append(obj) - elif 'IfcGroup' in obj.name and obj.type == 'CAMERA': + elif "IfcGroup" in obj.name and obj.type == "CAMERA": ifc_cutter.camera_obj = obj - if 'IfcAnnotation/' not in obj.name: + if "IfcAnnotation/" not in obj.name: continue - if 'Leader' in obj.name: + if "Leader" in obj.name: ifc_cutter.leader_obj = (obj, obj.data) - elif 'Stair' in obj.name: + elif "Stair" in obj.name: ifc_cutter.stair_obj = obj - elif 'Equal' in obj.name: + elif "Equal" in obj.name: ifc_cutter.equal_objs.append(obj) - elif 'Dimension' in obj.name: + elif "Dimension" in obj.name: ifc_cutter.dimension_objs.append(obj) - elif 'Break' in obj.name: + elif "Break" in obj.name: ifc_cutter.break_obj = obj - elif 'Hidden' in obj.name: + elif "Hidden" in obj.name: ifc_cutter.hidden_objs.append((obj, obj.data)) - elif 'Solid' in obj.name: + elif "Solid" in obj.name: ifc_cutter.solid_objs.append((obj, obj.data)) - elif 'Plan Level' in obj.name: + elif "Plan Level" in obj.name: ifc_cutter.plan_level_obj = obj - elif 'Section Level' in obj.name: + elif "Section Level" in obj.name: ifc_cutter.section_level_obj = obj - elif obj.type == 'FONT': + elif obj.type == "FONT": ifc_cutter.text_objs.append(obj) else: ifc_cutter.misc_objs.append(obj) ifc_cutter.section_box = { - 'projection': tuple(projection), - 'x_axis': tuple(x_axis), - 'y_axis': tuple(y_axis), - 'top_left_corner': tuple(top_left_corner), - 'x': width, - 'y': height, - 'z': depth, - 'shape': None, - 'face': None + "projection": tuple(projection), + "x_axis": tuple(x_axis), + "y_axis": tuple(y_axis), + "top_left_corner": tuple(top_left_corner), + "x": width, + "y": height, + "z": depth, + "shape": None, + "face": None, } - ifc_cutter.cut_pickle_file = os.path.join(ifc_cutter.data_dir, '{}-cut.pickle'.format(self.diagram_name)) - ifc_cutter.text_pickle_file = os.path.join(ifc_cutter.data_dir, '{}-text.pickle'.format(self.diagram_name)) - ifc_cutter.metadata_pickle_file = os.path.join(ifc_cutter.data_dir, '{}-metadata.pickle'.format(self.diagram_name)) + ifc_cutter.cut_pickle_file = os.path.join(ifc_cutter.data_dir, "{}-cut.pickle".format(self.diagram_name)) + ifc_cutter.text_pickle_file = os.path.join(ifc_cutter.data_dir, "{}-text.pickle".format(self.diagram_name)) + ifc_cutter.metadata_pickle_file = os.path.join( + ifc_cutter.data_dir, "{}-metadata.pickle".format(self.diagram_name) + ) ifc_cutter.should_recut = bpy.context.scene.DocProperties.should_recut ifc_cutter.should_recut_selected = bpy.context.scene.DocProperties.should_recut_selected selected_global_ids = [] for obj in bpy.context.selected_objects: - if 'Ifc' not in obj.name: + if "Ifc" not in obj.name: continue for attribute in obj.BIMObjectProperties.attributes: - if attribute.name == 'GlobalId': + if attribute.name == "GlobalId": selected_global_ids.append(attribute.string_value) break ifc_cutter.selected_global_ids = selected_global_ids ifc_cutter.should_extract = bpy.context.scene.DocProperties.should_extract svg_writer = svgwriter.SvgWriter(ifc_cutter) - if camera.data.BIMCameraProperties.diagram_scale == 'CUSTOM': - human_scale, fraction = camera.data.BIMCameraProperties.custom_diagram_scale.split('|') + if camera.data.BIMCameraProperties.diagram_scale == "CUSTOM": + human_scale, fraction = camera.data.BIMCameraProperties.custom_diagram_scale.split("|") else: - human_scale, fraction = camera.data.BIMCameraProperties.diagram_scale.split('|') - numerator, denominator = fraction.split('/') + human_scale, fraction = camera.data.BIMCameraProperties.diagram_scale.split("|") + numerator, denominator = fraction.split("/") if camera.data.BIMCameraProperties.is_nts: - svg_writer.human_scale = 'NTS' + svg_writer.human_scale = "NTS" else: svg_writer.human_scale = human_scale svg_writer.scale = float(numerator) / float(denominator) ifc_cutter.cut() svg_writer.write() bpy.ops.bim.open_view(view=self.diagram_name) - return {'FINISHED'} + return {"FINISHED"} def create_raster(self, camera, drawing_style): - if drawing_style.render_type == 'NONE': + if drawing_style.render_type == "NONE": return - if drawing_style.render_type == 'DEFAULT': + if drawing_style.render_type == "DEFAULT": return bpy.ops.render.render(write_still=True) previous_visibility = {} @@ -2259,20 +2322,22 @@ class CutSection(bpy.types.Operator): previous_visibility[obj.name] = obj.hide_get() obj.hide_set(True) for obj in bpy.context.visible_objects: - if not obj.data \ - or isinstance(obj.data, bpy.types.Camera) \ - or 'IfcGrid/' in obj.name \ - or 'IfcGridAxis/' in obj.name \ - or 'IfcOpeningElement/' in obj.name \ - or self.does_obj_have_target_view_representation(obj, camera): + if ( + not obj.data + or isinstance(obj.data, bpy.types.Camera) + or "IfcGrid/" in obj.name + or "IfcGridAxis/" in obj.name + or "IfcOpeningElement/" in obj.name + or self.does_obj_have_target_view_representation(obj, camera) + ): previous_visibility[obj.name] = obj.hide_get() obj.hide_set(True) space = self.get_view_3d() previous_shading = space.shading.type previous_format = bpy.context.scene.render.image_settings.file_format - space.shading.type = 'RENDERED' - bpy.context.scene.render.image_settings.file_format = 'PNG' + space.shading.type = "RENDERED" + bpy.context.scene.render.image_settings.file_format = "PNG" bpy.ops.render.opengl(write_still=True) space.shading.type = previous_shading bpy.context.scene.render.image_settings.file_format = previous_format @@ -2280,54 +2345,55 @@ class CutSection(bpy.types.Operator): for name, value in previous_visibility.items(): bpy.data.objects[name].hide_set(value) - def does_obj_have_target_view_representation(self, obj, camera): - return camera.data.BIMCameraProperties.target_view in [c.target_view for c in obj.BIMObjectProperties.representation_contexts] + return camera.data.BIMCameraProperties.target_view in [ + c.target_view for c in obj.BIMObjectProperties.representation_contexts + ] def is_landscape(self): return bpy.context.scene.render.resolution_x > bpy.context.scene.render.resolution_y def get_view_3d(self): for area in bpy.context.screen.areas: - if area.type != 'VIEW_3D': + if area.type != "VIEW_3D": continue for space in area.spaces: - if space.type != 'VIEW_3D': + if space.type != "VIEW_3D": continue return space - class AddSheet(bpy.types.Operator): - bl_idname = 'bim.add_sheet' - bl_label = 'Add Sheet' + bl_idname = "bim.add_sheet" + bl_label = "Add Sheet" def execute(self, context): new = bpy.context.scene.DocProperties.sheets.add() - new.name = '{} - SHEET'.format(len(bpy.context.scene.DocProperties.sheets)) + new.name = "{} - SHEET".format(len(bpy.context.scene.DocProperties.sheets)) sheet_builder = sheeter.SheetBuilder() sheet_builder.data_dir = bpy.context.scene.BIMProperties.data_dir sheet_builder.create(new.name, bpy.context.scene.DocProperties.titleblock) - return {'FINISHED'} + return {"FINISHED"} class OpenSheet(bpy.types.Operator): - bl_idname = 'bim.open_sheet' - bl_label = 'Open Sheet' + bl_idname = "bim.open_sheet" + bl_label = "Open Sheet" def execute(self, context): props = bpy.context.scene.DocProperties open_with_user_command( - bpy.context.preferences.addons['blenderbim'].preferences.svg_command, + bpy.context.preferences.addons["blenderbim"].preferences.svg_command, os.path.join( - bpy.context.scene.BIMProperties.data_dir, 'sheets', - props.sheets[props.active_sheet_index].name + '.svg')) - return {'FINISHED'} + bpy.context.scene.BIMProperties.data_dir, "sheets", props.sheets[props.active_sheet_index].name + ".svg" + ), + ) + return {"FINISHED"} class AddDrawingToSheet(bpy.types.Operator): - bl_idname = 'bim.add_drawing_to_sheet' - bl_label = 'Add Drawing To Sheet' + bl_idname = "bim.add_drawing_to_sheet" + bl_label = "Add Drawing To Sheet" def execute(self, context): props = bpy.context.scene.DocProperties @@ -2335,16 +2401,16 @@ class AddDrawingToSheet(bpy.types.Operator): sheet_builder.data_dir = bpy.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[props.active_drawing_index].name, props.sheets[props.active_sheet_index].name + ) except: - self.report({'ERROR'}, 'Drawings need to be created before being added to a sheet') - return {'FINISHED'} + self.report({"ERROR"}, "Drawings need to be created before being added to a sheet") + return {"FINISHED"} class CreateSheets(bpy.types.Operator): - bl_idname = 'bim.create_sheets' - bl_label = 'Create Sheets' + bl_idname = "bim.create_sheets" + bl_label = "Create Sheets" def execute(self, context): props = bpy.context.scene.DocProperties @@ -2353,13 +2419,13 @@ class CreateSheets(bpy.types.Operator): sheet_builder.data_dir = bpy.context.scene.BIMProperties.data_dir sheet_builder.build(name) - svg2pdf_command = bpy.context.preferences.addons['blenderbim'].preferences.svg2pdf_command - svg2dxf_command = bpy.context.preferences.addons['blenderbim'].preferences.svg2dxf_command + svg2pdf_command = bpy.context.preferences.addons["blenderbim"].preferences.svg2pdf_command + svg2dxf_command = bpy.context.preferences.addons["blenderbim"].preferences.svg2dxf_command if svg2pdf_command: - path = os.path.join(bpy.context.scene.BIMProperties.data_dir, 'build', name) - svg = os.path.join(path, name + '.svg') - pdf = os.path.join(path, name + '.pdf') + path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "build", name) + svg = os.path.join(path, name + ".svg") + pdf = os.path.join(path, name + ".pdf") # With great power comes great responsibility. Example: # [['inkscape', svg, '-o', pdf]] commands = eval(svg2pdf_command) @@ -2367,10 +2433,10 @@ class CreateSheets(bpy.types.Operator): subprocess.run(command) if svg2dxf_command: - path = os.path.join(bpy.context.scene.BIMProperties.data_dir, 'build', name) - svg = os.path.join(path, name + '.svg') - eps = os.path.join(path, name + '.eps') - dxf = os.path.join(path, name + '.dxf') + path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "build", name) + svg = os.path.join(path, name + ".svg") + eps = os.path.join(path, name + ".eps") + dxf = os.path.join(path, name + ".dxf") base = os.path.join(path, name) # With great power comes great responsibility. Example: # [['inkscape', svg, '-o', eps], ['pstoedit', '-dt', '-f', 'dxf:-polyaslines -mm', eps, dxf, '-psarg', '-dNOSAFER']] @@ -2378,47 +2444,51 @@ class CreateSheets(bpy.types.Operator): for command in commands: subprocess.run(command) - if svg2pdf_command: - open_with_user_command(bpy.context.preferences.addons['blenderbim'].preferences.pdf_command, pdf) + open_with_user_command(bpy.context.preferences.addons["blenderbim"].preferences.pdf_command, pdf) else: open_with_user_command( - bpy.context.preferences.addons['blenderbim'].preferences.svg_command, - os.path.join(bpy.context.scene.BIMProperties.data_dir, 'build', name, name + '.svg')) - return {'FINISHED'} + bpy.context.preferences.addons["blenderbim"].preferences.svg_command, + os.path.join(bpy.context.scene.BIMProperties.data_dir, "build", name, name + ".svg"), + ) + return {"FINISHED"} class ActivateView(bpy.types.Operator): - bl_idname = 'bim.activate_view' - bl_label = 'Activate View' + bl_idname = "bim.activate_view" + bl_label = "Activate View" drawing_index: bpy.props.IntProperty() def execute(self, context): camera = bpy.context.scene.DocProperties.drawings[self.drawing_index].camera if not camera: - return {'FINISHED'} + return {"FINISHED"} bpy.context.scene.camera = camera - area = next(area for area in bpy.context.screen.areas if area.type == 'VIEW_3D') - area.spaces[0].region_3d.view_perspective = 'CAMERA' - views_collection = bpy.data.collections.get('Views') + area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") + area.spaces[0].region_3d.view_perspective = "CAMERA" + views_collection = bpy.data.collections.get("Views") for collection in views_collection.children: # We assume the project collection is at the top level for project_collection in bpy.context.view_layer.layer_collection.children: # We assume a convention that the 'Views' collection is directly # in the project collection - if 'Views' in project_collection.children \ - and collection.name in project_collection.children['Views'].children: - project_collection.children['Views'].children[collection.name].hide_viewport = True + if ( + "Views" in project_collection.children + and collection.name in project_collection.children["Views"].children + ): + project_collection.children["Views"].children[collection.name].hide_viewport = True bpy.data.collections.get(collection.name).hide_render = True - bpy.context.view_layer.layer_collection.children['Views'].children[camera.users_collection[0].name].hide_viewport = False + bpy.context.view_layer.layer_collection.children["Views"].children[ + camera.users_collection[0].name + ].hide_viewport = False bpy.data.collections.get(camera.users_collection[0].name).hide_render = False bpy.ops.bim.activate_drawing_style() - return {'FINISHED'} + return {"FINISHED"} class SwitchContext(bpy.types.Operator): - bl_idname = 'bim.switch_context' - bl_label = 'Switch Context' + bl_idname = "bim.switch_context" + bl_label = "Switch Context" has_target_context: bpy.props.BoolProperty() context_name: bpy.props.StringProperty() subcontext_name: bpy.props.StringProperty() @@ -2429,13 +2499,13 @@ class SwitchContext(bpy.types.Operator): def execute(self, context): self.obj = bpy.context.active_object - if '/' not in self.obj.data.name: - self.obj.data.name = ifcopenshell.guid.compress(str(uuid.uuid4()).replace('-', '')) - self.obj.data.name = 'Model/Body/MODEL_VIEW/' + self.obj.data.name + if "/" not in self.obj.data.name: + self.obj.data.name = ifcopenshell.guid.compress(str(uuid.uuid4()).replace("-", "")) + self.obj.data.name = "Model/Body/MODEL_VIEW/" + self.obj.data.name representation_context = self.obj.BIMObjectProperties.representation_contexts.add() - representation_context.context = 'Model' - representation_context.name = 'Body' - representation_context.target_view = 'MODEL_VIEW' + representation_context.context = "Model" + representation_context.name = "Body" + representation_context.target_view = "MODEL_VIEW" self.context = bpy.context.scene.BIMProperties.available_contexts self.subcontext = bpy.context.scene.BIMProperties.available_subcontexts @@ -2448,21 +2518,25 @@ class SwitchContext(bpy.types.Operator): existing_mesh = self.obj.data existing_mesh.use_fake_user = True - mesh = bpy.data.meshes.get('{}/{}/{}/{}'.format( - self.context, self.subcontext, self.target_view, self.obj.data.name.split('/')[3])) + mesh = bpy.data.meshes.get( + "{}/{}/{}/{}".format(self.context, self.subcontext, self.target_view, self.obj.data.name.split("/")[3]) + ) if not mesh: try: - global_id = self.obj.BIMObjectProperties.attributes.get('GlobalId').string_value + global_id = self.obj.BIMObjectProperties.attributes.get("GlobalId").string_value mesh = self.pull_mesh_from_ifc(global_id) except: mesh = self.obj.data.copy() - mesh.name = '{}/{}/{}/{}'.format( - self.context, self.subcontext, self.target_view, self.obj.data.name.split('/')[3]) + mesh.name = "{}/{}/{}/{}".format( + self.context, self.subcontext, self.target_view, self.obj.data.name.split("/")[3] + ) has_context = False for context in self.obj.BIMObjectProperties.representation_contexts: - if context.context == self.context \ - and context.name == self.subcontext \ - and context.target_view == self.target_view: + if ( + context.context == self.context + and context.name == self.subcontext + and context.target_view == self.target_view + ): has_context = True break if not has_context: @@ -2472,40 +2546,45 @@ class SwitchContext(bpy.types.Operator): representation_context.target_view = self.target_view mesh.use_fake_user = True self.obj.data = mesh - return {'FINISHED'} + return {"FINISHED"} def pull_mesh_from_ifc(self, global_id): self.file = ifc.IfcStore.get_file() - logger = logging.getLogger('ImportIFC') + logger = logging.getLogger("ImportIFC") ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, ifc.IfcStore.path, logger) element = self.file.by_id(global_id) settings = ifcopenshell.geom.settings() settings.set(settings.INCLUDE_CURVES, True) - if element.is_a('IfcProduct'): + if element.is_a("IfcProduct"): representations = element.Representation.Representations else: representations = element.RepresentationMaps for rep in element.Representation.Representations: - if rep.ContextOfItems.is_a('IfcGeometricRepresentationSubContext') \ - and rep.ContextOfItems.ContextType == self.context \ - and rep.ContextOfItems.ContextIdentifier == self.subcontext \ - and rep.ContextOfItems.TargetView == self.target_view: + if ( + rep.ContextOfItems.is_a("IfcGeometricRepresentationSubContext") + and rep.ContextOfItems.ContextType == self.context + and rep.ContextOfItems.ContextIdentifier == self.subcontext + and rep.ContextOfItems.TargetView == self.target_view + ): break - elif rep.ContextOfItems.is_a('IfcGeometricRepresentationContext') \ - and rep.ContextOfItems.ContextType == self.context \ - and rep.ContextOfItems.ContextIdentifier == self.subcontext: + elif ( + rep.ContextOfItems.is_a("IfcGeometricRepresentationContext") + and rep.ContextOfItems.ContextType == self.context + and rep.ContextOfItems.ContextIdentifier == self.subcontext + ): break - if not element.is_a('IfcProduct'): + if not element.is_a("IfcProduct"): rep = rep.MappedRepresentation shape = ifcopenshell.geom.create_shape(settings, rep) ifc_importer = import_ifc.IfcImporter(ifc_import_settings) ifc_importer.file = self.file mesh = ifc_importer.create_mesh(element, shape) - mesh.name = '{}/{}/{}/{}'.format( - self.context, self.subcontext, self.target_view, self.obj.data.name.split('/')[3]) + mesh.name = "{}/{}/{}/{}".format( + self.context, self.subcontext, self.target_view, self.obj.data.name.split("/")[3] + ) self.obj.data = mesh material_creator = import_ifc.MaterialCreator(ifc_import_settings) material_creator.create(element, self.obj, mesh) @@ -2513,23 +2592,24 @@ class SwitchContext(bpy.types.Operator): class RemoveContext(bpy.types.Operator): - bl_idname = 'bim.remove_context' - bl_label = 'Remove Context' + bl_idname = "bim.remove_context" + bl_label = "Remove Context" index: bpy.props.IntProperty() def execute(self, context): obj = bpy.context.active_object data = obj.BIMObjectProperties.representation_contexts[self.index] - if '/' not in obj.data.name: - obj.data.name = 'Model/Body/MODEL_VIEW/' + obj.data.name + if "/" not in obj.data.name: + obj.data.name = "Model/Body/MODEL_VIEW/" + obj.data.name - mesh = bpy.data.meshes.get('{}/{}/{}/{}'.format( - data.context, data.name, data.target_view, obj.data.name.split('/')[3])) + mesh = bpy.data.meshes.get( + "{}/{}/{}/{}".format(data.context, data.name, data.target_view, obj.data.name.split("/")[3]) + ) if mesh: if obj.data == mesh: - void_name = 'Void/Void/Void/' + obj.data.name.split('/')[3] + void_name = "Void/Void/Void/" + obj.data.name.split("/")[3] void_mesh = bpy.data.meshes.get(void_name) if not void_mesh: void_mesh = bpy.data.meshes.new(void_name) @@ -2537,36 +2617,36 @@ class RemoveContext(bpy.types.Operator): bpy.data.meshes.remove(mesh) obj.BIMObjectProperties.representation_contexts.remove(self.index) - return {'FINISHED'} + return {"FINISHED"} class OpenUpstream(bpy.types.Operator): - bl_idname = 'bim.open_upstream' - bl_label = 'Open Upstream Reference' + bl_idname = "bim.open_upstream" + bl_label = "Open Upstream Reference" page: bpy.props.StringProperty() def execute(self, context): - if self.page == 'home': - webbrowser.open('https://blenderbim.org/') - elif self.page == 'docs': - webbrowser.open('https://blenderbim.org/docs/') - elif self.page == 'wiki': - webbrowser.open('https://wiki.osarch.org/index.php?title=Category:BlenderBIM_Add-on') - elif self.page == 'community': - webbrowser.open('https://community.osarch.org/') - return {'FINISHED'} + if self.page == "home": + webbrowser.open("https://blenderbim.org/") + elif self.page == "docs": + webbrowser.open("https://blenderbim.org/docs/") + elif self.page == "wiki": + webbrowser.open("https://wiki.osarch.org/index.php?title=Category:BlenderBIM_Add-on") + elif self.page == "community": + webbrowser.open("https://community.osarch.org/") + return {"FINISHED"} class BIM_OT_CopyAttributesToSelection(bpy.types.Operator): """Copies attributes from the active object towards selected objects""" + bl_idname = "bim.copy_attributes_to_selection" bl_label = "Copy Attributes To Selection" - prop_base = bpy.props.StringProperty() # data for properties to assign to + prop_base = bpy.props.StringProperty() # data for properties to assign to prop_name = bpy.props.StringProperty(description="Property name which to change") - sub_props = bpy.props.StringProperty() # properties which to copy (commasep). (empty = all) - collection_element = bpy.props.BoolProperty( - description="If this is a collection element, copy the complete thing") + sub_props = bpy.props.StringProperty() # properties which to copy (commasep). (empty = all) + collection_element = bpy.props.BoolProperty(description="If this is a collection element, copy the complete thing") @classmethod def poll(cls, context): @@ -2574,23 +2654,26 @@ class BIM_OT_CopyAttributesToSelection(bpy.types.Operator): def execute(self, context): active_object = bpy.context.active_object - selected_objects = [obj for obj in bpy.context.visible_objects - if obj.type == active_object.type and obj in bpy.context.selected_objects and obj!=active_object] + selected_objects = [ + obj + for obj in bpy.context.visible_objects + if obj.type == active_object.type and obj in bpy.context.selected_objects and obj != active_object + ] if self.prop_base: - prop_base = eval('active_object.'+ self.prop_base) + prop_base = eval("active_object." + self.prop_base) else: prop_base = active_object if not self.collection_element: self.copy_simple(prop_base, selected_objects) - return {'FINISHED'} + return {"FINISHED"} self.copy_collection(prop_base, selected_objects) - return {'FINISHED'} + return {"FINISHED"} def copy_simple(self, prop_base, selected_objects): prop = getattr(prop_base, self.prop_name) for obj in selected_objects: if self.prop_base: - new_prop_base = eval('obj.' + self.prop_base) + new_prop_base = eval("obj." + self.prop_base) else: new_prop_base = obj setattr(new_prop_base, self.prop_name, prop) @@ -2599,7 +2682,7 @@ class BIM_OT_CopyAttributesToSelection(bpy.types.Operator): prop = prop_base[self.prop_name] for obj in selected_objects: if self.prop_base: - new_prop_base = eval('obj.' + self.prop_base) + new_prop_base = eval("obj." + self.prop_base) else: new_prop_base = obj @@ -2609,20 +2692,22 @@ class BIM_OT_CopyAttributesToSelection(bpy.types.Operator): new_prop_base = new_prop_base.add() if self.sub_props: - for p in self.sub_props.replace(' ','').split(','): + for p in self.sub_props.replace(" ", "").split(","): try: setattr(new_prop_base, p, getattr(prop, p)) - except: pass + except: + pass else: for p in dir(prop): try: setattr(new_prop_base, p, getattr(prop, p)) - except: pass + except: + pass class CopyPropertyToSelection(bpy.types.Operator): - bl_idname = 'bim.copy_property_to_selection' - bl_label = 'Copy Property To Selection' + bl_idname = "bim.copy_property_to_selection" + bl_label = "Copy Property To Selection" pset_name: bpy.props.StringProperty() prop_name: bpy.props.StringProperty() prop_value: bpy.props.StringProperty() @@ -2631,22 +2716,22 @@ class CopyPropertyToSelection(bpy.types.Operator): self.applicable_psets_cache = {} self.empty = ifcopenshell.file() for obj in bpy.context.selected_objects: - if '/' not in obj.name: + if "/" not in obj.name: continue pset = obj.BIMObjectProperties.psets.get(self.pset_name) if not pset: - applicable_psets = self.get_applicable_psets(obj.name.split('/')[0]) + applicable_psets = self.get_applicable_psets(obj.name.split("/")[0]) if self.pset_name not in applicable_psets: continue pset = obj.BIMObjectProperties.psets.add() pset.name = self.pset_name - for template_prop_name in schema.ifc.psets[self.pset_name]['HasPropertyTemplates'].keys(): + for template_prop_name in schema.ifc.psets[self.pset_name]["HasPropertyTemplates"].keys(): prop = pset.properties.add() prop.name = template_prop_name prop = pset.properties.get(self.prop_name) if prop: prop.string_value = self.prop_value - return {'FINISHED'} + return {"FINISHED"} # TODO: move into util module. See bug #971 def get_applicable_psets(self, element_class): @@ -2677,40 +2762,42 @@ class BIM_OT_ChangeClassificationLevel(bpy.types.Operator): if self.path_itm: lst.root = self.path_itm else: - lst.root = '' - return {'FINISHED'} + lst.root = "" + return {"FINISHED"} class AddPropertySetTemplate(bpy.types.Operator): - bl_idname = 'bim.add_property_set_template' - bl_label = 'Add Property Set Template' + bl_idname = "bim.add_property_set_template" + bl_label = "Add Property Set Template" def execute(self, context): - context.scene.BIMProperties.active_property_set_template.global_id = '' - context.scene.BIMProperties.active_property_set_template.name = 'New_Pset' - context.scene.BIMProperties.active_property_set_template.description = '' - context.scene.BIMProperties.active_property_set_template.template_type = 'PSET_TYPEDRIVENONLY' - context.scene.BIMProperties.active_property_set_template.applicable_entity = 'IfcTypeObject' + context.scene.BIMProperties.active_property_set_template.global_id = "" + context.scene.BIMProperties.active_property_set_template.name = "New_Pset" + context.scene.BIMProperties.active_property_set_template.description = "" + context.scene.BIMProperties.active_property_set_template.template_type = "PSET_TYPEDRIVENONLY" + context.scene.BIMProperties.active_property_set_template.applicable_entity = "IfcTypeObject" while len(bpy.context.scene.BIMProperties.property_templates) > 0: bpy.context.scene.BIMProperties.property_templates.remove(0) - return {'FINISHED'} + return {"FINISHED"} class RemovePropertySetTemplate(bpy.types.Operator): - bl_idname = 'bim.remove_property_set_template' - bl_label = 'Remove Property Set Template' + bl_idname = "bim.remove_property_set_template" + bl_label = "Remove Property Set Template" def execute(self, context): template = ifc.IfcStore.pset_template_file.by_guid(context.scene.BIMProperties.property_set_templates) ifc.IfcStore.pset_template_file.remove(template) ifc.IfcStore.pset_template_file.write(ifc.IfcStore.pset_template_path) from . import prop + prop.refreshPropertySetTemplates(self, context) - return {'FINISHED'} + return {"FINISHED"} + class EditPropertySetTemplate(bpy.types.Operator): - bl_idname = 'bim.edit_property_set_template' - bl_label = 'Edit Property Set Template' + bl_idname = "bim.edit_property_set_template" + bl_label = "Edit Property Set Template" def execute(self, context): template = ifc.IfcStore.pset_template_file.by_guid(context.scene.BIMProperties.property_set_templates) @@ -2725,18 +2812,19 @@ class EditPropertySetTemplate(bpy.types.Operator): if template.HasPropertyTemplates: for property_template in template.HasPropertyTemplates: - if not property_template.is_a('IfcSimplePropertyTemplate'): + if not property_template.is_a("IfcSimplePropertyTemplate"): continue new = context.scene.BIMProperties.property_templates.add() new.global_id = property_template.GlobalId new.name = property_template.Name new.description = property_template.Description new.primary_measure_type = property_template.PrimaryMeasureType - return {'FINISHED'} + return {"FINISHED"} + class SavePropertySetTemplate(bpy.types.Operator): - bl_idname = 'bim.save_property_set_template' - bl_label = 'Save Property Set Template' + bl_idname = "bim.save_property_set_template" + bl_label = "Save Property Set Template" def execute(self, context): blender_property_set_template = context.scene.BIMProperties.active_property_set_template @@ -2767,8 +2855,8 @@ class SavePropertySetTemplate(bpy.types.Operator): property_template.Name = blender_property_template.name property_template.Description = blender_property_template.description property_template.PrimaryMeasureType = blender_property_template.primary_measure_type - property_template.TemplateType = 'P_SINGLEVALUE' - property_template.AccessState = 'READWRITE' + property_template.TemplateType = "P_SINGLEVALUE" + property_template.AccessState = "READWRITE" saved_global_ids.append(property_template.GlobalId) for element in template.HasPropertyTemplates: @@ -2777,32 +2865,33 @@ class SavePropertySetTemplate(bpy.types.Operator): ifc.IfcStore.pset_template_file.write(ifc.IfcStore.pset_template_path) from . import prop + prop.refreshPropertySetTemplates(self, context) - return {'FINISHED'} + return {"FINISHED"} class AddPropertyTemplate(bpy.types.Operator): - bl_idname = 'bim.add_property_template' - bl_label = 'Add Property Template' + bl_idname = "bim.add_property_template" + bl_label = "Add Property Template" def execute(self, context): context.scene.BIMProperties.property_templates.add() - return {'FINISHED'} + return {"FINISHED"} class RemovePropertyTemplate(bpy.types.Operator): - bl_idname = 'bim.remove_property_template' - bl_label = 'Remove Property Template' + bl_idname = "bim.remove_property_template" + bl_label = "Remove Property Template" index: bpy.props.IntProperty() def execute(self, context): bpy.context.scene.BIMProperties.property_templates.remove(self.index) - return {'FINISHED'} + return {"FINISHED"} class AddSectionPlane(bpy.types.Operator): - bl_idname = 'bim.add_section_plane' - bl_label = 'Add Temporary Section Cutaway' + bl_idname = "bim.add_section_plane" + bl_label = "Add Temporary Section Cutaway" def execute(self, context): obj = self.create_section_obj() @@ -2813,112 +2902,113 @@ class AddSectionPlane(bpy.types.Operator): self.append_obj_to_section_override_node(obj) self.add_default_material_if_none_exists() self.override_materials() - return {'FINISHED'} + return {"FINISHED"} def create_section_obj(self): - section = bpy.data.objects.new('Section', None) - section.empty_display_type = 'SINGLE_ARROW' + section = bpy.data.objects.new("Section", None) + section.empty_display_type = "SINGLE_ARROW" section.empty_display_size = 5 section.show_in_front = True - if bpy.context.active_object.select_get() \ - and isinstance(bpy.context.active_object.data, bpy.types.Camera): - section.matrix_world = bpy.context.active_object.matrix_world @ Euler((radians(180.0), 0.0, 0.0), 'XYZ').to_matrix().to_4x4() + if bpy.context.active_object.select_get() and isinstance(bpy.context.active_object.data, bpy.types.Camera): + section.matrix_world = ( + bpy.context.active_object.matrix_world @ Euler((radians(180.0), 0.0, 0.0), "XYZ").to_matrix().to_4x4() + ) else: - section.rotation_euler = Euler((radians(180.0), 0.0, 0.0), 'XYZ') + section.rotation_euler = Euler((radians(180.0), 0.0, 0.0), "XYZ") section.location = bpy.context.scene.cursor.location - collection = bpy.data.collections.get('Sections') + collection = bpy.data.collections.get("Sections") if not collection: - collection = bpy.data.collections.new('Sections') + collection = bpy.data.collections.new("Sections") bpy.context.scene.collection.children.link(collection) collection.objects.link(section) return section def has_section_override_node(self): - return bpy.data.node_groups.get('Section Override') + return bpy.data.node_groups.get("Section Override") def create_section_compare_node(self): - group = bpy.data.node_groups.new('Section Compare', type='ShaderNodeTree') - group_input = group.nodes.new(type='NodeGroupInput') - group_output = group.nodes.new(type='NodeGroupOutput') - separate_xyz_a = group.nodes.new(type='ShaderNodeSeparateXYZ') - separate_xyz_b = group.nodes.new(type='ShaderNodeSeparateXYZ') - gt_a = group.nodes.new(type='ShaderNodeMath') - gt_a.operation = 'GREATER_THAN' + group = bpy.data.node_groups.new("Section Compare", type="ShaderNodeTree") + group_input = group.nodes.new(type="NodeGroupInput") + group_output = group.nodes.new(type="NodeGroupOutput") + separate_xyz_a = group.nodes.new(type="ShaderNodeSeparateXYZ") + separate_xyz_b = group.nodes.new(type="ShaderNodeSeparateXYZ") + gt_a = group.nodes.new(type="ShaderNodeMath") + gt_a.operation = "GREATER_THAN" gt_a.inputs[1].default_value = 0 - gt_b = group.nodes.new(type='ShaderNodeMath') - gt_b.operation = 'GREATER_THAN' + gt_b = group.nodes.new(type="ShaderNodeMath") + gt_b.operation = "GREATER_THAN" gt_b.inputs[1].default_value = 0 - add = group.nodes.new(type='ShaderNodeMath') - compare = group.nodes.new(type='ShaderNodeMath') - compare.operation = 'COMPARE' + add = group.nodes.new(type="ShaderNodeMath") + compare = group.nodes.new(type="ShaderNodeMath") + compare.operation = "COMPARE" compare.inputs[1].default_value = 2 - group.links.new(group_input.outputs[''], separate_xyz_a.inputs[0]) - group.links.new(group_input.outputs[''], separate_xyz_b.inputs[0]) + group.links.new(group_input.outputs[""], separate_xyz_a.inputs[0]) + group.links.new(group_input.outputs[""], separate_xyz_b.inputs[0]) group.links.new(separate_xyz_a.outputs[2], gt_a.inputs[0]) group.links.new(separate_xyz_b.outputs[2], gt_b.inputs[0]) group.links.new(gt_a.outputs[0], add.inputs[0]) group.links.new(gt_b.outputs[0], add.inputs[1]) group.links.new(add.outputs[0], compare.inputs[0]) - group.links.new(compare.outputs[0], group_output.inputs['']) + group.links.new(compare.outputs[0], group_output.inputs[""]) def create_section_override_node(self, obj): - group = bpy.data.node_groups.new('Section Override', type='ShaderNodeTree') + group = bpy.data.node_groups.new("Section Override", type="ShaderNodeTree") - group_input = group.nodes.new(type='NodeGroupInput') - group_output = group.nodes.new(type='NodeGroupOutput') + group_input = group.nodes.new(type="NodeGroupInput") + group_output = group.nodes.new(type="NodeGroupOutput") - backfacing = group.nodes.new(type='ShaderNodeNewGeometry') - backfacing_mix = group.nodes.new(type='ShaderNodeMixShader') - emission = group.nodes.new(type='ShaderNodeEmission') + backfacing = group.nodes.new(type="ShaderNodeNewGeometry") + backfacing_mix = group.nodes.new(type="ShaderNodeMixShader") + emission = group.nodes.new(type="ShaderNodeEmission") emission.inputs[0].default_value = list(bpy.context.scene.BIMProperties.section_plane_colour) + [1] - group.links.new(backfacing.outputs['Backfacing'], backfacing_mix.inputs[0]) - group.links.new(group_input.outputs[''], backfacing_mix.inputs[1]) - group.links.new(emission.outputs['Emission'], backfacing_mix.inputs[2]) + group.links.new(backfacing.outputs["Backfacing"], backfacing_mix.inputs[0]) + group.links.new(group_input.outputs[""], backfacing_mix.inputs[1]) + group.links.new(emission.outputs["Emission"], backfacing_mix.inputs[2]) - transparent = group.nodes.new(type='ShaderNodeBsdfTransparent') - section_mix = group.nodes.new(type='ShaderNodeMixShader') - section_mix.name = 'Section Mix' + transparent = group.nodes.new(type="ShaderNodeBsdfTransparent") + section_mix = group.nodes.new(type="ShaderNodeMixShader") + section_mix.name = "Section Mix" - group.links.new(transparent.outputs['BSDF'], section_mix.inputs[1]) - group.links.new(backfacing_mix.outputs['Shader'], section_mix.inputs[2]) + group.links.new(transparent.outputs["BSDF"], section_mix.inputs[1]) + group.links.new(backfacing_mix.outputs["Shader"], section_mix.inputs[2]) - group.links.new(section_mix.outputs['Shader'], group_output.inputs['']) + group.links.new(section_mix.outputs["Shader"], group_output.inputs[""]) - cut_obj = group.nodes.new(type='ShaderNodeTexCoord') + cut_obj = group.nodes.new(type="ShaderNodeTexCoord") cut_obj.object = obj - section_compare = group.nodes.new(type='ShaderNodeGroup') - section_compare.node_tree = bpy.data.node_groups.get('Section Compare') - section_compare.name = 'Last Section Compare' - value = group.nodes.new(type='ShaderNodeValue') - value.name = 'Mock Section' - group.links.new(cut_obj.outputs['Object'], section_compare.inputs[0]) + section_compare = group.nodes.new(type="ShaderNodeGroup") + section_compare.node_tree = bpy.data.node_groups.get("Section Compare") + section_compare.name = "Last Section Compare" + value = group.nodes.new(type="ShaderNodeValue") + value.name = "Mock Section" + group.links.new(cut_obj.outputs["Object"], section_compare.inputs[0]) group.links.new(value.outputs[0], section_compare.inputs[1]) group.links.new(section_compare.outputs[0], section_mix.inputs[0]) def append_obj_to_section_override_node(self, obj): - group = bpy.data.node_groups.get('Section Override') - cut_obj = group.nodes.new(type='ShaderNodeTexCoord') + group = bpy.data.node_groups.get("Section Override") + cut_obj = group.nodes.new(type="ShaderNodeTexCoord") cut_obj.object = obj - section_compare = group.nodes.new(type='ShaderNodeGroup') - section_compare.node_tree = bpy.data.node_groups.get('Section Compare') + section_compare = group.nodes.new(type="ShaderNodeGroup") + section_compare.node_tree = bpy.data.node_groups.get("Section Compare") - last_compare = group.nodes.get('Last Section Compare') - last_compare.name = 'Section Compare' - mock_section = group.nodes.get('Mock Section') - section_mix = group.nodes.get('Section Mix') + last_compare = group.nodes.get("Last Section Compare") + last_compare.name = "Section Compare" + mock_section = group.nodes.get("Mock Section") + section_mix = group.nodes.get("Section Mix") group.links.new(last_compare.outputs[0], section_compare.inputs[0]) group.links.new(mock_section.outputs[0], section_compare.inputs[1]) - group.links.new(cut_obj.outputs['Object'], last_compare.inputs[1]) + group.links.new(cut_obj.outputs["Object"], last_compare.inputs[1]) group.links.new(section_compare.outputs[0], section_mix.inputs[0]) - section_compare.name = 'Last Section Compare' + section_compare.name = "Last Section Compare" def add_default_material_if_none_exists(self): - material = bpy.data.materials.get('Section Override') + material = bpy.data.materials.get("Section Override") if not material: - material = bpy.data.materials.new('Section Override') + material = bpy.data.materials.new("Section Override") material.use_nodes = True if bpy.context.scene.BIMProperties.should_section_selected_objects: @@ -2928,33 +3018,30 @@ class AddSectionPlane(bpy.types.Operator): for obj in objects: aggregate = obj.instance_collection - if aggregate and 'IfcRelAggregates/' in aggregate.name: + if aggregate and "IfcRelAggregates/" in aggregate.name: for part in aggregate.objects: objects.append(part) - if not (obj.data \ - and hasattr(obj.data, 'materials') \ - and obj.data.materials \ - and obj.data.materials[0]): - if obj.data and hasattr(obj.data, 'materials'): + if not (obj.data and hasattr(obj.data, "materials") and obj.data.materials and obj.data.materials[0]): + if obj.data and hasattr(obj.data, "materials"): if len(obj.material_slots): obj.material_slots[0].material = material else: obj.data.materials.append(material) def override_materials(self): - override = bpy.data.node_groups.get('Section Override') + override = bpy.data.node_groups.get("Section Override") for material in bpy.data.materials: material.use_nodes = True - if material.node_tree.nodes.get('Section Override'): + if material.node_tree.nodes.get("Section Override"): continue - material.blend_method = 'HASHED' - material.shadow_method = 'HASHED' - material_output = self.get_node(material.node_tree.nodes, 'OUTPUT_MATERIAL') + material.blend_method = "HASHED" + material.shadow_method = "HASHED" + material_output = self.get_node(material.node_tree.nodes, "OUTPUT_MATERIAL") if not material_output: continue from_socket = material_output.inputs[0].links[0].from_socket - section_override = material.node_tree.nodes.new(type='ShaderNodeGroup') - section_override.name = 'Section Override' + section_override = material.node_tree.nodes.new(type="ShaderNodeGroup") + section_override.name = "Section Override" section_override.node_tree = override material.node_tree.links.new(from_socket, section_override.inputs[0]) material.node_tree.links.new(section_override.outputs[0], material_output.inputs[0]) @@ -2966,93 +3053,94 @@ class AddSectionPlane(bpy.types.Operator): class RemoveSectionPlane(bpy.types.Operator): - bl_idname = 'bim.remove_section_plane' - bl_label = 'Remove Temporary Section Cutaway' + bl_idname = "bim.remove_section_plane" + bl_label = "Remove Temporary Section Cutaway" def execute(self, context): name = bpy.context.active_object.name - section_override = bpy.data.node_groups.get('Section Override') + section_override = bpy.data.node_groups.get("Section Override") if not section_override: - return {'FINISHED'} + return {"FINISHED"} for node in section_override.nodes: - if node.type != 'TEX_COORD' or node.object.name != name: + if node.type != "TEX_COORD" or node.object.name != name: continue - section_compare = node.outputs['Object'].links[0].to_node + section_compare = node.outputs["Object"].links[0].to_node # If the tex coord links to section_compare.inputs[1], it is called 'Input_3' - if node.outputs['Object'].links[0].to_socket.identifier == 'Input_3': + if node.outputs["Object"].links[0].to_socket.identifier == "Input_3": section_override.links.new( - section_compare.inputs[0].links[0].from_socket, - section_compare.outputs[0].links[0].to_socket) - else: # If it links to section_compare.inputs[0] - if section_compare.inputs[1].links[0].from_node.name == 'Mock Section': + section_compare.inputs[0].links[0].from_socket, section_compare.outputs[0].links[0].to_socket + ) + else: # If it links to section_compare.inputs[0] + if section_compare.inputs[1].links[0].from_node.name == "Mock Section": # Then it is the very last section. Purge everything. self.purge_all_section_data() - return {'FINISHED'} + return {"FINISHED"} section_override.links.new( - section_compare.inputs[1].links[0].from_socket, - section_compare.outputs[0].links[0].to_socket) + section_compare.inputs[1].links[0].from_socket, section_compare.outputs[0].links[0].to_socket + ) section_override.nodes.remove(section_compare) section_override.nodes.remove(node) - old_last_compare = section_override.nodes.get('Last Section Compare') - old_last_compare.name = 'Section Compare' - section_mix = section_override.nodes.get('Section Mix') + old_last_compare = section_override.nodes.get("Last Section Compare") + old_last_compare.name = "Section Compare" + section_mix = section_override.nodes.get("Section Mix") new_last_compare = section_mix.inputs[0].links[0].from_node - new_last_compare.name = 'Last Section Compare' + new_last_compare.name = "Last Section Compare" bpy.ops.object.delete({"selected_objects": [bpy.context.active_object]}) - return {'FINISHED'} + return {"FINISHED"} def purge_all_section_data(self): - bpy.data.materials.remove(bpy.data.materials.get('Section Override')) + bpy.data.materials.remove(bpy.data.materials.get("Section Override")) for material in bpy.data.materials: if not material.node_tree: continue - override = material.node_tree.nodes.get('Section Override') + override = material.node_tree.nodes.get("Section Override") if not override: continue material.node_tree.links.new( - override.inputs[0].links[0].from_socket, - override.outputs[0].links[0].to_socket) + override.inputs[0].links[0].from_socket, override.outputs[0].links[0].to_socket + ) material.node_tree.nodes.remove(override) - bpy.data.node_groups.remove(bpy.data.node_groups.get('Section Override')) - bpy.data.node_groups.remove(bpy.data.node_groups.get('Section Compare')) + bpy.data.node_groups.remove(bpy.data.node_groups.get("Section Override")) + bpy.data.node_groups.remove(bpy.data.node_groups.get("Section Compare")) bpy.ops.object.delete({"selected_objects": [bpy.context.active_object]}) class AddCsvAttribute(bpy.types.Operator): - bl_idname = 'bim.add_csv_attribute' - bl_label = 'Add CSV Attribute' + bl_idname = "bim.add_csv_attribute" + bl_label = "Add CSV Attribute" def execute(self, context): attribute = bpy.context.scene.BIMProperties.csv_attributes.add() - return {'FINISHED'} + return {"FINISHED"} class RemoveCsvAttribute(bpy.types.Operator): - bl_idname = 'bim.remove_csv_attribute' - bl_label = 'Remove CSV Attribute' + bl_idname = "bim.remove_csv_attribute" + bl_label = "Remove CSV Attribute" index: bpy.props.IntProperty() def execute(self, context): bpy.context.scene.BIMProperties.csv_attributes.remove(self.index) - return {'FINISHED'} + return {"FINISHED"} class ExportIfcCsv(bpy.types.Operator): - bl_idname = 'bim.export_ifccsv' - bl_label = 'Export IFC to CSV' - filename_ext = '.csv' - filepath: bpy.props.StringProperty(subtype='FILE_PATH') + bl_idname = "bim.export_ifccsv" + bl_label = "Export IFC to CSV" + filename_ext = ".csv" + filepath: bpy.props.StringProperty(subtype="FILE_PATH") def invoke(self, context, event): - self.filepath = bpy.path.ensure_ext(bpy.data.filepath, '.csv') + self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".csv") WindowManager = context.window_manager WindowManager.fileselect_add(self) - return {'RUNNING_MODAL'} + return {"RUNNING_MODAL"} def execute(self, context): import ifccsv - self.filepath = bpy.path.ensure_ext(self.filepath, '.csv') + + self.filepath = bpy.path.ensure_ext(self.filepath, ".csv") ifc_file = ifcopenshell.open(bpy.context.scene.BIMProperties.ifc_file) selector = ifcopenshell.util.selector.Selector() results = selector.parse(ifc_file, bpy.context.scene.BIMProperties.ifc_selector) @@ -3061,110 +3149,110 @@ class ExportIfcCsv(bpy.types.Operator): ifc_csv.attributes = [a.name for a in bpy.context.scene.BIMProperties.csv_attributes] ifc_csv.selector = selector ifc_csv.export(ifc_file, results) - return {'FINISHED'} + return {"FINISHED"} class ImportIfcCsv(bpy.types.Operator): - bl_idname = 'bim.import_ifccsv' - bl_label = 'Import CSV to IFC' - filename_ext = '.csv' - filepath: bpy.props.StringProperty(subtype='FILE_PATH') + bl_idname = "bim.import_ifccsv" + bl_label = "Import CSV to IFC" + filename_ext = ".csv" + filepath: bpy.props.StringProperty(subtype="FILE_PATH") def invoke(self, context, event): - self.filepath = bpy.path.ensure_ext(bpy.data.filepath, '.csv') + self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".csv") WindowManager = context.window_manager WindowManager.fileselect_add(self) - return {'RUNNING_MODAL'} + return {"RUNNING_MODAL"} def execute(self, context): import ifccsv + ifc_csv = ifccsv.IfcCsv() ifc_csv.output = self.filepath ifc_csv.Import(bpy.context.scene.BIMProperties.ifc_file) - return {'FINISHED'} + return {"FINISHED"} class EyedropIfcCsv(bpy.types.Operator): - bl_idname = 'bim.eyedrop_ifccsv' - bl_label = 'Query Selected Items' + bl_idname = "bim.eyedrop_ifccsv" + bl_label = "Query Selected Items" def execute(self, context): global_ids = [] for obj in bpy.context.selected_objects: - if hasattr(obj, 'BIMObjectProperties') \ - and obj.BIMObjectProperties.attributes.get('GlobalId'): - global_ids.append('#' + obj.BIMObjectProperties.attributes.get('GlobalId').string_value) - bpy.context.scene.BIMProperties.ifc_selector = '|'.join(global_ids) - return {'FINISHED'} + if hasattr(obj, "BIMObjectProperties") and obj.BIMObjectProperties.attributes.get("GlobalId"): + global_ids.append("#" + obj.BIMObjectProperties.attributes.get("GlobalId").string_value) + bpy.context.scene.BIMProperties.ifc_selector = "|".join(global_ids) + return {"FINISHED"} class ReloadIfcFile(bpy.types.Operator): - bl_idname = 'bim.reload_ifc_file' - bl_label = 'Reload IFC File' + bl_idname = "bim.reload_ifc_file" + bl_label = "Reload IFC File" def execute(self, context): self.diff_ifc() self.reimport_ifc(context) - return {'FINISHED'} + return {"FINISHED"} def diff_ifc(self): import ifcdiff - temp_file = tempfile.NamedTemporaryFile(delete = False) + + temp_file = tempfile.NamedTemporaryFile(delete=False) temp_file.close() ifc_diff = ifcdiff.IfcDiff( - bpy.context.scene.BIMProperties.ifc_cache, - bpy.context.scene.BIMProperties.ifc_file, - temp_file.name + bpy.context.scene.BIMProperties.ifc_cache, bpy.context.scene.BIMProperties.ifc_file, temp_file.name ) ifc_diff.diff() ifc_diff.export() bpy.context.scene.BIMProperties.diff_json_file = temp_file.name def reimport_ifc(self, context): - logger = logging.getLogger('ImportIFC') + logger = logging.getLogger("ImportIFC") logging.basicConfig( - filename=bpy.context.scene.BIMProperties.data_dir + 'process.log', - filemode='a', level=logging.DEBUG) + filename=bpy.context.scene.BIMProperties.data_dir + "process.log", filemode="a", level=logging.DEBUG + ) ifc_import_settings = import_ifc.IfcImportSettings.factory( - context, bpy.context.scene.BIMProperties.ifc_file, logger) + context, bpy.context.scene.BIMProperties.ifc_file, logger + ) ifc_importer = import_ifc.IfcImporter(ifc_import_settings) ifc_importer.execute() class SelectSimilarType(bpy.types.Operator): - bl_idname = 'bim.select_similar_type' - bl_label = 'Select Similar Type' + bl_idname = "bim.select_similar_type" + bl_label = "Select Similar Type" def execute(self, context): if context.active_object.BIMObjectProperties.relating_type: relating_type = context.active_object.BIMObjectProperties.relating_type - elif 'Type/' in context.active_object.name: + elif "Type/" in context.active_object.name: relating_type = context.active_object else: - return {'FINISHED'} + return {"FINISHED"} for obj in bpy.context.visible_objects: if obj.BIMObjectProperties.relating_type == relating_type: obj.select_set(True) - return {'FINISHED'} + return {"FINISHED"} class AddIfcFile(bpy.types.Operator): - bl_idname = 'bim.add_ifc_file' - bl_label = 'Add IFC File' + bl_idname = "bim.add_ifc_file" + bl_label = "Add IFC File" def execute(self, context): bpy.context.scene.DocProperties.ifc_files.add() - return {'FINISHED'} + return {"FINISHED"} class RemoveIfcFile(bpy.types.Operator): - bl_idname = 'bim.remove_ifc_file' - bl_label = 'Remove IFC File' + bl_idname = "bim.remove_ifc_file" + bl_label = "Remove IFC File" index: bpy.props.IntProperty() def execute(self, context): bpy.context.scene.DocProperties.ifc_files.remove(self.index) - return {'FINISHED'} + return {"FINISHED"} class SelectDocIfcFile(bpy.types.Operator): @@ -3175,23 +3263,23 @@ class SelectDocIfcFile(bpy.types.Operator): def execute(self, context): bpy.context.scene.DocProperties.ifc_files[self.index].name = self.filepath - return {'FINISHED'} + return {"FINISHED"} def invoke(self, context, event): context.window_manager.fileselect_add(self) - return {'RUNNING_MODAL'} + return {"RUNNING_MODAL"} class AddAnnotation(bpy.types.Operator): - bl_idname = 'bim.add_annotation' - bl_label = 'Add Annotation' + bl_idname = "bim.add_annotation" + bl_label = "Add Annotation" obj_name = bpy.props.StringProperty() data_type = bpy.props.StringProperty() def execute(self, context): if not bpy.context.scene.camera: - return {'FINISHED'} - if self.data_type == 'text': + return {"FINISHED"} + if self.data_type == "text": if bpy.context.selected_objects: for selected_object in bpy.context.selected_objects: obj = annotation.Annotator.add_text(related_element=selected_object) @@ -3200,36 +3288,36 @@ class AddAnnotation(bpy.types.Operator): else: obj = annotation.Annotator.get_annotation_obj(self.obj_name, self.data_type) obj = annotation.Annotator.add_line_to_annotation(obj) - bpy.ops.object.select_all(action='DESELECT') + bpy.ops.object.select_all(action="DESELECT") bpy.context.view_layer.objects.active = obj - bpy.ops.object.mode_set(mode='EDIT') - return {'FINISHED'} + bpy.ops.object.mode_set(mode="EDIT") + return {"FINISHED"} class GenerateReferences(bpy.types.Operator): - bl_idname = 'bim.generate_references' - bl_label = 'Generate References' + bl_idname = "bim.generate_references" + bl_label = "Generate References" def execute(self, context): self.camera = bpy.context.scene.camera self.filter_potential_references() - if self.camera.data.BIMCameraProperties.target_view == 'PLAN_VIEW': + if self.camera.data.BIMCameraProperties.target_view == "PLAN_VIEW": self.generate_grids() - if self.camera.data.BIMCameraProperties.target_view == 'ELEVATION_VIEW': + if self.camera.data.BIMCameraProperties.target_view == "ELEVATION_VIEW": self.generate_grids() self.generate_levels() - if self.camera.data.BIMCameraProperties.target_view == 'SECTION_VIEW': + if self.camera.data.BIMCameraProperties.target_view == "SECTION_VIEW": self.generate_grids() self.generate_levels() - return {'FINISHED'} + return {"FINISHED"} def filter_potential_references(self): - for name in ['grids', 'levels']: + for name in ["grids", "levels"]: setattr(self, name, []) for obj in bpy.data.objects: - if 'IfcGridAxis/' in obj.name: + if "IfcGridAxis/" in obj.name: self.grids.append(obj) - if 'IfcBuildingStorey/' in obj.name: + if "IfcBuildingStorey/" in obj.name: self.levels.append(obj) def generate_grids(self): @@ -3239,18 +3327,22 @@ class GenerateReferences(bpy.types.Operator): def generate_levels(self): if self.camera.data.BIMCameraProperties.raster_x > self.camera.data.BIMCameraProperties.raster_y: width = self.camera.data.ortho_scale - height = width / self.camera.data.BIMCameraProperties.raster_x * self.camera.data.BIMCameraProperties.raster_y + height = ( + width / self.camera.data.BIMCameraProperties.raster_x * self.camera.data.BIMCameraProperties.raster_y + ) else: height = self.camera.data.ortho_scale - width = height / self.camera.data.BIMCameraProperties.raster_y * self.camera.data.BIMCameraProperties.raster_x - level_obj = annotation.Annotator.get_annotation_obj('Section Level', 'curve') + width = ( + height / self.camera.data.BIMCameraProperties.raster_y * self.camera.data.BIMCameraProperties.raster_x + ) + level_obj = annotation.Annotator.get_annotation_obj("Section Level", "curve") width_in_mm = width * 1000 - if self.camera.data.BIMCameraProperties.diagram_scale == 'CUSTOM': - human_scale, fraction = self.camera.data.BIMCameraProperties.custom_diagram_scale.split('|') + if self.camera.data.BIMCameraProperties.diagram_scale == "CUSTOM": + human_scale, fraction = self.camera.data.BIMCameraProperties.custom_diagram_scale.split("|") else: - human_scale, fraction = self.camera.data.BIMCameraProperties.diagram_scale.split('|') - numerator, denominator = fraction.split('/') + human_scale, fraction = self.camera.data.BIMCameraProperties.diagram_scale.split("|") + numerator, denominator = fraction.split("/") scale = float(numerator) / float(denominator) real_world_width_in_mm = width_in_mm * scale offset_in_mm = 20 @@ -3258,52 +3350,50 @@ class GenerateReferences(bpy.types.Operator): for obj in self.levels: projection = self.project_point_onto_camera(obj.location) - co1 = self.camera.matrix_world @ Vector((width/2-(offset_percentage * width), projection[1], -1)) - co2 = self.camera.matrix_world @ Vector((-(width/2), projection[1], -1)) + co1 = self.camera.matrix_world @ Vector((width / 2 - (offset_percentage * width), projection[1], -1)) + co2 = self.camera.matrix_world @ Vector((-(width / 2), projection[1], -1)) annotation.Annotator.add_line_to_annotation(level_obj, co1, co2) def project_point_onto_camera(self, point): projection = self.camera.matrix_world.to_quaternion() @ Vector((0, 0, -1)) return self.camera.matrix_world.inverted() @ geometry.intersect_line_plane( - point.xyz, - point.xyz-projection, - self.camera.location, - projection) + point.xyz, point.xyz - projection, self.camera.location, projection + ) class ResizeText(bpy.types.Operator): - bl_idname = 'bim.resize_text' - bl_label = 'Resize Text' + bl_idname = "bim.resize_text" + bl_label = "Resize Text" def execute(self, context): for obj in bpy.context.scene.camera.users_collection[0].objects: if isinstance(obj.data, bpy.types.TextCurve): annotation.Annotator.resize_text(obj) - return {'FINISHED'} + return {"FINISHED"} class AddVariable(bpy.types.Operator): - bl_idname = 'bim.add_variable' - bl_label = 'Add Variable' + bl_idname = "bim.add_variable" + bl_label = "Add Variable" def execute(self, context): bpy.context.active_object.data.BIMTextProperties.variables.add() - return {'FINISHED'} + return {"FINISHED"} class RemoveVariable(bpy.types.Operator): - bl_idname = 'bim.remove_variable' - bl_label = 'Remove Variable' + bl_idname = "bim.remove_variable" + bl_label = "Remove Variable" index: bpy.props.IntProperty() def execute(self, context): bpy.context.active_object.data.BIMTextProperties.variables.remove(self.index) - return {'FINISHED'} + return {"FINISHED"} class PropagateTextData(bpy.types.Operator): - bl_idname = 'bim.propagate_text_data' - bl_label = 'Propagate Text Data' + bl_idname = "bim.propagate_text_data" + bl_label = "Propagate Text Data" def execute(self, context): source = bpy.context.active_object @@ -3321,19 +3411,19 @@ class PropagateTextData(bpy.types.Operator): new_variable = obj.data.BIMTextProperties.variables.add() new_variable.name = variable.name new_variable.prop_key = variable.prop_key - return {'FINISHED'} + return {"FINISHED"} class PushRepresentation(bpy.types.Operator): - bl_idname = 'bim.push_representation' - bl_label = 'Push Representation' + bl_idname = "bim.push_representation" + bl_label = "Push Representation" # Warning: This is an incredibly experimental operator. def execute(self, context): self.file = ifc.IfcStore.get_file() - logger = logging.getLogger('ExportIFC') - output_file = 'tmp.ifc' + logger = logging.getLogger("ExportIFC") + output_file = "tmp.ifc" ifc_export_settings = export_ifc.IfcExportSettings.factory(context, output_file, logger) qto_calculator = qto.QtoCalculator() ifc_parser = export_ifc.IfcParser(ifc_export_settings, qto_calculator) @@ -3344,7 +3434,7 @@ class PushRepresentation(bpy.types.Operator): self.ifc_exporter.create_rep_context() self.ifc_exporter.create_representations() - self.context, self.subcontext, self.target_view, self.mesh_name = bpy.context.active_object.data.name.split('/') + self.context, self.subcontext, self.target_view, self.mesh_name = bpy.context.active_object.data.name.split("/") rep_context = self.get_geometric_representation_context() for key, rep in self.ifc_exporter.ifc_parser.representations.items(): @@ -3352,22 +3442,24 @@ class PushRepresentation(bpy.types.Operator): continue if rep_context: self.ifc_exporter.file.add(rep_context) - rep['ifc'].MappedRepresentation.ContextOfItems = rep_context - self.push_representation(rep['ifc']) + rep["ifc"].MappedRepresentation.ContextOfItems = rep_context + self.push_representation(rep["ifc"]) break - self.file.write(bpy.context.scene.BIMProperties.ifc_file[0:-4] + '-patch.ifc') - return {'FINISHED'} + self.file.write(bpy.context.scene.BIMProperties.ifc_file[0:-4] + "-patch.ifc") + return {"FINISHED"} def get_geometric_representation_context(self): - for element in self.file.by_type('IfcGeometricRepresentationSubContext'): + for element in self.file.by_type("IfcGeometricRepresentationSubContext"): if self.is_current_context(element): return element def push_representation(self, new_representation): - element = self.file.by_guid(bpy.context.active_object.BIMObjectProperties.attributes.get('GlobalId').string_value) + element = self.file.by_guid( + bpy.context.active_object.BIMObjectProperties.attributes.get("GlobalId").string_value + ) old_shape = None new_shape = self.file.add(new_representation.MappedRepresentation) - if element.is_a('IfcProduct'): + if element.is_a("IfcProduct"): representations = element.Representation.Representations else: representations = [rm.MappedRepresentation for rm in element.RepresentationMaps] @@ -3381,7 +3473,7 @@ class PushRepresentation(bpy.types.Operator): self.add_new_representation(element, new_shape) def resolve_mapped_representation(self, representation): - if representation.RepresentationType == 'MappedRepresentation': + if representation.RepresentationType == "MappedRepresentation": if representation.Items: return representation.Items[0].MappingSource.MappedRepresentation return representation @@ -3390,8 +3482,7 @@ class PushRepresentation(bpy.types.Operator): inverse_elements = self.file.get_inverse(old) for element in inverse_elements: for i, attribute in enumerate(element): - if (isinstance(attribute, list) or isinstance(attribute, tuple)) \ - and old in attribute: + if (isinstance(attribute, list) or isinstance(attribute, tuple)) and old in attribute: items = list(attribute) for j, item in enumerate(items): if item == old: @@ -3402,7 +3493,7 @@ class PushRepresentation(bpy.types.Operator): element[i] = new def add_new_representation(self, element, new): - if element.is_a('IfcProduct'): + if element.is_a("IfcProduct"): self.add_new_representation_to_product(element, new) return @@ -3411,11 +3502,11 @@ class PushRepresentation(bpy.types.Operator): representation_maps.append(self.file.createIfcRepresentationMap(self.ifc_exporter.origin, new)) element.RepresentationMaps = representation_maps else: - element.RepresentationMaps = (self.file.createIfcRepresentationMap(self.ifc_exporter.origin, new)) + element.RepresentationMaps = self.file.createIfcRepresentationMap(self.ifc_exporter.origin, new) - if hasattr(element, 'Types'): + if hasattr(element, "Types"): related_objects = element.Types[0].RelatedObjects - elif hasattr(element, 'ObjectTypeOf'): # IFC2X3 + elif hasattr(element, "ObjectTypeOf"): # IFC2X3 related_objects = element.ObjectTypeOf[0].RelatedObjects for related_object in related_objects: @@ -3427,14 +3518,16 @@ class PushRepresentation(bpy.types.Operator): element.Representation.Representations = representations def is_current_context(self, element): - return element.ContextType == self.context \ - and element.ContextIdentifier == self.subcontext \ + return ( + element.ContextType == self.context + and element.ContextIdentifier == self.subcontext and element.TargetView == self.target_view + ) class ConvertLocalToGlobal(bpy.types.Operator): - bl_idname = 'bim.convert_local_to_global' - bl_label = 'Convert Local To Global' + bl_idname = "bim.convert_local_to_global" + bl_label = "Convert Local To Global" def execute(self, context): x, y, z = bpy.context.scene.cursor.location @@ -3442,11 +3535,12 @@ class ConvertLocalToGlobal(bpy.types.Operator): if bpy.context.scene.MapConversion.scale: scale = float(bpy.context.scene.MapConversion.scale) else: - scale = 1. + scale = 1.0 rotation = atan2( float(bpy.context.scene.MapConversion.x_axis_ordinate), - float(bpy.context.scene.MapConversion.x_axis_abscissa)) + float(bpy.context.scene.MapConversion.x_axis_abscissa), + ) a = scale * cos(rotation) b = scale * sin(rotation) @@ -3455,12 +3549,12 @@ class ConvertLocalToGlobal(bpy.types.Operator): height = z + float(bpy.context.scene.MapConversion.orthogonal_height) bpy.context.scene.cursor.location = (eastings, northings, height) - return {'FINISHED'} + return {"FINISHED"} class GuessQuantity(bpy.types.Operator): - bl_idname = 'bim.guess_quantity' - bl_label = 'Guess Quantity' + bl_idname = "bim.guess_quantity" + bl_label = "Guess Quantity" qto_index: bpy.props.IntProperty() prop_index: bpy.props.IntProperty() @@ -3477,22 +3571,21 @@ class GuessQuantity(bpy.types.Operator): dest_qto = self.add_qto(obj, source_qto.name) prop = dest_qto.properties.get(prop.name) self.guess_quantity(obj, prop, props) - return {'FINISHED'} + return {"FINISHED"} def guess_quantity(self, obj, prop, props): - quantity = self.qto_calculator.guess_quantity( - prop.name, [p.name for p in props], obj) - if 'area' in prop.name.lower(): + quantity = self.qto_calculator.guess_quantity(prop.name, [p.name for p in props], obj) + if "area" in prop.name.lower(): if bpy.context.scene.BIMProperties.area_unit: prefix, name = self.get_prefix_name(bpy.context.scene.BIMProperties.area_unit) - quantity = helper.SIUnitHelper.convert(quantity, None, 'SQUARE_METRE', prefix, name) - elif 'volume' in prop.name.lower(): + quantity = helper.SIUnitHelper.convert(quantity, None, "SQUARE_METRE", prefix, name) + elif "volume" in prop.name.lower(): if bpy.context.scene.BIMProperties.volume_unit: prefix, name = self.get_prefix_name(bpy.context.scene.BIMProperties.volume_unit) - quantity = helper.SIUnitHelper.convert(quantity, None, 'CUBIC_METRE', prefix, name) + quantity = helper.SIUnitHelper.convert(quantity, None, "CUBIC_METRE", prefix, name) else: prefix, name = self.get_blender_prefix_name() - quantity = helper.SIUnitHelper.convert(quantity, None, 'METRE', prefix, name) + quantity = helper.SIUnitHelper.convert(quantity, None, "METRE", prefix, name) prop.string_value = str(round(quantity, 3)) def add_qto(self, obj, name): @@ -3500,97 +3593,104 @@ class GuessQuantity(bpy.types.Operator): return qto = obj.BIMObjectProperties.qtos.add() qto.name = name - for prop_name in schema.ifc.qtos[name]['HasPropertyTemplates'].keys(): + for prop_name in schema.ifc.qtos[name]["HasPropertyTemplates"].keys(): prop = qto.properties.add() prop.name = prop_name return qto def get_prefix_name(self, value): - if '/' in value: - return value.split('/') + if "/" in value: + return value.split("/") return None, value def get_blender_prefix_name(self): - if bpy.context.scene.unit_settings.system == 'IMPERIAL': - if bpy.context.scene.unit_settings.length_unit == 'INCHES': - return None, 'inch' - elif bpy.context.scene.unit_settings.length_unit == 'FEET': - return None, 'foot' - elif bpy.context.scene.unit_settings.system == 'METRIC': - if bpy.context.scene.unit_settings.length_unit == 'METERS': - return None, 'METRE' - return bpy.context.scene.unit_settings.length_unit[0:-len('METERS')], 'METRE' + if bpy.context.scene.unit_settings.system == "IMPERIAL": + if bpy.context.scene.unit_settings.length_unit == "INCHES": + return None, "inch" + elif bpy.context.scene.unit_settings.length_unit == "FEET": + return None, "foot" + elif bpy.context.scene.unit_settings.system == "METRIC": + if bpy.context.scene.unit_settings.length_unit == "METERS": + return None, "METRE" + return bpy.context.scene.unit_settings.length_unit[0 : -len("METERS")], "METRE" class ExecuteBIMTester(bpy.types.Operator): - bl_idname = 'bim.execute_bim_tester' - bl_label = 'Execute BIMTester' + bl_idname = "bim.execute_bim_tester" + bl_label = "Execute BIMTester" def execute(self, context): import bimtester + filename = os.path.join( - bpy.context.scene.BIMProperties.features_dir, - bpy.context.scene.BIMProperties.features_file + '.feature') + bpy.context.scene.BIMProperties.features_dir, bpy.context.scene.BIMProperties.features_file + ".feature" + ) cwd = os.getcwd() os.chdir(bpy.context.scene.BIMProperties.features_dir) - bimtester.run_tests({'feature': filename, 'advanced_arguments': None, 'console': False}) + bimtester.run_tests({"feature": filename, "advanced_arguments": None, "console": False}) bimtester.generate_report() - webbrowser.open('file://' + os.path.join( - bpy.context.scene.BIMProperties.features_dir, - 'report', bpy.context.scene.BIMProperties.features_file + '.feature.html')) + webbrowser.open( + "file://" + + os.path.join( + bpy.context.scene.BIMProperties.features_dir, + "report", + bpy.context.scene.BIMProperties.features_file + ".feature.html", + ) + ) os.chdir(cwd) - return {'FINISHED'} + return {"FINISHED"} class BIMTesterPurge(bpy.types.Operator): - bl_idname = 'bim.bim_tester_purge' - bl_label = 'Purge Tests' + bl_idname = "bim.bim_tester_purge" + bl_label = "Purge Tests" def execute(self, context): import bimtester + filename = os.path.join( - bpy.context.scene.BIMProperties.features_dir, - bpy.context.scene.BIMProperties.features_file + '.feature') + bpy.context.scene.BIMProperties.features_dir, bpy.context.scene.BIMProperties.features_file + ".feature" + ) cwd = os.getcwd() os.chdir(bpy.context.scene.BIMProperties.features_dir) bimtester.TestPurger().purge() os.chdir(cwd) - return {'FINISHED'} + return {"FINISHED"} class SelectIfcPatchInput(bpy.types.Operator): bl_idname = "bim.select_ifc_patch_input" bl_label = "Select IFC Patch Input" - filter_glob: bpy.props.StringProperty(default="*.ifc", options={'HIDDEN'}) + filter_glob: bpy.props.StringProperty(default="*.ifc", options={"HIDDEN"}) filepath: bpy.props.StringProperty(subtype="FILE_PATH") def execute(self, context): bpy.context.scene.BIMProperties.ifc_patch_input = self.filepath - return {'FINISHED'} + return {"FINISHED"} def invoke(self, context, event): context.window_manager.fileselect_add(self) - return {'RUNNING_MODAL'} + return {"RUNNING_MODAL"} class SelectIfcPatchOutput(bpy.types.Operator): bl_idname = "bim.select_ifc_patch_output" bl_label = "Select IFC Patch Output" - filename_ext = '.ifc' + filename_ext = ".ifc" filepath: bpy.props.StringProperty(subtype="FILE_PATH") def execute(self, context): bpy.context.scene.BIMProperties.ifc_patch_output = self.filepath - return {'FINISHED'} + return {"FINISHED"} def invoke(self, context, event): context.window_manager.fileselect_add(self) - return {'RUNNING_MODAL'} + return {"RUNNING_MODAL"} class CalculateEdgeLengths(bpy.types.Operator): - bl_idname = 'bim.calculate_edge_lengths' - bl_label = 'Calculate Edge Lengths' + bl_idname = "bim.calculate_edge_lengths" + bl_label = "Calculate Edge Lengths" def execute(self, context): result = 0 @@ -3599,15 +3699,14 @@ class CalculateEdgeLengths(bpy.types.Operator): 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 += (obj.data.vertices[edge.vertices[1]].co - obj.data.vertices[edge.vertices[0]].co).length bpy.context.scene.BIMProperties.qto_result = str(round(result, 3)) - return {'FINISHED'} + return {"FINISHED"} class CalculateFaceAreas(bpy.types.Operator): - bl_idname = 'bim.calculate_face_areas' - bl_label = 'Calculate Face Areas' + bl_idname = "bim.calculate_face_areas" + bl_label = "Calculate Face Areas" def execute(self, context): result = 0 @@ -3618,12 +3717,12 @@ class CalculateFaceAreas(bpy.types.Operator): if polygon.select: result += polygon.area bpy.context.scene.BIMProperties.qto_result = str(round(result, 3)) - return {'FINISHED'} + return {"FINISHED"} class CalculateObjectVolumes(bpy.types.Operator): - bl_idname = 'bim.calculate_object_volumes' - bl_label = 'Calculate Object Volumes' + bl_idname = "bim.calculate_object_volumes" + bl_label = "Calculate Object Volumes" def execute(self, context): qto_calculator = qto.QtoCalculator() @@ -3633,15 +3732,15 @@ class CalculateObjectVolumes(bpy.types.Operator): continue result += qto_calculator.get_volume(obj) bpy.context.scene.BIMProperties.qto_result = str(round(result, 3)) - return {'FINISHED'} + return {"FINISHED"} class AddOpening(bpy.types.Operator): - bl_idname = 'bim.add_opening' - bl_label = 'Add Opening' + bl_idname = "bim.add_opening" + bl_label = "Add Opening" def execute(self, context): - if context.active_object.children and 'IfcOpeningElement/' in context.active_object.children[0].name: + if context.active_object.children and "IfcOpeningElement/" in context.active_object.children[0].name: opening = context.active_object.children[0] else: opening = context.active_object @@ -3649,135 +3748,145 @@ class AddOpening(bpy.types.Operator): obj = context.selected_objects[0] else: obj = context.selected_objects[1] - modifier = obj.modifiers.new('IfcOpeningElement', 'BOOLEAN') - modifier.operation = 'DIFFERENCE' + modifier = obj.modifiers.new("IfcOpeningElement", "BOOLEAN") + modifier.operation = "DIFFERENCE" modifier.object = opening - return {'FINISHED'} + return {"FINISHED"} class SetOverrideColour(bpy.types.Operator): - bl_idname = 'bim.set_override_colour' - bl_label = 'Set Override Colour' + bl_idname = "bim.set_override_colour" + bl_label = "Set Override Colour" def execute(self, context): result = 0 for obj in bpy.context.selected_objects: obj.color = bpy.context.scene.BIMProperties.override_colour - area = next(area for area in bpy.context.screen.areas if area.type == 'VIEW_3D') - area.spaces[0].shading.color_type = 'OBJECT' - return {'FINISHED'} + area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") + area.spaces[0].shading.color_type = "OBJECT" + return {"FINISHED"} class AddDrawingStyle(bpy.types.Operator): - bl_idname = 'bim.add_drawing_style' - bl_label = 'Add Drawing Style' + bl_idname = "bim.add_drawing_style" + bl_label = "Add Drawing Style" def execute(self, context): new = bpy.context.scene.DocProperties.drawing_styles.add() - new.name = 'New Drawing Style' - return {'FINISHED'} + new.name = "New Drawing Style" + return {"FINISHED"} class RemoveDrawingStyle(bpy.types.Operator): - bl_idname = 'bim.remove_drawing_style' - bl_label = 'Remove Drawing Style' + bl_idname = "bim.remove_drawing_style" + bl_label = "Remove Drawing Style" index: bpy.props.IntProperty() def execute(self, context): bpy.context.scene.DocProperties.drawing_styles.remove(self.index) - return {'FINISHED'} + return {"FINISHED"} class SaveDrawingStyle(bpy.types.Operator): - bl_idname = 'bim.save_drawing_style' - bl_label = 'Save Drawing Style' + bl_idname = "bim.save_drawing_style" + bl_label = "Save Drawing Style" index: bpy.props.StringProperty() def execute(self, context): space = self.get_view_3d() style = { - 'bpy.data.worlds[0].color': tuple(bpy.data.worlds[0].color), - 'bpy.context.scene.render.engine': bpy.context.scene.render.engine, - 'bpy.context.scene.render.film_transparent': bpy.context.scene.render.film_transparent, - 'bpy.context.scene.display.shading.show_object_outline': bpy.context.scene.display.shading.show_object_outline, - 'bpy.context.scene.display.shading.show_cavity': bpy.context.scene.display.shading.show_cavity, - 'bpy.context.scene.display.shading.cavity_type': bpy.context.scene.display.shading.cavity_type, - 'bpy.context.scene.display.shading.curvature_ridge_factor': bpy.context.scene.display.shading.curvature_ridge_factor, - 'bpy.context.scene.display.shading.curvature_valley_factor': bpy.context.scene.display.shading.curvature_valley_factor, - 'bpy.context.scene.view_settings.view_transform': bpy.context.scene.view_settings.view_transform, - 'bpy.context.scene.display.shading.light': bpy.context.scene.display.shading.light, - 'bpy.context.scene.display.shading.color_type': bpy.context.scene.display.shading.color_type, - 'bpy.context.scene.display.shading.single_color': tuple(bpy.context.scene.display.shading.single_color), - 'bpy.context.scene.display.shading.show_shadows': bpy.context.scene.display.shading.show_shadows, - 'bpy.context.scene.display.shading.shadow_intensity': bpy.context.scene.display.shading.shadow_intensity, - 'bpy.context.scene.display.light_direction': tuple(bpy.context.scene.display.light_direction), - 'bpy.context.scene.view_settings.use_curve_mapping': bpy.context.scene.view_settings.use_curve_mapping, - 'space.overlay.show_wireframes': space.overlay.show_wireframes, - 'space.overlay.wireframe_threshold': space.overlay.wireframe_threshold, - 'space.overlay.show_floor': space.overlay.show_floor, - 'space.overlay.show_axis_x': space.overlay.show_axis_x, - 'space.overlay.show_axis_y': space.overlay.show_axis_y, - 'space.overlay.show_axis_z': space.overlay.show_axis_z, - 'space.overlay.show_object_origins': space.overlay.show_object_origins, - 'space.overlay.show_relationship_lines': space.overlay.show_relationship_lines, + "bpy.data.worlds[0].color": tuple(bpy.data.worlds[0].color), + "bpy.context.scene.render.engine": bpy.context.scene.render.engine, + "bpy.context.scene.render.film_transparent": bpy.context.scene.render.film_transparent, + "bpy.context.scene.display.shading.show_object_outline": bpy.context.scene.display.shading.show_object_outline, + "bpy.context.scene.display.shading.show_cavity": bpy.context.scene.display.shading.show_cavity, + "bpy.context.scene.display.shading.cavity_type": bpy.context.scene.display.shading.cavity_type, + "bpy.context.scene.display.shading.curvature_ridge_factor": bpy.context.scene.display.shading.curvature_ridge_factor, + "bpy.context.scene.display.shading.curvature_valley_factor": bpy.context.scene.display.shading.curvature_valley_factor, + "bpy.context.scene.view_settings.view_transform": bpy.context.scene.view_settings.view_transform, + "bpy.context.scene.display.shading.light": bpy.context.scene.display.shading.light, + "bpy.context.scene.display.shading.color_type": bpy.context.scene.display.shading.color_type, + "bpy.context.scene.display.shading.single_color": tuple(bpy.context.scene.display.shading.single_color), + "bpy.context.scene.display.shading.show_shadows": bpy.context.scene.display.shading.show_shadows, + "bpy.context.scene.display.shading.shadow_intensity": bpy.context.scene.display.shading.shadow_intensity, + "bpy.context.scene.display.light_direction": tuple(bpy.context.scene.display.light_direction), + "bpy.context.scene.view_settings.use_curve_mapping": bpy.context.scene.view_settings.use_curve_mapping, + "space.overlay.show_wireframes": space.overlay.show_wireframes, + "space.overlay.wireframe_threshold": space.overlay.wireframe_threshold, + "space.overlay.show_floor": space.overlay.show_floor, + "space.overlay.show_axis_x": space.overlay.show_axis_x, + "space.overlay.show_axis_y": space.overlay.show_axis_y, + "space.overlay.show_axis_z": space.overlay.show_axis_z, + "space.overlay.show_object_origins": space.overlay.show_object_origins, + "space.overlay.show_relationship_lines": space.overlay.show_relationship_lines, } if self.index: index = int(self.index) else: index = bpy.context.active_object.data.BIMCameraProperties.active_drawing_style_index bpy.context.scene.DocProperties.drawing_styles[index].raster_style = json.dumps(style) - return {'FINISHED'} + return {"FINISHED"} def get_view_3d(self): for area in bpy.context.screen.areas: - if area.type != 'VIEW_3D': + if area.type != "VIEW_3D": continue for space in area.spaces: - if space.type != 'VIEW_3D': + if space.type != "VIEW_3D": continue return space class ActivateDrawingStyle(bpy.types.Operator): - bl_idname = 'bim.activate_drawing_style' - bl_label = 'Activate Drawing Style' + bl_idname = "bim.activate_drawing_style" + bl_label = "Activate Drawing Style" def execute(self, context): - if context.scene.camera.data.BIMCameraProperties.active_drawing_style_index < len(bpy.context.scene.DocProperties.drawing_styles): - self.drawing_style = bpy.context.scene.DocProperties.drawing_styles[context.scene.camera.data.BIMCameraProperties.active_drawing_style_index] + if context.scene.camera.data.BIMCameraProperties.active_drawing_style_index < len( + bpy.context.scene.DocProperties.drawing_styles + ): + self.drawing_style = bpy.context.scene.DocProperties.drawing_styles[ + context.scene.camera.data.BIMCameraProperties.active_drawing_style_index + ] self.set_raster_style() self.set_query() - return {'FINISHED'} + return {"FINISHED"} def set_raster_style(self): space = self.get_view_3d() style = json.loads(self.drawing_style.raster_style) - bpy.data.worlds[0].color = style['bpy.data.worlds[0].color'] - bpy.context.scene.render.engine = style['bpy.context.scene.render.engine'] - bpy.context.scene.render.film_transparent = style['bpy.context.scene.render.film_transparent'] - bpy.context.scene.display.shading.show_object_outline = style['bpy.context.scene.display.shading.show_object_outline'] - bpy.context.scene.display.shading.show_cavity = style['bpy.context.scene.display.shading.show_cavity'] - bpy.context.scene.display.shading.cavity_type = style['bpy.context.scene.display.shading.cavity_type'] - bpy.context.scene.display.shading.curvature_ridge_factor = style['bpy.context.scene.display.shading.curvature_ridge_factor'] - bpy.context.scene.display.shading.curvature_valley_factor = style['bpy.context.scene.display.shading.curvature_valley_factor'] - bpy.context.scene.view_settings.view_transform = style['bpy.context.scene.view_settings.view_transform'] - bpy.context.scene.display.shading.light = style['bpy.context.scene.display.shading.light'] - bpy.context.scene.display.shading.color_type = style['bpy.context.scene.display.shading.color_type'] - bpy.context.scene.display.shading.single_color = style['bpy.context.scene.display.shading.single_color'] - bpy.context.scene.display.shading.show_shadows = style['bpy.context.scene.display.shading.show_shadows'] - bpy.context.scene.display.shading.shadow_intensity = style['bpy.context.scene.display.shading.shadow_intensity'] - bpy.context.scene.display.light_direction = style['bpy.context.scene.display.light_direction'] + bpy.data.worlds[0].color = style["bpy.data.worlds[0].color"] + bpy.context.scene.render.engine = style["bpy.context.scene.render.engine"] + bpy.context.scene.render.film_transparent = style["bpy.context.scene.render.film_transparent"] + bpy.context.scene.display.shading.show_object_outline = style[ + "bpy.context.scene.display.shading.show_object_outline" + ] + bpy.context.scene.display.shading.show_cavity = style["bpy.context.scene.display.shading.show_cavity"] + bpy.context.scene.display.shading.cavity_type = style["bpy.context.scene.display.shading.cavity_type"] + bpy.context.scene.display.shading.curvature_ridge_factor = style[ + "bpy.context.scene.display.shading.curvature_ridge_factor" + ] + bpy.context.scene.display.shading.curvature_valley_factor = style[ + "bpy.context.scene.display.shading.curvature_valley_factor" + ] + bpy.context.scene.view_settings.view_transform = style["bpy.context.scene.view_settings.view_transform"] + bpy.context.scene.display.shading.light = style["bpy.context.scene.display.shading.light"] + bpy.context.scene.display.shading.color_type = style["bpy.context.scene.display.shading.color_type"] + bpy.context.scene.display.shading.single_color = style["bpy.context.scene.display.shading.single_color"] + bpy.context.scene.display.shading.show_shadows = style["bpy.context.scene.display.shading.show_shadows"] + bpy.context.scene.display.shading.shadow_intensity = style["bpy.context.scene.display.shading.shadow_intensity"] + bpy.context.scene.display.light_direction = style["bpy.context.scene.display.light_direction"] - bpy.context.scene.view_settings.use_curve_mapping = style['bpy.context.scene.view_settings.use_curve_mapping'] - space.overlay.show_wireframes = style['space.overlay.show_wireframes'] - space.overlay.wireframe_threshold = style['space.overlay.wireframe_threshold'] - space.overlay.show_floor = style['space.overlay.show_floor'] - space.overlay.show_axis_x = style['space.overlay.show_axis_x'] - space.overlay.show_axis_y = style['space.overlay.show_axis_y'] - space.overlay.show_axis_z = style['space.overlay.show_axis_z'] - space.overlay.show_object_origins = style['space.overlay.show_object_origins'] - space.overlay.show_relationship_lines = style['space.overlay.show_relationship_lines'] - space.shading.type = 'RENDERED' + bpy.context.scene.view_settings.use_curve_mapping = style["bpy.context.scene.view_settings.use_curve_mapping"] + space.overlay.show_wireframes = style["space.overlay.show_wireframes"] + space.overlay.wireframe_threshold = style["space.overlay.wireframe_threshold"] + space.overlay.show_floor = style["space.overlay.show_floor"] + space.overlay.show_axis_x = style["space.overlay.show_axis_x"] + space.overlay.show_axis_y = style["space.overlay.show_axis_y"] + space.overlay.show_axis_z = style["space.overlay.show_axis_z"] + space.overlay.show_object_origins = style["space.overlay.show_object_origins"] + space.overlay.show_relationship_lines = style["space.overlay.show_relationship_lines"] + space.shading.type = "RENDERED" def set_query(self): self.selector = ifcopenshell.util.selector.Selector() @@ -3795,76 +3904,74 @@ class ActivateDrawingStyle(bpy.types.Operator): results = self.selector.parse(ifc, self.drawing_style.exclude_query) self.exclude_global_ids.extend([e.GlobalId for e in results]) if self.drawing_style.include_query: - self.parse_filter_query('INCLUDE') + self.parse_filter_query("INCLUDE") else: for obj in bpy.context.scene.objects: obj.hide_viewport = False if self.drawing_style.exclude_query: - self.parse_filter_query('EXCLUDE') + self.parse_filter_query("EXCLUDE") def parse_filter_query(self, mode): - if mode == 'INCLUDE': + if mode == "INCLUDE": objects = bpy.context.scene.objects - elif mode == 'EXCLUDE': + elif mode == "EXCLUDE": objects = bpy.context.visible_objects for obj in objects: - if mode == 'INCLUDE': - obj.hide_viewport = False # Note: this breaks alt-H - global_id = obj.BIMObjectProperties.attributes.get('GlobalId') + if mode == "INCLUDE": + obj.hide_viewport = False # Note: this breaks alt-H + global_id = obj.BIMObjectProperties.attributes.get("GlobalId") if not global_id: continue global_id = global_id.string_value - if mode == 'INCLUDE': + if mode == "INCLUDE": if global_id not in self.include_global_ids: - obj.hide_viewport = True # Note: this breaks alt-H - elif mode == 'EXCLUDE': + obj.hide_viewport = True # Note: this breaks alt-H + elif mode == "EXCLUDE": if global_id in self.exclude_global_ids: - obj.hide_viewport = True # Note: this breaks alt-H - + obj.hide_viewport = True # Note: this breaks alt-H def get_view_3d(self): for area in bpy.context.screen.areas: - if area.type != 'VIEW_3D': + if area.type != "VIEW_3D": continue for space in area.spaces: - if space.type != 'VIEW_3D': + if space.type != "VIEW_3D": continue return space class AddDrawing(bpy.types.Operator): - bl_idname = 'bim.add_drawing' - bl_label = 'Add Drawing' + bl_idname = "bim.add_drawing" + bl_label = "Add Drawing" def execute(self, context): new = bpy.context.scene.DocProperties.drawings.add() - new.name = 'DRAWING {}'.format(len(bpy.context.scene.DocProperties.drawings)) - if not bpy.data.collections.get('Views'): - bpy.context.scene.collection.children.link(bpy.data.collections.new('Views')) - views_collection = bpy.data.collections.get('Views') - view_collection = bpy.data.collections.new('IfcGroup/' + new.name) + new.name = "DRAWING {}".format(len(bpy.context.scene.DocProperties.drawings)) + if not bpy.data.collections.get("Views"): + bpy.context.scene.collection.children.link(bpy.data.collections.new("Views")) + views_collection = bpy.data.collections.get("Views") + view_collection = bpy.data.collections.new("IfcGroup/" + new.name) views_collection.children.link(view_collection) - camera = bpy.data.objects.new('IfcGroup/' + new.name, - bpy.data.cameras.new('IfcGroup/' + new.name)) - camera.location = (0, 0, 1.7) # The view shall be 1.7m above the origin - camera.data.type = 'ORTHO' - camera.data.ortho_scale = 50 # The default of 6m is too small - if bpy.context.scene.unit_settings.system == 'IMPERIAL': + camera = bpy.data.objects.new("IfcGroup/" + new.name, bpy.data.cameras.new("IfcGroup/" + new.name)) + camera.location = (0, 0, 1.7) # The view shall be 1.7m above the origin + camera.data.type = "ORTHO" + camera.data.ortho_scale = 50 # The default of 6m is too small + if bpy.context.scene.unit_settings.system == "IMPERIAL": camera.data.BIMCameraProperties.diagram_scale = '1/8"=1\'-0"|1/96' else: - camera.data.BIMCameraProperties.diagram_scale = '1:100|1/100' + camera.data.BIMCameraProperties.diagram_scale = "1:100|1/100" bpy.context.scene.camera = camera view_collection.objects.link(camera) - area = next(area for area in bpy.context.screen.areas if area.type == 'VIEW_3D') - area.spaces[0].region_3d.view_perspective = 'CAMERA' + area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") + area.spaces[0].region_3d.view_perspective = "CAMERA" new.camera = camera bpy.ops.bim.activate_drawing_style() - return {'FINISHED'} + return {"FINISHED"} class RemoveDrawing(bpy.types.Operator): - bl_idname = 'bim.remove_drawing' - bl_label = 'Remove Drawing' + bl_idname = "bim.remove_drawing" + bl_label = "Remove Drawing" index: bpy.props.IntProperty() def execute(self, context): @@ -3875,187 +3982,188 @@ class RemoveDrawing(bpy.types.Operator): bpy.data.objects.remove(obj) bpy.data.collections.remove(collection, do_unlink=True) props.drawings.remove(self.index) - return {'FINISHED'} + return {"FINISHED"} class EditVectorStyle(bpy.types.Operator): - bl_idname = 'bim.edit_vector_style' - bl_label = 'Edit Vector Style' + bl_idname = "bim.edit_vector_style" + bl_label = "Edit Vector Style" def execute(self, context): camera = context.scene.camera - vector_style = context.scene.DocProperties.drawing_styles[camera.data.BIMCameraProperties.active_drawing_style_index].vector_style - bpy.data.texts.load(os.path.join(context.scene.BIMProperties.data_dir, 'styles', vector_style + '.css')) - return {'FINISHED'} + vector_style = context.scene.DocProperties.drawing_styles[ + camera.data.BIMCameraProperties.active_drawing_style_index + ].vector_style + bpy.data.texts.load(os.path.join(context.scene.BIMProperties.data_dir, "styles", vector_style + ".css")) + return {"FINISHED"} class RemoveSheet(bpy.types.Operator): - bl_idname = 'bim.remove_sheet' - bl_label = 'Remove Sheet' + bl_idname = "bim.remove_sheet" + bl_label = "Remove Sheet" index: bpy.props.IntProperty() def execute(self, context): props = bpy.context.scene.DocProperties props.sheets.remove(self.index) - return {'FINISHED'} + return {"FINISHED"} class AddSchedule(bpy.types.Operator): - bl_idname = 'bim.add_schedule' - bl_label = 'Add Schedule' + bl_idname = "bim.add_schedule" + bl_label = "Add Schedule" def execute(self, context): new = bpy.context.scene.DocProperties.schedules.add() - new.name = 'SCHEDULE {}'.format(len(bpy.context.scene.DocProperties.schedules)) - return {'FINISHED'} + new.name = "SCHEDULE {}".format(len(bpy.context.scene.DocProperties.schedules)) + return {"FINISHED"} class RemoveSchedule(bpy.types.Operator): - bl_idname = 'bim.remove_schedule' - bl_label = 'Remove Schedule' + bl_idname = "bim.remove_schedule" + bl_label = "Remove Schedule" index: bpy.props.IntProperty() def execute(self, context): props = bpy.context.scene.DocProperties props.schedules.remove(self.index) - return {'FINISHED'} + return {"FINISHED"} class SelectScheduleFile(bpy.types.Operator): bl_idname = "bim.select_schedule_file" bl_label = "Select Documentation IFC File" filepath: bpy.props.StringProperty(subtype="FILE_PATH") - filter_glob: bpy.props.StringProperty(default="*.ods", options={'HIDDEN'}) + filter_glob: bpy.props.StringProperty(default="*.ods", options={"HIDDEN"}) index: bpy.props.IntProperty() def execute(self, context): props = bpy.context.scene.DocProperties props.schedules[props.active_schedule_index].file = self.filepath - return {'FINISHED'} + return {"FINISHED"} def invoke(self, context, event): context.window_manager.fileselect_add(self) - return {'RUNNING_MODAL'} + return {"RUNNING_MODAL"} class BuildSchedule(bpy.types.Operator): - bl_idname = 'bim.build_schedule' - bl_label = 'Build Schedule' + bl_idname = "bim.build_schedule" + bl_label = "Build Schedule" def execute(self, context): props = bpy.context.scene.DocProperties schedule = props.schedules[props.active_schedule_index] schedule_creator = scheduler.Scheduler() - outfile = os.path.join( - bpy.context.scene.BIMProperties.data_dir, 'schedules', - schedule.name + '.svg') + outfile = os.path.join(bpy.context.scene.BIMProperties.data_dir, "schedules", schedule.name + ".svg") schedule_creator.schedule(schedule.file, outfile) - open_with_user_command( - bpy.context.preferences.addons['blenderbim'].preferences.svg_command, - outfile) - return {'FINISHED'} + open_with_user_command(bpy.context.preferences.addons["blenderbim"].preferences.svg_command, outfile) + return {"FINISHED"} class AddScheduleToSheet(bpy.types.Operator): - bl_idname = 'bim.add_schedule_to_sheet' - bl_label = 'Add Schedule To Sheet' + bl_idname = "bim.add_schedule_to_sheet" + bl_label = "Add Schedule To Sheet" def execute(self, context): props = bpy.context.scene.DocProperties sheet_builder = sheeter.SheetBuilder() sheet_builder.data_dir = bpy.context.scene.BIMProperties.data_dir sheet_builder.add_schedule( - props.schedules[props.active_schedule_index].name, - props.sheets[props.active_sheet_index].name) - return {'FINISHED'} + props.schedules[props.active_schedule_index].name, props.sheets[props.active_sheet_index].name + ) + return {"FINISHED"} class SetViewportShadowFromSun(bpy.types.Operator): - bl_idname = 'bim.set_viewport_shadow_from_sun' - bl_label = 'Set Viewport Shadow from Sun' + bl_idname = "bim.set_viewport_shadow_from_sun" + bl_label = "Set Viewport Shadow from Sun" 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))) - context.scene.display.light_direction = mat.inverted() @ (context.active_object.matrix_world.to_quaternion() @ Vector((0,0,-1))) - return {'FINISHED'} + 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))) + context.scene.display.light_direction = mat.inverted() @ ( + context.active_object.matrix_world.to_quaternion() @ Vector((0, 0, -1)) + ) + return {"FINISHED"} class SetNorthOffset(bpy.types.Operator): - bl_idname = 'bim.set_north_offset' - bl_label = 'Set North Offset' + bl_idname = "bim.set_north_offset" + bl_label = "Set North Offset" def execute(self, context): - context.scene.sun_pos_properties.north_offset = radians(ifcopenshell.util.geolocation.xy2angle( - float(bpy.context.scene.MapConversion.x_axis_ordinate), - float(bpy.context.scene.MapConversion.x_axis_abscissa))) - return {'FINISHED'} + context.scene.sun_pos_properties.north_offset = radians( + ifcopenshell.util.geolocation.xy2angle( + float(bpy.context.scene.MapConversion.x_axis_ordinate), + float(bpy.context.scene.MapConversion.x_axis_abscissa), + ) + ) + return {"FINISHED"} class GetNorthOffset(bpy.types.Operator): - bl_idname = 'bim.get_north_offset' - bl_label = 'Get North Offset' + bl_idname = "bim.get_north_offset" + bl_label = "Get North Offset" def execute(self, context): x_angle = -context.scene.sun_pos_properties.north_offset bpy.context.scene.MapConversion.x_axis_abscissa = str(cos(x_angle)) bpy.context.scene.MapConversion.x_axis_ordinate = str(sin(x_angle)) - return {'FINISHED'} + return {"FINISHED"} class AddDrawingStyleAttribute(bpy.types.Operator): - bl_idname = 'bim.add_drawing_style_attribute' - bl_label = 'Add Drawing Style Attribute' + bl_idname = "bim.add_drawing_style_attribute" + bl_label = "Add Drawing Style Attribute" def execute(self, context): props = bpy.context.scene.camera.data.BIMCameraProperties context.scene.DocProperties.drawing_styles[props.active_drawing_style_index].attributes.add() - return {'FINISHED'} + return {"FINISHED"} class RemoveDrawingStyleAttribute(bpy.types.Operator): - bl_idname = 'bim.remove_drawing_style_attribute' - bl_label = 'Remove Drawing Style Attribute' + bl_idname = "bim.remove_drawing_style_attribute" + bl_label = "Remove Drawing Style Attribute" index: bpy.props.IntProperty() def execute(self, context): props = bpy.context.scene.camera.data.BIMCameraProperties context.scene.DocProperties.drawing_styles[props.active_drawing_style_index].attributes.remove(self.index) - return {'FINISHED'} + return {"FINISHED"} class CreateShapeFromStepId(bpy.types.Operator): - bl_idname = 'bim.create_shape_from_step_id' - bl_label = 'Create Shape From STEP ID' + bl_idname = "bim.create_shape_from_step_id" + bl_label = "Create Shape From STEP ID" def execute(self, context): - logger = logging.getLogger('ImportIFC') + logger = logging.getLogger("ImportIFC") self.ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, ifc.IfcStore.path, logger) self.file = ifc.IfcStore.get_file() element = self.file.by_id(int(bpy.context.scene.BIMDebugProperties.step_id)) settings = ifcopenshell.geom.settings() - #settings.set(settings.INCLUDE_CURVES, True) + # settings.set(settings.INCLUDE_CURVES, True) shape = ifcopenshell.geom.create_shape(settings, element) ifc_importer = import_ifc.IfcImporter(self.ifc_import_settings) ifc_importer.file = self.file mesh = ifc_importer.create_mesh(element, shape) - obj = bpy.data.objects.new('Debug', mesh) + obj = bpy.data.objects.new("Debug", mesh) bpy.context.scene.collection.objects.link(obj) - return {'FINISHED'} + return {"FINISHED"} class SelectHighPolygonMeshes(bpy.types.Operator): - bl_idname = 'bim.select_high_polygon_meshes' - bl_label = 'Select High Polygon Meshes' + bl_idname = "bim.select_high_polygon_meshes" + bl_label = "Select High Polygon Meshes" def execute(self, context): results = {} for obj in bpy.data.objects: - if not isinstance(obj.data, bpy.types.Mesh) \ - or len(obj.data.polygons) < int(bpy.context.scene.BIMDebugProperties.number_of_polygons): + if not isinstance(obj.data, bpy.types.Mesh) or len(obj.data.polygons) < int( + bpy.context.scene.BIMDebugProperties.number_of_polygons + ): continue try: obj.select_set(True) @@ -4065,12 +4173,12 @@ class SelectHighPolygonMeshes(bpy.types.Operator): relating_type = obj.BIMObjectProperties.relating_type if relating_type: relating_type.select_set(True) - return {'FINISHED'} + return {"FINISHED"} class RefreshDrawingList(bpy.types.Operator): - bl_idname = 'bim.refresh_drawing_list' - bl_label = 'Refresh Drawing List' + bl_idname = "bim.refresh_drawing_list" + bl_label = "Refresh Drawing List" def execute(self, context): while len(bpy.context.scene.DocProperties.drawings) > 0: @@ -4078,38 +4186,38 @@ class RefreshDrawingList(bpy.types.Operator): for obj in bpy.context.scene.objects: if not isinstance(obj.data, bpy.types.Camera): continue - if 'IfcGroup/' in obj.name and obj.users_collection[0].name == obj.name: + if "IfcGroup/" in obj.name and obj.users_collection[0].name == obj.name: new = bpy.context.scene.DocProperties.drawings.add() - new.name = obj.name.split('/')[1] + new.name = obj.name.split("/")[1] new.camera = obj - return {'FINISHED'} + return {"FINISHED"} class GetRepresentationIfcParameters(bpy.types.Operator): - bl_idname = 'bim.get_representation_ifc_parameters' - bl_label = 'Get Representation IFC Parameters' + bl_idname = "bim.get_representation_ifc_parameters" + bl_label = "Get Representation IFC Parameters" def execute(self, context): props = bpy.context.active_object.data.BIMMeshProperties dummy = ifcopenshell.file.from_string(props.ifc_definition) for element in dummy: - if not element.is_a('IfcRepresentationItem'): + if not element.is_a("IfcRepresentationItem"): continue for i in range(0, len(element)): - if element.attribute_type(i) == 'DOUBLE': + if element.attribute_type(i) == "DOUBLE": new = props.ifc_parameters.add() - new.name = '{}/{}'.format(element.is_a(), element.attribute_name(i)) + new.name = "{}/{}".format(element.is_a(), element.attribute_name(i)) new.step_id = element.id() new.type = element.attribute_type(i) new.index = i if element[i]: new.value = element[i] - return {'FINISHED'} + return {"FINISHED"} class UpdateIfcRepresentation(bpy.types.Operator): - bl_idname = 'bim.update_ifc_representation' - bl_label = 'Update IFC Representation' + bl_idname = "bim.update_ifc_representation" + bl_label = "Update IFC Representation" index: bpy.props.IntProperty() def execute(self, context): @@ -4119,12 +4227,12 @@ class UpdateIfcRepresentation(bpy.types.Operator): element = dummy.by_id(parameter.step_id)[parameter.index] = parameter.value props.ifc_definition = dummy.to_string() self.recreate_ifc_representation() - return {'FINISHED'} + return {"FINISHED"} def recreate_ifc_representation(self): props = bpy.context.active_object.data.BIMMeshProperties dummy = ifcopenshell.file.from_string(props.ifc_definition) - logger = logging.getLogger('ImportIFC') + logger = logging.getLogger("ImportIFC") self.ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, ifc.IfcStore.path, logger) element = dummy.by_id(props.ifc_definition_id) settings = ifcopenshell.geom.settings() diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 35fa830fa9..4a2254e79a 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -19,7 +19,7 @@ from bpy.props import ( IntProperty, FloatProperty, FloatVectorProperty, - CollectionProperty + CollectionProperty, ) cwd = os.path.dirname(os.path.realpath(__file__)) @@ -53,84 +53,90 @@ sheets_enum = [] vector_styles_enum = [] bcfviewpoints_enum = [] + @persistent def setDefaultProperties(scene): - if bpy.context.scene.BIMProperties.has_model_context \ - and len(bpy.context.scene.BIMProperties.model_subcontexts) == 0: + if ( + bpy.context.scene.BIMProperties.has_model_context + and len(bpy.context.scene.BIMProperties.model_subcontexts) == 0 + ): subcontext = bpy.context.scene.BIMProperties.model_subcontexts.add() - subcontext.name = 'Body' - subcontext.target_view = 'MODEL_VIEW' + subcontext.name = "Body" + subcontext.target_view = "MODEL_VIEW" subcontext = bpy.context.scene.BIMProperties.model_subcontexts.add() - subcontext.name = 'Box' - subcontext.target_view = 'MODEL_VIEW' - if bpy.context.scene.BIMProperties.has_plan_context \ - and len(bpy.context.scene.BIMProperties.plan_subcontexts) == 0: + subcontext.name = "Box" + subcontext.target_view = "MODEL_VIEW" + if bpy.context.scene.BIMProperties.has_plan_context and len(bpy.context.scene.BIMProperties.plan_subcontexts) == 0: subcontext = bpy.context.scene.BIMProperties.plan_subcontexts.add() - subcontext.name = 'Annotation' - subcontext.target_view = 'PLAN_VIEW' + subcontext.name = "Annotation" + subcontext.target_view = "PLAN_VIEW" if len(bpy.context.scene.DocProperties.drawing_styles) == 0: drawing_style = bpy.context.scene.DocProperties.drawing_styles.add() - drawing_style.name = 'Technical' - drawing_style.render_type = 'VIEWPORT' - drawing_style.raster_style = json.dumps({ - 'bpy.data.worlds[0].color': (1, 1, 1), - 'bpy.context.scene.render.engine': 'BLENDER_WORKBENCH', - 'bpy.context.scene.render.film_transparent': False, - 'bpy.context.scene.display.shading.show_object_outline': True, - 'bpy.context.scene.display.shading.show_cavity': False, - 'bpy.context.scene.display.shading.cavity_type': 'BOTH', - 'bpy.context.scene.display.shading.curvature_ridge_factor': 1, - 'bpy.context.scene.display.shading.curvature_valley_factor': 1, - 'bpy.context.scene.view_settings.view_transform': 'Standard', - 'bpy.context.scene.display.shading.light': 'FLAT', - 'bpy.context.scene.display.shading.color_type': 'SINGLE', - 'bpy.context.scene.display.shading.single_color': (1, 1, 1), - 'bpy.context.scene.display.shading.show_shadows': False, - 'bpy.context.scene.display.shading.shadow_intensity': 0.5, - 'bpy.context.scene.display.light_direction': (.5, .5, .5), - 'bpy.context.scene.view_settings.use_curve_mapping': False, - 'space.overlay.show_wireframes': True, - 'space.overlay.wireframe_threshold': 0, - 'space.overlay.show_floor': False, - 'space.overlay.show_axis_x': False, - 'space.overlay.show_axis_y': False, - 'space.overlay.show_axis_z': False, - 'space.overlay.show_object_origins': False, - 'space.overlay.show_relationship_lines': False, - }) + drawing_style.name = "Technical" + drawing_style.render_type = "VIEWPORT" + drawing_style.raster_style = json.dumps( + { + "bpy.data.worlds[0].color": (1, 1, 1), + "bpy.context.scene.render.engine": "BLENDER_WORKBENCH", + "bpy.context.scene.render.film_transparent": False, + "bpy.context.scene.display.shading.show_object_outline": True, + "bpy.context.scene.display.shading.show_cavity": False, + "bpy.context.scene.display.shading.cavity_type": "BOTH", + "bpy.context.scene.display.shading.curvature_ridge_factor": 1, + "bpy.context.scene.display.shading.curvature_valley_factor": 1, + "bpy.context.scene.view_settings.view_transform": "Standard", + "bpy.context.scene.display.shading.light": "FLAT", + "bpy.context.scene.display.shading.color_type": "SINGLE", + "bpy.context.scene.display.shading.single_color": (1, 1, 1), + "bpy.context.scene.display.shading.show_shadows": False, + "bpy.context.scene.display.shading.shadow_intensity": 0.5, + "bpy.context.scene.display.light_direction": (0.5, 0.5, 0.5), + "bpy.context.scene.view_settings.use_curve_mapping": False, + "space.overlay.show_wireframes": True, + "space.overlay.wireframe_threshold": 0, + "space.overlay.show_floor": False, + "space.overlay.show_axis_x": False, + "space.overlay.show_axis_y": False, + "space.overlay.show_axis_z": False, + "space.overlay.show_object_origins": False, + "space.overlay.show_relationship_lines": False, + } + ) drawing_style = bpy.context.scene.DocProperties.drawing_styles.add() - drawing_style.name = 'Shaded' - drawing_style.render_type = 'VIEWPORT' - drawing_style.raster_style = json.dumps({ - 'bpy.data.worlds[0].color': (1, 1, 1), - 'bpy.context.scene.render.engine': 'BLENDER_WORKBENCH', - 'bpy.context.scene.render.film_transparent': False, - 'bpy.context.scene.display.shading.show_object_outline': True, - 'bpy.context.scene.display.shading.show_cavity': True, - 'bpy.context.scene.display.shading.cavity_type': 'BOTH', - 'bpy.context.scene.display.shading.curvature_ridge_factor': 1, - 'bpy.context.scene.display.shading.curvature_valley_factor': 1, - 'bpy.context.scene.view_settings.view_transform': 'Standard', - 'bpy.context.scene.display.shading.light': 'STUDIO', - 'bpy.context.scene.display.shading.color_type': 'MATERIAL', - 'bpy.context.scene.display.shading.single_color': (1, 1, 1), - 'bpy.context.scene.display.shading.show_shadows': True, - 'bpy.context.scene.display.shading.shadow_intensity': 0.5, - 'bpy.context.scene.display.light_direction': (.5, .5, .5), - 'bpy.context.scene.view_settings.use_curve_mapping': False, - 'space.overlay.show_wireframes': True, - 'space.overlay.wireframe_threshold': 0, - 'space.overlay.show_floor': False, - 'space.overlay.show_axis_x': False, - 'space.overlay.show_axis_y': False, - 'space.overlay.show_axis_z': False, - 'space.overlay.show_object_origins': False, - 'space.overlay.show_relationship_lines': False, - }) + drawing_style.name = "Shaded" + drawing_style.render_type = "VIEWPORT" + drawing_style.raster_style = json.dumps( + { + "bpy.data.worlds[0].color": (1, 1, 1), + "bpy.context.scene.render.engine": "BLENDER_WORKBENCH", + "bpy.context.scene.render.film_transparent": False, + "bpy.context.scene.display.shading.show_object_outline": True, + "bpy.context.scene.display.shading.show_cavity": True, + "bpy.context.scene.display.shading.cavity_type": "BOTH", + "bpy.context.scene.display.shading.curvature_ridge_factor": 1, + "bpy.context.scene.display.shading.curvature_valley_factor": 1, + "bpy.context.scene.view_settings.view_transform": "Standard", + "bpy.context.scene.display.shading.light": "STUDIO", + "bpy.context.scene.display.shading.color_type": "MATERIAL", + "bpy.context.scene.display.shading.single_color": (1, 1, 1), + "bpy.context.scene.display.shading.show_shadows": True, + "bpy.context.scene.display.shading.shadow_intensity": 0.5, + "bpy.context.scene.display.light_direction": (0.5, 0.5, 0.5), + "bpy.context.scene.view_settings.use_curve_mapping": False, + "space.overlay.show_wireframes": True, + "space.overlay.wireframe_threshold": 0, + "space.overlay.show_floor": False, + "space.overlay.show_axis_x": False, + "space.overlay.show_axis_y": False, + "space.overlay.show_axis_z": False, + "space.overlay.show_object_origins": False, + "space.overlay.show_relationship_lines": False, + } + ) drawing_style = bpy.context.scene.DocProperties.drawing_styles.add() - drawing_style.name = 'Blender Default' - drawing_style.render_type = 'DEFAULT' - bpy.ops.bim.save_drawing_style(index='2') + drawing_style.name = "Blender Default" + drawing_style.render_type = "DEFAULT" + bpy.ops.bim.save_drawing_style(index="2") def getIfcPredefinedTypes(self, context): @@ -139,10 +145,10 @@ def getIfcPredefinedTypes(self, context): for name, data in schema.ifc.elements.items(): if name != self.ifc_class.strip(): continue - for attribute in data['attributes']: - if attribute['name'] != 'PredefinedType': + for attribute in data["attributes"]: + if attribute["name"] != "PredefinedType": continue - types_enum.extend([(e, e, '') for e in attribute['enum_values']]) + types_enum.extend([(e, e, "") for e in attribute["enum_values"]]) return types_enum @@ -162,58 +168,60 @@ def refreshPredefinedTypes(self, context): def getDiagramScales(self, context): global diagram_scales_enum - if len(diagram_scales_enum) < 1 \ - or (bpy.context.scene.unit_settings.system == 'IMPERIAL' and len(diagram_scales_enum) == 13) \ - or (bpy.context.scene.unit_settings.system == 'METRIC' and len(diagram_scales_enum) == 31) : - if bpy.context.scene.unit_settings.system == 'IMPERIAL': + if ( + len(diagram_scales_enum) < 1 + or (bpy.context.scene.unit_settings.system == "IMPERIAL" and len(diagram_scales_enum) == 13) + or (bpy.context.scene.unit_settings.system == "METRIC" and len(diagram_scales_enum) == 31) + ): + if bpy.context.scene.unit_settings.system == "IMPERIAL": diagram_scales_enum = [ - ('CUSTOM', 'Custom', ''), - ('1\'=1\'-0"|1/1', '1\'=1\'-0"', ''), - ('6"=1\'-0"|1/6', '6"=1\'-0"', ''), - ('1-1/2"=1\'-0"|1/8', '1-1/2"=1\'-0"', ''), - ('1"=1\'-0"|1/12', '1"=1\'-0"', ''), - ('3/4"=1\'-0"|1/16', '3/4"=1\'-0"', ''), - ('1/2"=1\'-0"|1/24', '1/2"=1\'-0"', ''), - ('3/8"=1\'-0"|1/32', '3/8"=1\'-0"', ''), - ('1/4"=1\'-0"|1/48', '1/4"=1\'-0"', ''), - ('3/16"=1\'-0"|1/64', '3/16"=1\'-0"', ''), - ('1/8"=1\'-0"|1/96', '1/8"=1\'-0"', ''), - ('3/32"=1\'-0"|1/128', '3/32"=1\'-0"', ''), - ('1/16"=1\'-0"|1/192', '1/16"=1\'-0"', ''), - ('1/32"=1\'-0"|1/384', '1/32"=1\'-0"', ''), - ('1/64"=1\'-0"|1/768', '1/64"=1\'-0"', ''), - ('1/128"=1\'-0"|1/1536', '1/128"=1\'-0"', ''), - ('1"=10\'|1/120', '1"=10\'', ''), - ('1"=20\'|1/240', '1"=20\'', ''), - ('1"=30\'|1/360', '1"=30\'', ''), - ('1"=40\'|1/480', '1"=40\'', ''), - ('1"=50\'|1/600', '1"=50\'', ''), - ('1"=60\'|1/720', '1"=60\'', ''), - ('1"=70\'|1/840', '1"=70\'', ''), - ('1"=80\'|1/960', '1"=80\'', ''), - ('1"=90\'|1/1080', '1"=90\'', ''), - ('1"=100\'|1/1200', '1"=100\'', ''), - ('1"=150\'|1/1800', '1"=150\'', ''), - ('1"=200\'|1/2400', '1"=200\'', ''), - ('1"=300\'|1/3600', '1"=300\'', ''), - ('1"=400\'|1/4800', '1"=400\'', ''), - ('1"=500\'|1/6000', '1"=500\'', ''), + ("CUSTOM", "Custom", ""), + ("1'=1'-0\"|1/1", "1'=1'-0\"", ""), + ('6"=1\'-0"|1/6', '6"=1\'-0"', ""), + ('1-1/2"=1\'-0"|1/8', '1-1/2"=1\'-0"', ""), + ('1"=1\'-0"|1/12', '1"=1\'-0"', ""), + ('3/4"=1\'-0"|1/16', '3/4"=1\'-0"', ""), + ('1/2"=1\'-0"|1/24', '1/2"=1\'-0"', ""), + ('3/8"=1\'-0"|1/32', '3/8"=1\'-0"', ""), + ('1/4"=1\'-0"|1/48', '1/4"=1\'-0"', ""), + ('3/16"=1\'-0"|1/64', '3/16"=1\'-0"', ""), + ('1/8"=1\'-0"|1/96', '1/8"=1\'-0"', ""), + ('3/32"=1\'-0"|1/128', '3/32"=1\'-0"', ""), + ('1/16"=1\'-0"|1/192', '1/16"=1\'-0"', ""), + ('1/32"=1\'-0"|1/384', '1/32"=1\'-0"', ""), + ('1/64"=1\'-0"|1/768', '1/64"=1\'-0"', ""), + ('1/128"=1\'-0"|1/1536', '1/128"=1\'-0"', ""), + ("1\"=10'|1/120", "1\"=10'", ""), + ("1\"=20'|1/240", "1\"=20'", ""), + ("1\"=30'|1/360", "1\"=30'", ""), + ("1\"=40'|1/480", "1\"=40'", ""), + ("1\"=50'|1/600", "1\"=50'", ""), + ("1\"=60'|1/720", "1\"=60'", ""), + ("1\"=70'|1/840", "1\"=70'", ""), + ("1\"=80'|1/960", "1\"=80'", ""), + ("1\"=90'|1/1080", "1\"=90'", ""), + ("1\"=100'|1/1200", "1\"=100'", ""), + ("1\"=150'|1/1800", "1\"=150'", ""), + ("1\"=200'|1/2400", "1\"=200'", ""), + ("1\"=300'|1/3600", "1\"=300'", ""), + ("1\"=400'|1/4800", "1\"=400'", ""), + ("1\"=500'|1/6000", "1\"=500'", ""), ] else: diagram_scales_enum = [ - ('CUSTOM', 'Custom', ''), - ('1:5000|1/5000', '1:5000', ''), - ('1:2000|1/2000', '1:2000', ''), - ('1:1000|1/1000', '1:1000', ''), - ('1:500|1/500', '1:500', ''), - ('1:200|1/200', '1:200', ''), - ('1:100|1/100', '1:100', ''), - ('1:50|1/50', '1:50', ''), - ('1:20|1/20', '1:20', ''), - ('1:10|1/10', '1:10', ''), - ('1:5|1/5', '1:5', ''), - ('1:2|1/2', '1:2', ''), - ('1:1|1/1', '1:1', '') + ("CUSTOM", "Custom", ""), + ("1:5000|1/5000", "1:5000", ""), + ("1:2000|1/2000", "1:2000", ""), + ("1:1000|1/1000", "1:1000", ""), + ("1:500|1/500", "1:500", ""), + ("1:200|1/200", "1:200", ""), + ("1:100|1/100", "1:100", ""), + ("1:50|1/50", "1:50", ""), + ("1:20|1/20", "1:20", ""), + ("1:10|1/10", "1:10", ""), + ("1:5|1/5", "1:5", ""), + ("1:2|1/2", "1:2", ""), + ("1:1|1/1", "1:1", ""), ] return diagram_scales_enum @@ -223,23 +231,31 @@ def updateDrawingName(self, context): return if self.camera.name == self.name: return - self.camera.name = 'IfcGroup/{}'.format(self.name) + self.camera.name = "IfcGroup/{}".format(self.name) self.camera.users_collection[0].name = self.camera.name - self.name = self.camera.name.split('/')[1] + self.name = self.camera.name.split("/")[1] def getBoundaryConditionClasses(self, context): - return [(c, c, '') for c in - ['IfcBoundaryEdgeCondition', 'IfcBoundaryFaceCondition', - 'IfcBoundaryNodeCondition', 'IfcBoundaryNodeConditionWarping']] + return [ + (c, c, "") + for c in [ + "IfcBoundaryEdgeCondition", + "IfcBoundaryFaceCondition", + "IfcBoundaryNodeCondition", + "IfcBoundaryNodeConditionWarping", + ] + ] def refreshBoundaryConditionAttributes(self, context): while len(context.active_object.BIMObjectProperties.boundary_condition.attributes) > 0: context.active_object.BIMObjectProperties.boundary_condition.attributes.remove(0) - for attribute in schema.ifc.elements[context.active_object.BIMObjectProperties.boundary_condition.name]['complex_attributes']: + for attribute in schema.ifc.elements[context.active_object.BIMObjectProperties.boundary_condition.name][ + "complex_attributes" + ]: new_attribute = context.active_object.BIMObjectProperties.boundary_condition.attributes.add() - new_attribute.name = attribute['name'] + new_attribute.name = attribute["name"] def refreshActiveDrawingIndex(self, context): @@ -249,43 +265,49 @@ def refreshActiveDrawingIndex(self, context): def getIfcProducts(self, context): global products_enum if len(products_enum) < 1: - products_enum.extend([(e, e, '') for e in [ - 'IfcElement', - 'IfcElementType', - 'IfcSpatialElement', - 'IfcGroup', - 'IfcStructural', - 'IfcPositioningElement', - 'IfcContext', - 'IfcAnnotation']]) + products_enum.extend( + [ + (e, e, "") + for e in [ + "IfcElement", + "IfcElementType", + "IfcSpatialElement", + "IfcGroup", + "IfcStructural", + "IfcPositioningElement", + "IfcContext", + "IfcAnnotation", + ] + ] + ) return products_enum def getIfcClasses(self, context): global classes_enum if len(classes_enum) < 1: - classes_enum.extend([(e, e, '') for e in getattr(schema.ifc, self.ifc_product)]) + classes_enum.extend([(e, e, "") for e in getattr(schema.ifc, self.ifc_product)]) return classes_enum def getProfileDef(self, context): global profiledef_enum if len(profiledef_enum) < 1: - profiledef_enum.extend([(e, e, '') for e in getattr(schema.ifc, 'IfcParameterizedProfileDef')]) + profiledef_enum.extend([(e, e, "") for e in getattr(schema.ifc, "IfcParameterizedProfileDef")]) return profiledef_enum def getPersons(self, context): global persons_enum persons_enum.clear() - persons_enum.extend([(p.name, p.name, '') for p in bpy.context.scene.BIMProperties.people]) + persons_enum.extend([(p.name, p.name, "") for p in bpy.context.scene.BIMProperties.people]) return persons_enum def getOrganisations(self, context): global organisations_enum organisations_enum.clear() - organisations_enum.extend([(o.name, o.name, '') for o in bpy.context.scene.BIMProperties.organisations]) + organisations_enum.extend([(o.name, o.name, "") for o in bpy.context.scene.BIMProperties.organisations]) return organisations_enum @@ -293,8 +315,8 @@ def getAvailableMaterialPsets(self, context): global availablematerialpsets_enum if len(availablematerialpsets_enum) < 1: availablematerialpsets_enum.clear() - files = os.listdir(os.path.join(context.scene.BIMProperties.data_dir, 'material')) - availablematerialpsets_enum.extend([(f, f, '') for f in files]) + files = os.listdir(os.path.join(context.scene.BIMProperties.data_dir, "material")) + availablematerialpsets_enum.extend([(f, f, "") for f in files]) return availablematerialpsets_enum @@ -302,11 +324,11 @@ def getIfcPatchRecipes(self, context): global ifcpatchrecipes_enum if len(ifcpatchrecipes_enum) < 1: ifcpatchrecipes_enum.clear() - for filename in Path(os.path.join(cwd, '..', 'libs', 'site', 'packages', 'recipes')).glob('*.py'): + for filename in Path(os.path.join(cwd, "..", "libs", "site", "packages", "recipes")).glob("*.py"): f = str(filename.stem) - if f == '__init__': + if f == "__init__": continue - ifcpatchrecipes_enum.append((f, f, '')) + ifcpatchrecipes_enum.append((f, f, "")) return ifcpatchrecipes_enum @@ -314,9 +336,9 @@ def getFeaturesFiles(self, context): global featuresfiles_enum if len(featuresfiles_enum) < 1: featuresfiles_enum.clear() - for filename in Path(context.scene.BIMProperties.features_dir).glob('*.feature'): + for filename in Path(context.scene.BIMProperties.features_dir).glob("*.feature"): f = str(filename.stem) - featuresfiles_enum.append((f, f, '')) + featuresfiles_enum.append((f, f, "")) return featuresfiles_enum @@ -330,9 +352,11 @@ def getTitleblocks(self, context): global titleblocks_enum if len(titleblocks_enum) < 1: titleblocks_enum.clear() - for filename in Path(os.path.join(context.scene.BIMProperties.data_dir, 'templates', 'titleblocks')).glob('*.svg'): + for filename in Path(os.path.join(context.scene.BIMProperties.data_dir, "templates", "titleblocks")).glob( + "*.svg" + ): f = str(filename.stem) - titleblocks_enum.append((f, f, '')) + titleblocks_enum.append((f, f, "")) return titleblocks_enum @@ -347,14 +371,14 @@ def getScenarios(self, context): if len(scenarios_enum) < 1: scenarios_enum.clear() filename = os.path.join( - context.scene.BIMProperties.features_dir, - context.scene.BIMProperties.features_file + '.feature') - with open(filename, 'r') as feature_file: + context.scene.BIMProperties.features_dir, context.scene.BIMProperties.features_file + ".feature" + ) + with open(filename, "r") as feature_file: lines = feature_file.readlines() for line in lines: - if 'Scenario:' in line: - s = line.strip()[len('Scenario: '):] - scenarios_enum.append((s, s, '')) + if "Scenario:" in line: + s = line.strip()[len("Scenario: ") :] + scenarios_enum.append((s, s, "")) return scenarios_enum @@ -367,8 +391,8 @@ def refreshScenarios(self, context): def getPsetTemplateFiles(self, context): global psettemplatefiles_enum if len(psettemplatefiles_enum) < 1: - files = os.listdir(os.path.join(self.data_dir, 'pset')) - psettemplatefiles_enum.extend([(f.replace('.ifc', ''), f.replace('.ifc', ''), '') for f in files]) + files = os.listdir(os.path.join(self.data_dir, "pset")) + psettemplatefiles_enum.extend([(f.replace(".ifc", ""), f.replace(".ifc", ""), "") for f in files]) return psettemplatefiles_enum @@ -382,10 +406,11 @@ def getPropertySetTemplates(self, context): global propertysettemplates_enum if len(propertysettemplates_enum) < 1: ifc.IfcStore.pset_template_path = os.path.join( - context.scene.BIMProperties.data_dir, 'pset', context.scene.BIMProperties.pset_template_files + '.ifc') + context.scene.BIMProperties.data_dir, "pset", context.scene.BIMProperties.pset_template_files + ".ifc" + ) ifc.IfcStore.pset_template_file = ifcopenshell.open(ifc.IfcStore.pset_template_path) - templates = ifc.IfcStore.pset_template_file.by_type('IfcPropertySetTemplate') - propertysettemplates_enum.extend([(t.GlobalId, t.Name, '') for t in templates]) + templates = ifc.IfcStore.pset_template_file.by_type("IfcPropertySetTemplate") + propertysettemplates_enum.extend([(t.GlobalId, t.Name, "") for t in templates]) return propertysettemplates_enum @@ -393,28 +418,27 @@ def getClassifications(self, context): global classification_enum if len(classification_enum) < 1: classification_enum.clear() - files = os.listdir(os.path.join(self.schema_dir, 'classifications')) - classification_enum.extend([(f.replace('.ifc', ''), f.replace('.ifc', ''), '') for f in files]) + files = os.listdir(os.path.join(self.schema_dir, "classifications")) + classification_enum.extend([(f.replace(".ifc", ""), f.replace(".ifc", ""), "") for f in files]) return classification_enum def refreshReferences(self, context): context.scene.BIMProperties.classification_references.root = None ClassificationView.raw_data = schema.ifc.load_classification(context.scene.BIMProperties.classification) - context.scene.BIMProperties.classification_references.root = '' + context.scene.BIMProperties.classification_references.root = "" # TODO: move into util module. See bug #971 def getPsetNames(self, context): global psetnames_enum psetnames_enum.clear() - if '/' in context.active_object.name \ - and context.active_object.name.split('/')[0] in schema.ifc.elements: + if "/" in context.active_object.name and context.active_object.name.split("/")[0] in schema.ifc.elements: empty = ifcopenshell.file() - element = empty.create_entity(context.active_object.name.split('/')[0]) + element = empty.create_entity(context.active_object.name.split("/")[0]) for ifc_class, pset_names in schema.ifc.applicable_psets.items(): if element.is_a(ifc_class): - psetnames_enum.extend([(p, p, '') for p in pset_names]) + psetnames_enum.extend([(p, p, "") for p in pset_names]) return psetnames_enum @@ -422,57 +446,60 @@ def getPsetNames(self, context): def getQtoNames(self, context): global qtonames_enum qtonames_enum.clear() - if '/' in context.active_object.name \ - and context.active_object.name.split('/')[0] in schema.ifc.elements: + if "/" in context.active_object.name and context.active_object.name.split("/")[0] in schema.ifc.elements: empty = ifcopenshell.file() - element = empty.create_entity(context.active_object.name.split('/')[0]) + element = empty.create_entity(context.active_object.name.split("/")[0]) for ifc_class, qto_names in schema.ifc.applicable_qtos.items(): if element.is_a(ifc_class): - qtonames_enum.extend([(q, q, '') for q in qto_names]) + qtonames_enum.extend([(q, q, "") for q in qto_names]) return qtonames_enum def getApplicableAttributes(self, context): global attributes_enum attributes_enum.clear() - if '/' in context.active_object.name \ - and context.active_object.name.split('/')[0] in schema.ifc.elements: - attributes_enum.extend([(a['name'], a['name'], '') for a in - schema.ifc.elements[context.active_object.name.split('/')[0]]['attributes'] - if self.attributes.find(a['name']) == -1]) + if "/" in context.active_object.name and context.active_object.name.split("/")[0] in schema.ifc.elements: + attributes_enum.extend( + [ + (a["name"], a["name"], "") + for a in schema.ifc.elements[context.active_object.name.split("/")[0]]["attributes"] + if self.attributes.find(a["name"]) == -1 + ] + ) return attributes_enum def getApplicableMaterialAttributes(self, context): global materialattributes_enum materialattributes_enum.clear() - if '/' in context.active_object.name \ - and context.active_object.name.split('/')[0] in schema.ifc.elements: + if "/" in context.active_object.name and context.active_object.name.split("/")[0] in schema.ifc.elements: material_type = context.active_object.BIMObjectProperties.material_type - if material_type[-3:] == 'Set': + if material_type[-3:] == "Set": material_type = material_type[0:-3] - materialattributes_enum.extend([(a['name'], a['name'], '') for a in - schema.ifc.IfcMaterialDefinition[material_type]['attributes'] - if self.attributes.find(a['name']) == -1]) + materialattributes_enum.extend( + [ + (a["name"], a["name"], "") + for a in schema.ifc.IfcMaterialDefinition[material_type]["attributes"] + if self.attributes.find(a["name"]) == -1 + ] + ) return materialattributes_enum def refreshProfileAttributes(self, context): while len(context.active_object.active_material.BIMMaterialProperties.profile_attributes) > 0: context.active_object.active_material.BIMMaterialProperties.profile_attributes.remove(0) - for attribute in schema.ifc.IfcParameterizedProfileDef[self.profile_def]['attributes']: + for attribute in schema.ifc.IfcParameterizedProfileDef[self.profile_def]["attributes"]: profile_attribute = context.active_object.active_material.BIMMaterialProperties.profile_attributes.add() - profile_attribute.name = attribute['name'] + profile_attribute.name = attribute["name"] def getMaterialTypes(self, context): global materialtypes_enum materialtypes_enum.clear() - materialtypes_enum = [(m, m, '') for m in [ - 'IfcMaterial', - 'IfcMaterialConstituentSet', - 'IfcMaterialLayerSet', - 'IfcMaterialProfileSet']] + materialtypes_enum = [ + (m, m, "") for m in ["IfcMaterial", "IfcMaterialConstituentSet", "IfcMaterialLayerSet", "IfcMaterialProfileSet"] + ] return materialtypes_enum @@ -482,7 +509,7 @@ def getSubcontexts(self, context): # TODO: allow override of generated subcontexts? subcontexts = export_ifc.IfcExportSettings().subcontexts for subcontext in subcontexts: - subcontexts_enum.append((subcontext, subcontext, '')) + subcontexts_enum.append((subcontext, subcontext, "")) return subcontexts_enum @@ -490,7 +517,7 @@ def getTargetViews(self, context): global target_views_enum target_views_enum.clear() for target_view in export_ifc.IfcExportSettings().target_views: - target_views_enum.append((target_view, target_view, '')) + target_views_enum.append((target_view, target_view, "")) return target_views_enum @@ -498,9 +525,9 @@ def getVectorStyles(self, context): global vector_styles_enum if len(vector_styles_enum) < 1: sheets_enum.clear() - for filename in Path(os.path.join(context.scene.BIMProperties.data_dir, 'styles')).glob('*.css'): + for filename in Path(os.path.join(context.scene.BIMProperties.data_dir, "styles")).glob("*.css"): f = str(filename.stem) - vector_styles_enum.append((f, f, '')) + vector_styles_enum.append((f, f, "")) return vector_styles_enum @@ -518,139 +545,171 @@ class Variable(PropertyGroup): class Subcontext(PropertyGroup): - name: StringProperty(name='Name') - context: StringProperty(name='Context') - target_view: StringProperty(name='Target View') + name: StringProperty(name="Name") + context: StringProperty(name="Context") + target_view: StringProperty(name="Target View") class Drawing(PropertyGroup): - name: StringProperty(name='Name', update=updateDrawingName) - camera: PointerProperty(name='Camera', type=bpy.types.Object) + name: StringProperty(name="Name", update=updateDrawingName) + camera: PointerProperty(name="Camera", type=bpy.types.Object) class Schedule(PropertyGroup): - name: StringProperty(name='Name') - file: StringProperty(name='File') + name: StringProperty(name="Name") + file: StringProperty(name="File") class Sheet(PropertyGroup): def set_name(self, new): - old = self.get('name') - path = os.path.join(bpy.context.scene.BIMProperties.data_dir, 'sheets') - if old and os.path.isfile(os.path.join(path, old + '.svg')): - os.rename(os.path.join(path, old + '.svg'), os.path.join(path, new + '.svg')) - self['name'] = new + old = self.get("name") + path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "sheets") + if old and os.path.isfile(os.path.join(path, old + ".svg")): + os.rename(os.path.join(path, old + ".svg"), os.path.join(path, new + ".svg")) + self["name"] = new def get_name(self): - return self.get('name') + return self.get("name") - name: StringProperty(name='Name', get=get_name, set=set_name) - drawings: CollectionProperty(name='Drawings', type=Drawing) - active_drawing_index: IntProperty(name='Active Drawing Index') + name: StringProperty(name="Name", get=get_name, set=set_name) + drawings: CollectionProperty(name="Drawings", type=Drawing) + active_drawing_index: IntProperty(name="Active Drawing Index") class DrawingStyle(PropertyGroup): - name: StringProperty(name='Name') - raster_style: StringProperty(name='Raster Style') - render_type: EnumProperty(items=[ - ('NONE', 'None', ''), - ('DEFAULT', 'Default', ''), - ('VIEWPORT', 'Viewport', ''), - ], name='Render Type', default='VIEWPORT') - vector_style: EnumProperty(items=getVectorStyles, name='Vector Style') - include_query: StringProperty(name='Include Query') - exclude_query: StringProperty(name='Exclude Query') - attributes: CollectionProperty(name='Attributes', type=StrProperty) + name: StringProperty(name="Name") + raster_style: StringProperty(name="Raster Style") + render_type: EnumProperty( + items=[ + ("NONE", "None", ""), + ("DEFAULT", "Default", ""), + ("VIEWPORT", "Viewport", ""), + ], + name="Render Type", + default="VIEWPORT", + ) + vector_style: EnumProperty(items=getVectorStyles, name="Vector Style") + include_query: StringProperty(name="Include Query") + exclude_query: StringProperty(name="Exclude Query") + attributes: CollectionProperty(name="Attributes", type=StrProperty) class DocProperties(PropertyGroup): should_recut: BoolProperty(name="Should Recut", default=True) should_recut_selected: BoolProperty(name="Should Recut Selected Only", default=False) should_extract: BoolProperty(name="Should Extract", default=True) - drawings: CollectionProperty(name='Drawings', type=Drawing) - active_drawing_index: IntProperty(name='Active Drawing Index', update=refreshActiveDrawingIndex) - current_drawing_index: IntProperty(name='Current Drawing Index') - schedules: CollectionProperty(name='Schedules', type=Schedule) - active_schedule_index: IntProperty(name='Active Schedule Index') + drawings: CollectionProperty(name="Drawings", type=Drawing) + active_drawing_index: IntProperty(name="Active Drawing Index", update=refreshActiveDrawingIndex) + current_drawing_index: IntProperty(name="Current Drawing Index") + schedules: CollectionProperty(name="Schedules", type=Schedule) + active_schedule_index: IntProperty(name="Active Schedule Index") titleblock: EnumProperty(items=getTitleblocks, name="Titleblock", update=refreshTitleblocks) - sheets: CollectionProperty(name='Sheets', type=Sheet) - active_sheet_index: IntProperty(name='Active Sheet Index') - ifc_files: CollectionProperty(name='IFCs', type=StrProperty) - drawing_styles: CollectionProperty(name='Drawing Styles', type=DrawingStyle) + sheets: CollectionProperty(name="Sheets", type=Sheet) + active_sheet_index: IntProperty(name="Active Sheet Index") + ifc_files: CollectionProperty(name="IFCs", type=StrProperty) + drawing_styles: CollectionProperty(name="Drawing Styles", type=DrawingStyle) class BIMCameraProperties(PropertyGroup): view_name: StringProperty(name="View Name") - target_view: EnumProperty(items=[ - ('PLAN_VIEW', 'PLAN_VIEW', ''), - ('ELEVATION_VIEW', 'ELEVATION_VIEW', ''), - ('SECTION_VIEW', 'SECTION_VIEW', ''), - ('REFLECTED_PLAN_VIEW', 'REFLECTED_PLAN_VIEW', ''), - ('MODEL_VIEW', 'MODEL_VIEW', ''), - ], name='Target View', default='PLAN_VIEW') - diagram_scale: EnumProperty(items=getDiagramScales, name='Drawing Scale') - custom_diagram_scale: StringProperty(name='Custom Scale') - raster_x: IntProperty(name='Raster X', default=1000) - raster_y: IntProperty(name='Raster Y', default=1000) - is_nts: BoolProperty(name='Is NTS') - cut_objects: EnumProperty(items=[ - ('.IfcWall|.IfcSlab|.IfcCurtainWall|.IfcStair|.IfcStairFlight|.IfcColumn|.IfcBeam|.IfcMember|.IfcCovering|.IfcSpace', - 'Overall Plan / Section', ''), - ('.IfcElement', 'Detail Drawing', ''), - ('CUSTOM', 'Custom', '') - ], name='Cut Objects') - cut_objects_custom: StringProperty(name='Custom Cut') - active_drawing_style_index: IntProperty(name='Active Drawing Style Index') + target_view: EnumProperty( + items=[ + ("PLAN_VIEW", "PLAN_VIEW", ""), + ("ELEVATION_VIEW", "ELEVATION_VIEW", ""), + ("SECTION_VIEW", "SECTION_VIEW", ""), + ("REFLECTED_PLAN_VIEW", "REFLECTED_PLAN_VIEW", ""), + ("MODEL_VIEW", "MODEL_VIEW", ""), + ], + name="Target View", + default="PLAN_VIEW", + ) + diagram_scale: EnumProperty(items=getDiagramScales, name="Drawing Scale") + custom_diagram_scale: StringProperty(name="Custom Scale") + raster_x: IntProperty(name="Raster X", default=1000) + raster_y: IntProperty(name="Raster Y", default=1000) + is_nts: BoolProperty(name="Is NTS") + cut_objects: EnumProperty( + items=[ + ( + ".IfcWall|.IfcSlab|.IfcCurtainWall|.IfcStair|.IfcStairFlight|.IfcColumn|.IfcBeam|.IfcMember|.IfcCovering|.IfcSpace", + "Overall Plan / Section", + "", + ), + (".IfcElement", "Detail Drawing", ""), + ("CUSTOM", "Custom", ""), + ], + name="Cut Objects", + ) + cut_objects_custom: StringProperty(name="Custom Cut") + active_drawing_style_index: IntProperty(name="Active Drawing Style Index") class BIMTextProperties(PropertyGroup): - font_size: EnumProperty(items=[ - ('1.8', '1.8 - Small', ''), - ('2.5', '2.5 - Regular', ''), - ('3.5', '3.5 - Large', ''), - ('5.0', '5.0 - Header', ''), - ('7.0', '7.0 - Title', ''), - ], update=refreshFontSize, name='Font Size') - symbol: EnumProperty(items=[ - ('None', 'None', ''), - ('rectangle-tag', 'Rectangle Tag', ''), - ('door-tag', 'Door Tag', ''), - ], update=refreshFontSize, name='Symbol') - related_element: PointerProperty(name='Related Element', type=bpy.types.Object) - variables: CollectionProperty(name='Variables', type=Variable) + font_size: EnumProperty( + items=[ + ("1.8", "1.8 - Small", ""), + ("2.5", "2.5 - Regular", ""), + ("3.5", "3.5 - Large", ""), + ("5.0", "5.0 - Header", ""), + ("7.0", "7.0 - Title", ""), + ], + update=refreshFontSize, + name="Font Size", + ) + symbol: EnumProperty( + items=[ + ("None", "None", ""), + ("rectangle-tag", "Rectangle Tag", ""), + ("door-tag", "Door Tag", ""), + ], + update=refreshFontSize, + name="Symbol", + ) + related_element: PointerProperty(name="Related Element", type=bpy.types.Object) + variables: CollectionProperty(name="Variables", type=Variable) class DocumentInformation(PropertyGroup): - name: StringProperty(name='Identification') - human_name: StringProperty(name='Name') - description: StringProperty(name='Description') - location: StringProperty(name='Location') - purpose: StringProperty(name='Purpose') - intended_use: StringProperty(name='Intended Use') - scope: StringProperty(name='Scope') - revision: StringProperty(name='Revision') - document_owner: StringProperty(name='Owner') - editors: StringProperty(name='Editors') - creation_time: StringProperty(name='Created On') - last_revision_time: StringProperty(name='Last Revised') - electronic_format: StringProperty(name='Format') - valid_from: StringProperty(name='Valid From') - valid_until: StringProperty(name='Valid Until') - confidentiality: EnumProperty(items=[ - ('NOTDEFINED', 'NOTDEFINED', 'Not defined.'), - ('PUBLIC', 'PUBLIC', 'Document is publicly available.'), - ('RESTRICTED', 'RESTRICTED', 'Document availability is restricted.'), - ('CONFIDENTIAL', 'CONFIDENTIAL', 'Document is confidential and its contents should not be revealed without permission.'), - ('PERSONAL', 'PERSONAL', 'Document is personal to the author.'), - ('USERDEFINED', 'USERDEFINED', 'Describe confidentiality elsewhere.') - ], name='Confidentiality') - status: EnumProperty(items=[ - ('NOTDEFINED', 'NOTDEFINED', 'Not defined'), - ('DRAFT', 'DRAFT', 'Document is a draft.'), - ('FINALDRAFT', 'FINALDRAFT', 'Document is a final draft.'), - ('FINAL', 'FINAL', 'Document is final.'), - ('REVISION', 'REVISION', 'Document has undergone revision.'), - ], name='Status') + name: StringProperty(name="Identification") + human_name: StringProperty(name="Name") + description: StringProperty(name="Description") + location: StringProperty(name="Location") + purpose: StringProperty(name="Purpose") + intended_use: StringProperty(name="Intended Use") + scope: StringProperty(name="Scope") + revision: StringProperty(name="Revision") + document_owner: StringProperty(name="Owner") + editors: StringProperty(name="Editors") + creation_time: StringProperty(name="Created On") + last_revision_time: StringProperty(name="Last Revised") + electronic_format: StringProperty(name="Format") + valid_from: StringProperty(name="Valid From") + valid_until: StringProperty(name="Valid Until") + confidentiality: EnumProperty( + items=[ + ("NOTDEFINED", "NOTDEFINED", "Not defined."), + ("PUBLIC", "PUBLIC", "Document is publicly available."), + ("RESTRICTED", "RESTRICTED", "Document availability is restricted."), + ( + "CONFIDENTIAL", + "CONFIDENTIAL", + "Document is confidential and its contents should not be revealed without permission.", + ), + ("PERSONAL", "PERSONAL", "Document is personal to the author."), + ("USERDEFINED", "USERDEFINED", "Describe confidentiality elsewhere."), + ], + name="Confidentiality", + ) + status: EnumProperty( + items=[ + ("NOTDEFINED", "NOTDEFINED", "Not defined"), + ("DRAFT", "DRAFT", "Document is a draft."), + ("FINALDRAFT", "FINALDRAFT", "Document is a final draft."), + ("FINAL", "FINAL", "Document is final."), + ("REVISION", "REVISION", "Document has undergone revision."), + ], + name="Status", + ) class DocumentReference(PropertyGroup): @@ -662,94 +721,161 @@ class DocumentReference(PropertyGroup): class ClashSource(PropertyGroup): - name: StringProperty(name='File') - selector: StringProperty(name='Selector') - mode: EnumProperty(items=[ - ('i', 'Include', 'Only the selected objects are included for clashing'), - ('e', 'Exclude', 'All objects except the selected objects are included for clashing') - ], name='Mode') + name: StringProperty(name="File") + selector: StringProperty(name="Selector") + mode: EnumProperty( + items=[ + ("i", "Include", "Only the selected objects are included for clashing"), + ("e", "Exclude", "All objects except the selected objects are included for clashing"), + ], + name="Mode", + ) class ClashSet(PropertyGroup): - name: StringProperty(name='Name') - tolerance: FloatProperty(name='Tolerance') - a: CollectionProperty(name='Group A', type=ClashSource) - b: CollectionProperty(name='Group B', type=ClashSource) + name: StringProperty(name="Name") + tolerance: FloatProperty(name="Tolerance") + a: CollectionProperty(name="Group A", type=ClashSource) + b: CollectionProperty(name="Group B", type=ClashSource) class Constraint(PropertyGroup): - name: StringProperty(name='Name') - description: StringProperty(name='Description') - constraint_grade: EnumProperty(items=[ - ('HARD', 'HARD', 'Qualifies a constraint such that it must be followed rigidly within or at the values set.'), - ('SOFT', 'SOFT', 'Qualifies a constraint such that it should be followed within or at the values set.'), - ('ADVISORY', 'ADVISORY', 'Qualifies a constraint such that it is advised that it is followed within or at the values set.'), - ('USERDEFINED', 'USERDEFINED', 'A user-defined grade indicated by a separate attribute at the referencing entity.'), - ('NOTDEFINED', 'NOTDEFINED', 'Grade has not been specified.'), - ], name='Grade') - constraint_source: StringProperty(name='Source') - user_defined_grade: StringProperty(name='Custom Grade') - objective_qualifier: EnumProperty(items=[ - ('CODECOMPLIANCE', 'CODECOMPLIANCE', 'A constraint whose objective is to ensure satisfaction of a code compliance provision.'), - ('CODEWAIVER', 'CODEWAIVER', 'A constraint whose objective is to identify an agreement that code compliance requirements (the waiver) will not be enforced.'), - ('DESIGNINTENT', 'DESIGNINTENT', 'A constraint whose objective is to ensure satisfaction of a design intent provision.'), - ('EXTERNAL', 'EXTERNAL', 'A constraint whose objective is to synchronize data with an external source such as a file'), - ('HEALTHANDSAFETY', 'HEALTHANDSAFETY', 'A constraint whose objective is to ensure satisfaction of a health and safety provision.'), - ('MERGECONFLICT', 'MERGECONFLICT', 'A constraint whose objective is to resolve a conflict such as merging data from multiple sources.'), - ('MODELVIEW', 'MODELVIEW', 'A constraint whose objective is to ensure data conforms to a model view definition.'), - ('PARAMETER', 'PARAMETER', 'A constraint whose objective is to calculate a value based on other referenced values.'), - ('REQUIREMENT', 'REQUIREMENT', 'A constraint whose objective is to ensure satisfaction of a project requirement provision.'), - ('SPECIFICATION', 'SPECIFICATION', 'A constraint whose objective is to ensure satisfaction of a specification provision.'), - ('TRIGGERCONDITION', 'TRIGGERCONDITION', 'A constraint whose objective is to indicate a limiting value beyond which the condition of an object requires a particular form of attention.'), - ('USERDEFINED', 'USERDEFINED', ''), - ('NOTDEFINED', 'NOTDEFINED', '') - ], name='Qualifier') - user_defined_qualifier: StringProperty(name='Custom Qualifier') + name: StringProperty(name="Name") + description: StringProperty(name="Description") + constraint_grade: EnumProperty( + items=[ + ( + "HARD", + "HARD", + "Qualifies a constraint such that it must be followed rigidly within or at the values set.", + ), + ("SOFT", "SOFT", "Qualifies a constraint such that it should be followed within or at the values set."), + ( + "ADVISORY", + "ADVISORY", + "Qualifies a constraint such that it is advised that it is followed within or at the values set.", + ), + ( + "USERDEFINED", + "USERDEFINED", + "A user-defined grade indicated by a separate attribute at the referencing entity.", + ), + ("NOTDEFINED", "NOTDEFINED", "Grade has not been specified."), + ], + name="Grade", + ) + constraint_source: StringProperty(name="Source") + user_defined_grade: StringProperty(name="Custom Grade") + objective_qualifier: EnumProperty( + items=[ + ( + "CODECOMPLIANCE", + "CODECOMPLIANCE", + "A constraint whose objective is to ensure satisfaction of a code compliance provision.", + ), + ( + "CODEWAIVER", + "CODEWAIVER", + "A constraint whose objective is to identify an agreement that code compliance requirements (the waiver) will not be enforced.", + ), + ( + "DESIGNINTENT", + "DESIGNINTENT", + "A constraint whose objective is to ensure satisfaction of a design intent provision.", + ), + ( + "EXTERNAL", + "EXTERNAL", + "A constraint whose objective is to synchronize data with an external source such as a file", + ), + ( + "HEALTHANDSAFETY", + "HEALTHANDSAFETY", + "A constraint whose objective is to ensure satisfaction of a health and safety provision.", + ), + ( + "MERGECONFLICT", + "MERGECONFLICT", + "A constraint whose objective is to resolve a conflict such as merging data from multiple sources.", + ), + ( + "MODELVIEW", + "MODELVIEW", + "A constraint whose objective is to ensure data conforms to a model view definition.", + ), + ( + "PARAMETER", + "PARAMETER", + "A constraint whose objective is to calculate a value based on other referenced values.", + ), + ( + "REQUIREMENT", + "REQUIREMENT", + "A constraint whose objective is to ensure satisfaction of a project requirement provision.", + ), + ( + "SPECIFICATION", + "SPECIFICATION", + "A constraint whose objective is to ensure satisfaction of a specification provision.", + ), + ( + "TRIGGERCONDITION", + "TRIGGERCONDITION", + "A constraint whose objective is to indicate a limiting value beyond which the condition of an object requires a particular form of attention.", + ), + ("USERDEFINED", "USERDEFINED", ""), + ("NOTDEFINED", "NOTDEFINED", ""), + ], + name="Qualifier", + ) + user_defined_qualifier: StringProperty(name="Custom Qualifier") class BcfTopic(PropertyGroup): - name: StringProperty(name='Name') + name: StringProperty(name="Name") class BcfTopicLabel(PropertyGroup): - name: StringProperty(name='Name') + name: StringProperty(name="Name") class BcfTopicLink(PropertyGroup): - name: StringProperty(name='Name') + name: StringProperty(name="Name") class BcfTopicFile(PropertyGroup): - name: StringProperty(name='Name') - reference: StringProperty(name='Reference') - date: StringProperty(name='Date') - is_external: BoolProperty(name='Is External') - ifc_project: StringProperty(name='IFC Project') - ifc_spatial: StringProperty(name='IFC Spatial') + name: StringProperty(name="Name") + reference: StringProperty(name="Reference") + date: StringProperty(name="Date") + is_external: BoolProperty(name="Is External") + ifc_project: StringProperty(name="IFC Project") + ifc_spatial: StringProperty(name="IFC Spatial") class BcfTopicDocumentReference(PropertyGroup): - name: StringProperty(name='Reference') - description: StringProperty(name='Description') - guid: StringProperty(name='GUID') - is_external: BoolProperty(name='Is External') + name: StringProperty(name="Reference") + description: StringProperty(name="Description") + guid: StringProperty(name="GUID") + is_external: BoolProperty(name="Is External") class BcfTopicRelatedTopic(PropertyGroup): - name: StringProperty(name='Name') - guid: StringProperty(name='GUID') + name: StringProperty(name="Name") + guid: StringProperty(name="GUID") def refreshBcfTopic(self, context): RefreshBcfTopic.refresh(context) -class RefreshBcfTopic(): + +class RefreshBcfTopic: props: None topic: None @classmethod def refresh(cls, context): import bcfplugin + global bcfviewpoints_enum cls.props = bpy.context.scene.BCFProperties @@ -773,20 +899,20 @@ class RefreshBcfTopic(): cls.props.topic_priority = cls.topic.priority cls.props.topic_stage = cls.topic.stage if cls.topic.date: - cls.props.topic_creation_date = cls.topic.date.strftime('%a %Y-%m-%d %H:%S') + cls.props.topic_creation_date = cls.topic.date.strftime("%a %Y-%m-%d %H:%S") else: - cls.props.topic_creation_date = '' + cls.props.topic_creation_date = "" cls.props.topic_creation_author = cls.topic.author if cls.topic.modDate: - cls.props.topic_modified_date = cls.topic.modDate.strftime('%a %Y-%m-%d %H:%S') + cls.props.topic_modified_date = cls.topic.modDate.strftime("%a %Y-%m-%d %H:%S") else: - cls.props.topic_modified_date = '' + cls.props.topic_modified_date = "" cls.props.topic_modified_author = cls.topic.modAuthor cls.props.topic_assigned_to = cls.topic.assignee if cls.topic.dueDate: - cls.props.topic_due_date = cls.topic.dueDate.strftime('%a %Y-%m-%d %H:%S') + cls.props.topic_due_date = cls.topic.dueDate.strftime("%a %Y-%m-%d %H:%S") else: - cls.props.topic_due_date = '' + cls.props.topic_due_date = "" cls.props.topic_description = cls.topic.description @classmethod @@ -800,13 +926,14 @@ class RefreshBcfTopic(): @classmethod def load_topic_files(cls): import bcfplugin + while len(cls.props.topic_files) > 0: cls.props.topic_files.remove(0) files = bcfplugin.getRelevantIfcFiles(cls.topic) for f in files: new = cls.props.topic_files.add() new.name = f.filename - new.date = f.time.strftime('%a %Y-%m-%d %H:%S') + new.date = f.time.strftime("%a %Y-%m-%d %H:%S") new.reference = f.reference.uri new.ifc_project = f.ifcProjectId new.ifc_spatial = f.ifcSpatialStructureElement @@ -847,6 +974,7 @@ class RefreshBcfTopic(): @classmethod def load_related_topics(cls): import bcfplugin + while len(cls.props.topic_related_topics) > 0: cls.props.topic_related_topics.remove(0) for t in cls.topic.relatedTopics: @@ -857,27 +985,29 @@ class RefreshBcfTopic(): @classmethod def load_viewpoints(cls): import bcfplugin + bcfviewpoints_enum.clear() bcf.BcfStore.viewpoints = bcfplugin.getViewpoints(cls.topic, realViewpoint=False) for i, viewpoint in enumerate(bcf.BcfStore.viewpoints): - bcfviewpoints_enum.append((str(i), 'View {}'.format(i+1), '')) + bcfviewpoints_enum.append((str(i), "View {}".format(i + 1), "")) @classmethod def load_comments(cls): import bcfplugin + bcf.BcfStore.comments = bcfplugin.getComments(cls.topic) - comments = bpy.data.texts.get('BCF Comments') + comments = bpy.data.texts.get("BCF Comments") if comments: comments.clear() else: - comments = bpy.data.texts.new('BCF Comments') + comments = bpy.data.texts.new("BCF Comments") for i, comment in enumerate(bcf.BcfStore.comments): - comments.write('# Comment {} - {}\n'.format(i + 1, comment[1].xmlId)) - comments.write('# From: {} on {}\n'.format(comment[1].author, comment[1].date)) + comments.write("# Comment {} - {}\n".format(i + 1, comment[1].xmlId)) + comments.write("# From: {} on {}\n".format(comment[1].author, comment[1].date)) if comment[1].modDate: - comments.write('# Modified by {} on {}\n'.format(comment[1].modAuthor, comment[1].modDate)) + comments.write("# Modified by {} on {}\n".format(comment[1].modAuthor, comment[1].modDate)) comments.write(comment[1].comment) - comments.write('\n\n-----\n\n') + comments.write("\n\n-----\n\n") def getBcfViewpoints(self, context): @@ -889,148 +1019,188 @@ class PropertySetTemplate(PropertyGroup): global_id: StringProperty(name="Global ID") name: StringProperty(name="Name") description: StringProperty(name="Description") - template_type: EnumProperty(items=[ - ('PSET_TYPEDRIVENONLY', 'Pset - IfcTypeObject', 'The property sets defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcTypeObject.'), - ('PSET_TYPEDRIVENOVERRIDE', 'Pset - IfcTypeObject - Override', 'The property sets defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcTypeObject.'), - ('PSET_OCCURRENCEDRIVEN', 'Pset - IfcObject', 'The property sets defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcObject.'), - ('PSET_PERFORMANCEDRIVEN', 'Pset - IfcPerformanceHistory', 'The property sets defined by this IfcPropertySetTemplate can only be assigned to IfcPerformanceHistory.'), - ('QTO_TYPEDRIVENONLY', 'Qto - IfcTypeObject', 'The element quantity defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcTypeObject.'), - ('QTO_TYPEDRIVENOVERRIDE', 'Qto - IfcTypeObject - Override', 'The element quantity defined by this IfcPropertySetTemplate can be assigned to subtypes of IfcTypeObject and can be overridden by an element quantity with same name at subtypes of IfcObject.'), - ('QTO_OCCURRENCEDRIVEN', 'Qto - IfcObject', 'The element quantity defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcObject.'), - ('NOTDEFINED', 'Not defined', 'No restriction provided, the property sets defined by this IfcPropertySetTemplate can be assigned to any entity, if not otherwise restricted by the ApplicableEntity attribute.') - ], name="Template Type") + template_type: EnumProperty( + items=[ + ( + "PSET_TYPEDRIVENONLY", + "Pset - IfcTypeObject", + "The property sets defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcTypeObject.", + ), + ( + "PSET_TYPEDRIVENOVERRIDE", + "Pset - IfcTypeObject - Override", + "The property sets defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcTypeObject.", + ), + ( + "PSET_OCCURRENCEDRIVEN", + "Pset - IfcObject", + "The property sets defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcObject.", + ), + ( + "PSET_PERFORMANCEDRIVEN", + "Pset - IfcPerformanceHistory", + "The property sets defined by this IfcPropertySetTemplate can only be assigned to IfcPerformanceHistory.", + ), + ( + "QTO_TYPEDRIVENONLY", + "Qto - IfcTypeObject", + "The element quantity defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcTypeObject.", + ), + ( + "QTO_TYPEDRIVENOVERRIDE", + "Qto - IfcTypeObject - Override", + "The element quantity defined by this IfcPropertySetTemplate can be assigned to subtypes of IfcTypeObject and can be overridden by an element quantity with same name at subtypes of IfcObject.", + ), + ( + "QTO_OCCURRENCEDRIVEN", + "Qto - IfcObject", + "The element quantity defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcObject.", + ), + ( + "NOTDEFINED", + "Not defined", + "No restriction provided, the property sets defined by this IfcPropertySetTemplate can be assigned to any entity, if not otherwise restricted by the ApplicableEntity attribute.", + ), + ], + name="Template Type", + ) applicable_entity: StringProperty(name="Applicable Entity") class PropertyTemplate(PropertyGroup): - global_id: StringProperty(name='Global ID') - name: StringProperty(name='Name') - description: StringProperty(name='Description') - primary_measure_type: EnumProperty(items=[ - (x, x, '') for x in [ - 'IfcInteger', - 'IfcReal', - 'IfcBoolean', - 'IfcIdentifier', - 'IfcText', - 'IfcLabel', - 'IfcLogical', - 'IfcDateTime', - 'IfcDate', - 'IfcTime', - 'IfcDuration', - 'IfcTimeStamp', - - 'IfcPositiveInteger', - 'IfcBinary', - 'IfcVolumeMeasure', - 'IfcTimeMeasure', - 'IfcThermodynamicTemperatureMeasure', - 'IfcSolidAngleMeasure', - 'IfcPositiveRatioMeasure', - 'IfcRatioMeasure', - 'IfcPositivePlaneAngleMeasure', - 'IfcPlaneAngleMeasure', - 'IfcParameterValue', - 'IfcNumericMeasure', - 'IfcMassMeasure', - 'IfcPositiveLengthMeasure', - 'IfcLengthMeasure', - 'IfcElectricCurrentMeasure', - 'IfcDescriptiveMeasure', - 'IfcCountMeasure', - 'IfcContextDependentMeasure', - 'IfcAreaMeasure', - 'IfcAmountOfSubstanceMeasure', - 'IfcLuminousIntensityMeasure', - 'IfcNormalisedRatioMeasure', - 'IfcComplexNumber', - 'IfcNonNegativeLengthMeasure', - - 'IfcAbsorbedDoseMeasure', - 'IfcAccelerationMeasure', - 'IfcAngularVelocityMeasure', - 'IfcAreaDensityMeasure', - 'IfcCompoundPlaneAngleMeasure', - 'IfcCurvatureMeasure', - 'IfcDoseEquivalentMeasure', - 'IfcDynamicViscosityMeasure', - 'IfcElectricCapacitanceMeasure', - 'IfcElectricChargeMeasure', - 'IfcElectricConductanceMeasure', - 'IfcElectricResistanceMeasure', - 'IfcElectricVoltageMeasure', - 'IfcEnergyMeasure', - 'IfcForceMeasure', - 'IfcFrequencyMeasure', - 'IfcHeatFluxDensityMeasure', - 'IfcHeatingValueMeasure', - 'IfcIlluminanceMeasure', - 'IfcInductanceMeasure', - 'IfcIntegerCountRateMeasure', - 'IfcIonConcentrationMeasure', - 'IfcIsothermalMoistureCapacityMeasure', - 'IfcKinematicViscosityMeasure', - 'IfcLinearForceMeasure', - 'IfcLinearMomentMeasure', - 'IfcLinearStiffnessMeasure', - 'IfcLinearVelocityMeasure', - 'IfcLuminousFluxMeasure', - 'IfcLuminousIntensityDistributionMeasure', - 'IfcMagneticFluxDensityMeasure', - 'IfcMagneticFluxMeasure', - 'IfcMassDensityMeasure', - 'IfcMassFlowRateMeasure', - 'IfcMassPerLengthMeasure', - 'IfcModulusOfElasticityMeasure', - 'IfcModulusOfLinearSubgradeReactionMeasure', - 'IfcModulusOfRotationalSubgradeReactionMeasure', - 'IfcModulusOfSubgradeReactionMeasure', - 'IfcMoistureDiffusivityMeasure', - 'IfcMolecularWeightMeasure', - 'IfcMomentOfInertiaMeasure', - 'IfcMonetaryMeasure', - 'IfcPHMeasure', - 'IfcPlanarForceMeasure', - 'IfcPowerMeasure', - 'IfcPressureMeasure', - 'IfcRadioActivityMeasure', - 'IfcRotationalFrequencyMeasure', - 'IfcRotationalMassMeasure', - 'IfcRotationalStiffnessMeasure', - 'IfcSectionModulusMeasure', - 'IfcSectionalAreaIntegralMeasure', - 'IfcShearModulusMeasure', - 'IfcSoundPowerLevelMeasure', - 'IfcSoundPowerMeasure', - 'IfcSoundPressureLevelMeasure', - 'IfcSoundPressureMeasure', - 'IfcSpecificHeatCapacityMeasure', - 'IfcTemperatureGradientMeasure', - 'IfcTemperatureRateOfChangeMeasure', - 'IfcThermalAdmittanceMeasure', - 'IfcThermalConductivityMeasure', - 'IfcThermalExpansionCoefficientMeasure', - 'IfcThermalResistanceMeasure', - 'IfcThermalTransmittanceMeasure', - 'IfcTorqueMeasure', - 'IfcVaporPermeabilityMeasure', - 'IfcVolumetricFlowRateMeasure', - 'IfcWarpingConstantMeasure', - 'IfcWarpingMomentMeasure', + global_id: StringProperty(name="Global ID") + name: StringProperty(name="Name") + description: StringProperty(name="Description") + primary_measure_type: EnumProperty( + items=[ + (x, x, "") + for x in [ + "IfcInteger", + "IfcReal", + "IfcBoolean", + "IfcIdentifier", + "IfcText", + "IfcLabel", + "IfcLogical", + "IfcDateTime", + "IfcDate", + "IfcTime", + "IfcDuration", + "IfcTimeStamp", + "IfcPositiveInteger", + "IfcBinary", + "IfcVolumeMeasure", + "IfcTimeMeasure", + "IfcThermodynamicTemperatureMeasure", + "IfcSolidAngleMeasure", + "IfcPositiveRatioMeasure", + "IfcRatioMeasure", + "IfcPositivePlaneAngleMeasure", + "IfcPlaneAngleMeasure", + "IfcParameterValue", + "IfcNumericMeasure", + "IfcMassMeasure", + "IfcPositiveLengthMeasure", + "IfcLengthMeasure", + "IfcElectricCurrentMeasure", + "IfcDescriptiveMeasure", + "IfcCountMeasure", + "IfcContextDependentMeasure", + "IfcAreaMeasure", + "IfcAmountOfSubstanceMeasure", + "IfcLuminousIntensityMeasure", + "IfcNormalisedRatioMeasure", + "IfcComplexNumber", + "IfcNonNegativeLengthMeasure", + "IfcAbsorbedDoseMeasure", + "IfcAccelerationMeasure", + "IfcAngularVelocityMeasure", + "IfcAreaDensityMeasure", + "IfcCompoundPlaneAngleMeasure", + "IfcCurvatureMeasure", + "IfcDoseEquivalentMeasure", + "IfcDynamicViscosityMeasure", + "IfcElectricCapacitanceMeasure", + "IfcElectricChargeMeasure", + "IfcElectricConductanceMeasure", + "IfcElectricResistanceMeasure", + "IfcElectricVoltageMeasure", + "IfcEnergyMeasure", + "IfcForceMeasure", + "IfcFrequencyMeasure", + "IfcHeatFluxDensityMeasure", + "IfcHeatingValueMeasure", + "IfcIlluminanceMeasure", + "IfcInductanceMeasure", + "IfcIntegerCountRateMeasure", + "IfcIonConcentrationMeasure", + "IfcIsothermalMoistureCapacityMeasure", + "IfcKinematicViscosityMeasure", + "IfcLinearForceMeasure", + "IfcLinearMomentMeasure", + "IfcLinearStiffnessMeasure", + "IfcLinearVelocityMeasure", + "IfcLuminousFluxMeasure", + "IfcLuminousIntensityDistributionMeasure", + "IfcMagneticFluxDensityMeasure", + "IfcMagneticFluxMeasure", + "IfcMassDensityMeasure", + "IfcMassFlowRateMeasure", + "IfcMassPerLengthMeasure", + "IfcModulusOfElasticityMeasure", + "IfcModulusOfLinearSubgradeReactionMeasure", + "IfcModulusOfRotationalSubgradeReactionMeasure", + "IfcModulusOfSubgradeReactionMeasure", + "IfcMoistureDiffusivityMeasure", + "IfcMolecularWeightMeasure", + "IfcMomentOfInertiaMeasure", + "IfcMonetaryMeasure", + "IfcPHMeasure", + "IfcPlanarForceMeasure", + "IfcPowerMeasure", + "IfcPressureMeasure", + "IfcRadioActivityMeasure", + "IfcRotationalFrequencyMeasure", + "IfcRotationalMassMeasure", + "IfcRotationalStiffnessMeasure", + "IfcSectionModulusMeasure", + "IfcSectionalAreaIntegralMeasure", + "IfcShearModulusMeasure", + "IfcSoundPowerLevelMeasure", + "IfcSoundPowerMeasure", + "IfcSoundPressureLevelMeasure", + "IfcSoundPressureMeasure", + "IfcSpecificHeatCapacityMeasure", + "IfcTemperatureGradientMeasure", + "IfcTemperatureRateOfChangeMeasure", + "IfcThermalAdmittanceMeasure", + "IfcThermalConductivityMeasure", + "IfcThermalExpansionCoefficientMeasure", + "IfcThermalResistanceMeasure", + "IfcThermalTransmittanceMeasure", + "IfcTorqueMeasure", + "IfcVaporPermeabilityMeasure", + "IfcVolumetricFlowRateMeasure", + "IfcWarpingConstantMeasure", + "IfcWarpingMomentMeasure", ] - ], name='Primary Measure Type') + ], + name="Primary Measure Type", + ) class Address(PropertyGroup): - name: StringProperty(name="Name", default='IfcPostalAddress') # Stores IfcPostalAddress or IfcTelecomAddress - purpose: EnumProperty(items=[ - ('OFFICE', 'OFFICE', 'An office address.'), - ('SITE', 'SITE', 'A site address.'), - ('HOME', 'HOME', 'A home address.'), - ('DISTRIBUTIONPOINT', 'DISTRIBUTIONPOINT', 'A postal distribution point address.'), - ('USERDEFINED', 'USERDEFINED', 'A user defined address type to be provided.'), - ], name='Purpose') + name: StringProperty(name="Name", default="IfcPostalAddress") # Stores IfcPostalAddress or IfcTelecomAddress + purpose: EnumProperty( + items=[ + ("OFFICE", "OFFICE", "An office address."), + ("SITE", "SITE", "A site address."), + ("HOME", "HOME", "A home address."), + ("DISTRIBUTIONPOINT", "DISTRIBUTIONPOINT", "A postal distribution point address."), + ("USERDEFINED", "USERDEFINED", "A user defined address type to be provided."), + ], + name="Purpose", + ) description: StringProperty(name="Description") user_defined_purpose: StringProperty(name="Custom Purpose") @@ -1051,31 +1221,34 @@ class Address(PropertyGroup): class Role(PropertyGroup): - name: EnumProperty(items=[ - ('SUPPLIER', 'SUPPLIER', ''), - ('MANUFACTURER', 'MANUFACTURER', ''), - ('CONTRACTOR', 'CONTRACTOR', ''), - ('SUBCONTRACTOR', 'SUBCONTRACTOR', ''), - ('ARCHITECT', 'ARCHITECT', ''), - ('STRUCTURALENGINEER', 'STRUCTURALENGINEER', ''), - ('COSTENGINEER', 'COSTENGINEER', ''), - ('CLIENT', 'CLIENT', ''), - ('BUILDINGOWNER', 'BUILDINGOWNER', ''), - ('BUILDINGOPERATOR', 'BUILDINGOPERATOR', ''), - ('MECHANICALENGINEER', 'MECHANICALENGINEER', ''), - ('ELECTRICALENGINEER', 'ELECTRICALENGINEER', ''), - ('PROJECTMANAGER', 'PROJECTMANAGER', ''), - ('FACILITIESMANAGER', 'FACILITIESMANAGER', ''), - ('CIVILENGINEER', 'CIVILENGINEER', ''), - ('COMMISSIONINGENGINEER', 'COMMISSIONINGENGINEER', ''), - ('ENGINEER', 'ENGINEER', ''), - ('OWNER', 'OWNER', ''), - ('CONSULTANT', 'CONSULTANT', ''), - ('CONSTRUCTIONMANAGER', 'CONSTRUCTIONMANAGER', ''), - ('FIELDCONSTRUCTIONMANAGER', 'FIELDCONSTRUCTIONMANAGER', ''), - ('RESELLER', 'RESELLER', ''), - ('USERDEFINED', 'USERDEFINED', ''), - ], name='Name') + name: EnumProperty( + items=[ + ("SUPPLIER", "SUPPLIER", ""), + ("MANUFACTURER", "MANUFACTURER", ""), + ("CONTRACTOR", "CONTRACTOR", ""), + ("SUBCONTRACTOR", "SUBCONTRACTOR", ""), + ("ARCHITECT", "ARCHITECT", ""), + ("STRUCTURALENGINEER", "STRUCTURALENGINEER", ""), + ("COSTENGINEER", "COSTENGINEER", ""), + ("CLIENT", "CLIENT", ""), + ("BUILDINGOWNER", "BUILDINGOWNER", ""), + ("BUILDINGOPERATOR", "BUILDINGOPERATOR", ""), + ("MECHANICALENGINEER", "MECHANICALENGINEER", ""), + ("ELECTRICALENGINEER", "ELECTRICALENGINEER", ""), + ("PROJECTMANAGER", "PROJECTMANAGER", ""), + ("FACILITIESMANAGER", "FACILITIESMANAGER", ""), + ("CIVILENGINEER", "CIVILENGINEER", ""), + ("COMMISSIONINGENGINEER", "COMMISSIONINGENGINEER", ""), + ("ENGINEER", "ENGINEER", ""), + ("OWNER", "OWNER", ""), + ("CONSULTANT", "CONSULTANT", ""), + ("CONSTRUCTIONMANAGER", "CONSTRUCTIONMANAGER", ""), + ("FIELDCONSTRUCTIONMANAGER", "FIELDCONSTRUCTIONMANAGER", ""), + ("RESELLER", "RESELLER", ""), + ("USERDEFINED", "USERDEFINED", ""), + ], + name="Name", + ) user_defined_role: StringProperty(name="Custom Role") description: StringProperty(name="Description") @@ -1132,9 +1305,9 @@ class ClassificationView(PropertyGroup): def root(self): data = self.raw_data for crumb in self.crumbs: - data = data['children'].get(crumb.name) + data = data["children"].get(crumb.name) if not data: - raise TypeError('Cannot resolve crumb path') + raise TypeError("Cannot resolve crumb path") return data @root.setter @@ -1142,57 +1315,54 @@ class ClassificationView(PropertyGroup): if rt == None: self.crumbs.clear() self.children.clear() - elif rt == '': + elif rt == "": if self.crumbs: - self.crumbs.remove(len(self.crumbs)-1) + self.crumbs.remove(len(self.crumbs) - 1) self.children.clear() - for child in self.root['children'].keys(): + for child in self.root["children"].keys(): self.children.add().name = child else: data = self.root - if rt in data['children'].keys(): + if rt in data["children"].keys(): self.crumbs.add().name = rt self.children.clear() - for child in data['children'][rt]['children'].keys(): + for child in data["children"][rt]["children"].keys(): self.children.add().name = child def draw_stub(self, context, layout): if not self.children: - op = layout.operator('bim.change_classification_level', text="@Toplevel") + op = layout.operator("bim.change_classification_level", text="@Toplevel") else: - op = layout.operator('bim.change_classification_level', text=self.root['name']) - op.path_sid = "%r"%self.id_data + op = layout.operator("bim.change_classification_level", text=self.root["name"]) + op.path_sid = "%r" % self.id_data op.path_lst = self.path_from_id() - op.path_itm = '' - layout.template_list('BIM_UL_classifications', - self.path_from_id(), self, 'children', self, 'active_index') + op.path_itm = "" + layout.template_list("BIM_UL_classifications", self.path_from_id(), self, "children", self, "active_index") # Monkey-patched, just to keep registration in one block -ClassificationView.__annotations__['crumbs'] = \ - bpy.props.CollectionProperty(type=StrProperty) -ClassificationView.__annotations__['children'] = \ - bpy.props.CollectionProperty(type=StrProperty) +ClassificationView.__annotations__["crumbs"] = bpy.props.CollectionProperty(type=StrProperty) +ClassificationView.__annotations__["children"] = bpy.props.CollectionProperty(type=StrProperty) class BIMProperties(PropertyGroup): - schema_dir: StringProperty(default=os.path.join(cwd ,'schema') + os.path.sep, name="Schema Directory") - data_dir: StringProperty(default=os.path.join(cwd, 'data') + os.path.sep, name="Data Directory") + schema_dir: StringProperty(default=os.path.join(cwd, "schema") + os.path.sep, name="Schema Directory") + data_dir: StringProperty(default=os.path.join(cwd, "data") + os.path.sep, name="Data Directory") ifc_file: StringProperty(name="IFC File") ifc_cache: StringProperty(name="IFC Cache") audit_ifc_class: EnumProperty(items=getIfcClasses, name="Audit Class") ifc_product: EnumProperty(items=getIfcProducts, name="Products", update=refreshClasses) ifc_class: EnumProperty(items=getIfcClasses, name="Class", update=refreshPredefinedTypes) - ifc_predefined_type: EnumProperty( - items = getIfcPredefinedTypes, - name="Predefined Type", default=None) + ifc_predefined_type: EnumProperty(items=getIfcPredefinedTypes, name="Predefined Type", default=None) ifc_userdefined_type: StringProperty(name="Userdefined Type") - export_schema: EnumProperty(items=[('IFC4', 'IFC4', ''), ('IFC2X3', 'IFC2X3', '')], name='IFC Schema') - export_json_version: EnumProperty(items=[('4', '4', ''), ('5a', '5a', '')], name='IFC JSON Version') + export_schema: EnumProperty(items=[("IFC4", "IFC4", ""), ("IFC2X3", "IFC2X3", "")], name="IFC Schema") + export_json_version: EnumProperty(items=[("4", "4", ""), ("5a", "5a", "")], name="IFC JSON Version") export_json_compact: BoolProperty(name="Export Compact IFCJSON", default=False) export_has_representations: BoolProperty(name="Export Representations", default=True) export_should_guess_quantities: BoolProperty(name="Export with Guessed Quantities", default=False) - export_should_use_presentation_style_assignment: BoolProperty(name="Export with Presentation Style Assignment", default=False) + export_should_use_presentation_style_assignment: BoolProperty( + name="Export with Presentation Style Assignment", default=False + ) export_should_force_faceted_brep: BoolProperty(name="Export with Faceted Breps", default=False) import_should_ignore_site_coordinates: BoolProperty(name="Import Ignoring Site Coordinates", default=False) import_should_ignore_building_coordinates: BoolProperty(name="Import Ignoring Building Coordinates", default=False) @@ -1202,7 +1372,9 @@ class BIMProperties(PropertyGroup): import_should_import_opening_elements: BoolProperty(name="Import Opening Elements", default=False) import_should_import_spaces: BoolProperty(name="Import Spaces", default=False) import_should_auto_set_workarounds: BoolProperty(name="Automatically Set Vendor Workarounds", default=True) - import_should_treat_styled_item_as_material: BoolProperty(name="Import Treating Styled Item as Material", default=False) + import_should_treat_styled_item_as_material: BoolProperty( + name="Import Treating Styled Item as Material", default=False + ) import_should_use_legacy: BoolProperty(name="Import with Legacy Importer", default=False) import_should_import_native: BoolProperty(name="Import Native Representations", default=False) import_export_should_roundtrip_native: BoolProperty(name="Roundtrip Native Representations", default=False) @@ -1218,14 +1390,14 @@ class BIMProperties(PropertyGroup): import_angular_tolerance: FloatProperty(name="Import Angular Tolerance", default=0.5) import_should_allow_non_element_aggregates: BoolProperty(name="Import Non-Element Aggregates", default=False) import_should_offset_model: BoolProperty(name="Import and Offset Model", default=False) - import_model_offset_coordinates: StringProperty(name="Model Offset Coordinates", default='0,0,0') + import_model_offset_coordinates: StringProperty(name="Model Offset Coordinates", default="0,0,0") qa_reject_element_reason: StringProperty(name="Element Rejection Reason") person: EnumProperty(items=getPersons, name="Person") organisation: EnumProperty(items=getOrganisations, name="Organisation") people: CollectionProperty(name="People", type=Person) organisations: CollectionProperty(name="Organisations", type=Organisation) - active_person_index: IntProperty(name='Active Person Index') - active_organisation_index: IntProperty(name='Active Organisation Index') + active_person_index: IntProperty(name="Active Person Index") + active_organisation_index: IntProperty(name="Active Organisation Index") has_georeferencing: BoolProperty(name="Has Georeferencing", default=False) has_library: BoolProperty(name="Has Project Library", default=False) search_regex: BoolProperty(name="Search With Regex", default=False) @@ -1236,17 +1408,17 @@ class BIMProperties(PropertyGroup): search_pset_name: StringProperty(name="Search Pset Name") search_prop_name: StringProperty(name="Search Prop Name") search_pset_value: StringProperty(name="Search Pset Value") - features_dir: StringProperty(default='', name="Features Directory", update=refreshFeaturesFiles) + features_dir: StringProperty(default="", name="Features Directory", update=refreshFeaturesFiles) features_file: EnumProperty(items=getFeaturesFiles, name="Features File", update=refreshScenarios) scenario: EnumProperty(items=getScenarios, name="Scenario") - cobie_ifc_file: StringProperty(default='', name="COBie IFC File") - cobie_types: StringProperty(default='.COBieType', name="COBie Types") - cobie_components: StringProperty(default='.COBie', name="COBie Components") - cobie_json_file: StringProperty(default='', name="COBie JSON File") - diff_json_file: StringProperty(default='', name="Diff JSON File") - diff_old_file: StringProperty(default='', name="Diff Old IFC File") - diff_new_file: StringProperty(default='', name="Diff New IFC File") - diff_relationships: StringProperty(default='', name="Diff Relationships") + cobie_ifc_file: StringProperty(default="", name="COBie IFC File") + cobie_types: StringProperty(default=".COBieType", name="COBie Types") + cobie_components: StringProperty(default=".COBie", name="COBie Components") + cobie_json_file: StringProperty(default="", name="COBie JSON File") + diff_json_file: StringProperty(default="", name="Diff JSON File") + diff_old_file: StringProperty(default="", name="Diff Old IFC File") + diff_new_file: StringProperty(default="", name="Diff New IFC File") + diff_relationships: StringProperty(default="", name="Diff Relationships") aggregate_class: EnumProperty(items=getIfcClasses, name="Aggregate Class") aggregate_name: StringProperty(name="Aggregate Name") classification: EnumProperty(items=getClassifications, name="Classification", update=refreshReferences) @@ -1254,99 +1426,117 @@ class BIMProperties(PropertyGroup): classifications: CollectionProperty(name="Classifications", type=Classification) has_model_context: BoolProperty(name="Has Model Context", default=True) has_plan_context: BoolProperty(name="Has Plan Context", default=True) - model_subcontexts: CollectionProperty(name='Model Subcontexts', type=Subcontext) - plan_subcontexts: CollectionProperty(name='Plan Subcontexts', type=Subcontext) - available_contexts: EnumProperty(items=[('Model', 'Model', ''), ('Plan', 'Plan', '')], name="Available Contexts") + model_subcontexts: CollectionProperty(name="Model Subcontexts", type=Subcontext) + plan_subcontexts: CollectionProperty(name="Plan Subcontexts", type=Subcontext) + available_contexts: EnumProperty(items=[("Model", "Model", ""), ("Plan", "Plan", "")], name="Available Contexts") available_subcontexts: EnumProperty(items=getSubcontexts, name="Available Subcontexts") available_target_views: EnumProperty(items=getTargetViews, name="Available Target Views") classification_references: PointerProperty(type=ClassificationView) - pset_template_files: EnumProperty(items=getPsetTemplateFiles, name="Pset Template Files", update=refreshPropertySetTemplates) + pset_template_files: EnumProperty( + items=getPsetTemplateFiles, name="Pset Template Files", update=refreshPropertySetTemplates + ) property_set_templates: EnumProperty(items=getPropertySetTemplates, name="Pset Template Files") active_property_set_template: PointerProperty(type=PropertySetTemplate) - property_templates: CollectionProperty(name='Property Templates', type=PropertyTemplate) + property_templates: CollectionProperty(name="Property Templates", type=PropertyTemplate) should_section_selected_objects: BoolProperty(name="Section Selected Objects", default=False) - section_plane_colour: FloatVectorProperty(name='Temporary Section Cutaway Colour', subtype='COLOR', default=(1, 0, 0), min=0.0, max=1.0) - ifc_import_filter: EnumProperty(items=[ - ('NONE', 'None', ''), - ('WHITELIST', 'Whitelist', ''), - ('BLACKLIST', 'Blacklist', ''), - ], name='Import Filter') - ifc_selector: StringProperty(default='', name='IFC Selector') - csv_attributes: CollectionProperty(name='CSV Attributes', type=StrProperty) - document_information: CollectionProperty(name='Document Information', type=DocumentInformation) - active_document_information_index: IntProperty(name='Active Document Information Index') - document_references: CollectionProperty(name='Document References', type=DocumentReference) - active_document_reference_index: IntProperty(name='Active Document Reference Index') - clash_sets: CollectionProperty(name='Clash Sets', type=ClashSet) - active_clash_set_index: IntProperty(name='Active Clash Set Index') - constraints: CollectionProperty(name='Constraints', type=Constraint) - active_constraint_index: IntProperty(name='Active Constraint Index') + section_plane_colour: FloatVectorProperty( + name="Temporary Section Cutaway Colour", subtype="COLOR", default=(1, 0, 0), min=0.0, max=1.0 + ) + ifc_import_filter: EnumProperty( + items=[ + ("NONE", "None", ""), + ("WHITELIST", "Whitelist", ""), + ("BLACKLIST", "Blacklist", ""), + ], + name="Import Filter", + ) + ifc_selector: StringProperty(default="", name="IFC Selector") + csv_attributes: CollectionProperty(name="CSV Attributes", type=StrProperty) + document_information: CollectionProperty(name="Document Information", type=DocumentInformation) + active_document_information_index: IntProperty(name="Active Document Information Index") + document_references: CollectionProperty(name="Document References", type=DocumentReference) + active_document_reference_index: IntProperty(name="Active Document Reference Index") + clash_sets: CollectionProperty(name="Clash Sets", type=ClashSet) + active_clash_set_index: IntProperty(name="Active Clash Set Index") + constraints: CollectionProperty(name="Constraints", type=Constraint) + active_constraint_index: IntProperty(name="Active Constraint Index") ifc_patch_recipes: EnumProperty(items=getIfcPatchRecipes, name="Recipes") - ifc_patch_input: StringProperty(default='', name='IFC Patch Input IFC') - ifc_patch_output: StringProperty(default='', name='IFC Patch Output IFC') - ifc_patch_args: StringProperty(default='', name='Arguments') - qto_result: StringProperty(default='', name='Qto Result') - area_unit: EnumProperty(items=[ - ('square centimeters', 'square centimeters', ''), - ('square feet', 'square feet', ''), - ('square inches', 'square inches', ''), - ('square kilometers', 'square kilometers', ''), - ('square meters', 'square meters', ''), - ('square miles', 'square miles', ''), - ('square millimeters', 'square millimeters', ''), - ('square yards', 'square yards', ''), - ], name='IFC Area Unit') - volume_unit: EnumProperty(items=[ - ('cubic centimeters', 'cubic centimeters', ''), - ('cubic feet', 'cubic feet', ''), - ('cubic inches', 'cubic inches', ''), - ('cubic meters', 'cubic meters', ''), - ('cubic millimeters', 'cubic millimeters', ''), - ('cubic yards', 'cubic yards', ''), - ], name='IFC Volume Unit') - metric_precision: FloatProperty(default=0, name='Drawing Metric Precision') - imperial_precision: EnumProperty(items=[ - ('NONE', 'No rounding', ''), - ('1', 'Nearest 1"', ''), - ('1/2', 'Nearest 1/2"', ''), - ('1/4', 'Nearest 1/4"', ''), - ('1/8', 'Nearest 1/8"', ''), - ('1/16', 'Nearest 1/16"', ''), - ('1/32', 'Nearest 1/32"', ''), - ('1/64', 'Nearest 1/64"', ''), - ('1/128', 'Nearest 1/128"', ''), - ('1/256', 'Nearest 1/256"', ''), - ], name='Drawing Imperial Precision') - override_colour: FloatVectorProperty(name='Override Colour', subtype='COLOR', default=(1, 0, 0, 1), min=0.0, max=1.0, size=4) + ifc_patch_input: StringProperty(default="", name="IFC Patch Input IFC") + ifc_patch_output: StringProperty(default="", name="IFC Patch Output IFC") + ifc_patch_args: StringProperty(default="", name="Arguments") + qto_result: StringProperty(default="", name="Qto Result") + area_unit: EnumProperty( + items=[ + ("square centimeters", "square centimeters", ""), + ("square feet", "square feet", ""), + ("square inches", "square inches", ""), + ("square kilometers", "square kilometers", ""), + ("square meters", "square meters", ""), + ("square miles", "square miles", ""), + ("square millimeters", "square millimeters", ""), + ("square yards", "square yards", ""), + ], + name="IFC Area Unit", + ) + volume_unit: EnumProperty( + items=[ + ("cubic centimeters", "cubic centimeters", ""), + ("cubic feet", "cubic feet", ""), + ("cubic inches", "cubic inches", ""), + ("cubic meters", "cubic meters", ""), + ("cubic millimeters", "cubic millimeters", ""), + ("cubic yards", "cubic yards", ""), + ], + name="IFC Volume Unit", + ) + metric_precision: FloatProperty(default=0, name="Drawing Metric Precision") + imperial_precision: EnumProperty( + items=[ + ("NONE", "No rounding", ""), + ("1", 'Nearest 1"', ""), + ("1/2", 'Nearest 1/2"', ""), + ("1/4", 'Nearest 1/4"', ""), + ("1/8", 'Nearest 1/8"', ""), + ("1/16", 'Nearest 1/16"', ""), + ("1/32", 'Nearest 1/32"', ""), + ("1/64", 'Nearest 1/64"', ""), + ("1/128", 'Nearest 1/128"', ""), + ("1/256", 'Nearest 1/256"', ""), + ], + name="Drawing Imperial Precision", + ) + override_colour: FloatVectorProperty( + name="Override Colour", subtype="COLOR", default=(1, 0, 0, 1), min=0.0, max=1.0, size=4 + ) class BCFProperties(PropertyGroup): - bcf_file: StringProperty(default='', name='BCF File') - topics: CollectionProperty(name='BCF Topics', type=BcfTopic) - active_topic_index: IntProperty(name='Active BCF Topic Index', update=refreshBcfTopic) - viewpoints: EnumProperty(items=getBcfViewpoints, name='BCF Viewpoints') - topic_guid: StringProperty(default='', name='Topic GUID') - topic_type: StringProperty(default='', name='Topic Type') - topic_status: StringProperty(default='', name='Topic Status') - topic_priority: StringProperty(default='', name='Topic Priority') - topic_stage: StringProperty(default='', name='Topic Stage') - topic_creation_date: StringProperty(default='', name='Topic Date') - topic_creation_author: StringProperty(default='', name='Topic Author') - topic_modified_date: StringProperty(default='', name='Topic Modified Date') - topic_modified_author: StringProperty(default='', name='Topic Modified By') - topic_assigned_to: StringProperty(default='', name='Topic Assigned To') - topic_due_date: StringProperty(default='', name='Topic Due Date') - topic_description: StringProperty(default='', name='Topic Description') - topic_labels: CollectionProperty(name='BCF Topic Labels', type=BcfTopicLabel) - topic_files: CollectionProperty(name='BCF Topic Files', type=BcfTopicFile) - topic_links: CollectionProperty(name='BCF Topic Links', type=BcfTopicLink) - topic_has_snippet: BoolProperty(name='BCF Topic Has Snippet', default=False) - topic_snippet_reference: StringProperty(name='BIM Snippet Reference') - topic_snippet_schema: StringProperty(name='BIM Snippet Schema') - topic_snippet_type: StringProperty(name='BIM Snippet Type') - topic_snippet_is_external: BoolProperty(name='Is BIM Snippet External') - topic_document_references: CollectionProperty(name='BCF Topic Document References', type=BcfTopicDocumentReference) - topic_related_topics: CollectionProperty(name='BCF Topic Related Topics', type=BcfTopicRelatedTopic) + bcf_file: StringProperty(default="", name="BCF File") + topics: CollectionProperty(name="BCF Topics", type=BcfTopic) + active_topic_index: IntProperty(name="Active BCF Topic Index", update=refreshBcfTopic) + viewpoints: EnumProperty(items=getBcfViewpoints, name="BCF Viewpoints") + topic_guid: StringProperty(default="", name="Topic GUID") + topic_type: StringProperty(default="", name="Topic Type") + topic_status: StringProperty(default="", name="Topic Status") + topic_priority: StringProperty(default="", name="Topic Priority") + topic_stage: StringProperty(default="", name="Topic Stage") + topic_creation_date: StringProperty(default="", name="Topic Date") + topic_creation_author: StringProperty(default="", name="Topic Author") + topic_modified_date: StringProperty(default="", name="Topic Modified Date") + topic_modified_author: StringProperty(default="", name="Topic Modified By") + topic_assigned_to: StringProperty(default="", name="Topic Assigned To") + topic_due_date: StringProperty(default="", name="Topic Due Date") + topic_description: StringProperty(default="", name="Topic Description") + topic_labels: CollectionProperty(name="BCF Topic Labels", type=BcfTopicLabel) + topic_files: CollectionProperty(name="BCF Topic Files", type=BcfTopicFile) + topic_links: CollectionProperty(name="BCF Topic Links", type=BcfTopicLink) + topic_has_snippet: BoolProperty(name="BCF Topic Has Snippet", default=False) + topic_snippet_reference: StringProperty(name="BIM Snippet Reference") + topic_snippet_schema: StringProperty(name="BIM Snippet Schema") + topic_snippet_type: StringProperty(name="BIM Snippet Type") + topic_snippet_is_external: BoolProperty(name="Is BIM Snippet External") + topic_document_references: CollectionProperty(name="BCF Topic Document References", type=BcfTopicDocumentReference) + topic_related_topics: CollectionProperty(name="BCF Topic Related Topics", type=BcfTopicRelatedTopic) class MapConversion(PropertyGroup): @@ -1357,6 +1547,7 @@ class MapConversion(PropertyGroup): x_axis_ordinate: StringProperty(name="X Axis Ordinate") scale: StringProperty(name="Scale") + class TargetCRS(PropertyGroup): name: StringProperty(name="Name") description: StringProperty(name="Description") @@ -1366,6 +1557,7 @@ class TargetCRS(PropertyGroup): map_zone: StringProperty(name="Map Zone") map_unit: StringProperty(name="Map Unit") + class BIMLibrary(PropertyGroup): name: StringProperty(name="Name") version: StringProperty(name="Version") @@ -1388,7 +1580,7 @@ class IfcParameter(PropertyGroup): name: StringProperty(name="Name") step_id: IntProperty(name="STEP ID") index: IntProperty(name="Index") - value: FloatProperty(name="Value") # For now, only floats + value: FloatProperty(name="Value") # For now, only floats type: StringProperty(name="Type") @@ -1402,32 +1594,34 @@ class GlobalId(PropertyGroup): class BoundaryCondition(PropertyGroup): - name: EnumProperty(items=getBoundaryConditionClasses, name='Boundary Type', update=refreshBoundaryConditionAttributes) + name: EnumProperty( + items=getBoundaryConditionClasses, name="Boundary Type", update=refreshBoundaryConditionAttributes + ) attributes: CollectionProperty(name="Attributes", type=Attribute) class BIMObjectProperties(PropertyGroup): global_ids: CollectionProperty(name="GlobalIds", type=GlobalId) attributes: CollectionProperty(name="Attributes", type=Attribute) - relating_type: PointerProperty(name='Type Product', type=bpy.types.Object) - relating_structure: PointerProperty(name='Spatial Container', type=bpy.types.Object) + relating_type: PointerProperty(name="Type Product", type=bpy.types.Object) + relating_structure: PointerProperty(name="Spatial Container", type=bpy.types.Object) psets: CollectionProperty(name="Psets", type=PsetQto) qtos: CollectionProperty(name="Qtos", type=PsetQto) applicable_attributes: EnumProperty(items=getApplicableAttributes, name="Attribute Names") document_references: CollectionProperty(name="Document References", type=DocumentReference) - active_document_reference_index: IntProperty(name='Active Document Reference Index') - constraints: CollectionProperty(name='Constraints', type=Constraint) - active_constraint_index: IntProperty(name='Active Constraint Index') + active_document_reference_index: IntProperty(name="Active Document Reference Index") + constraints: CollectionProperty(name="Constraints", type=Constraint) + active_constraint_index: IntProperty(name="Active Constraint Index") classifications: CollectionProperty(name="Classifications", type=ClassificationReference) material_type: EnumProperty(items=getMaterialTypes, name="Material Type") - pset_name: EnumProperty(items=getPsetNames, name='Pset Name') - qto_name: EnumProperty(items=getQtoNames, name='Qto Name') - has_boundary_condition: BoolProperty(name='Has Boundary Condition') - boundary_condition: PointerProperty(name='Boundary Condition', type=BoundaryCondition) - structural_member_connection: PointerProperty(name='Structural Member Connection', type=bpy.types.Object) + pset_name: EnumProperty(items=getPsetNames, name="Pset Name") + qto_name: EnumProperty(items=getQtoNames, name="Qto Name") + has_boundary_condition: BoolProperty(name="Has Boundary Condition") + boundary_condition: PointerProperty(name="Boundary Condition", type=BoundaryCondition) + structural_member_connection: PointerProperty(name="Structural Member Connection", type=bpy.types.Object) representation_contexts: CollectionProperty(name="Representation Contexts", type=Subcontext) # Address applies to IfcSite's SiteAddress and IfcBuilding's BuildingAddress - address: PointerProperty(name='Address', type=Address) + address: PointerProperty(name="Address", type=Address) class BIMDebugProperties(PropertyGroup): @@ -1444,8 +1638,8 @@ class BIMMaterialProperties(PropertyGroup): psets: CollectionProperty(name="Psets", type=PsetQto) attributes: CollectionProperty(name="Attributes", type=Attribute) applicable_attributes: EnumProperty(items=getApplicableMaterialAttributes, name="Attribute Names") - profile_def: EnumProperty(items=getProfileDef, name='Parameterized Profile Def', update=refreshProfileAttributes) - profile_attributes: CollectionProperty(name='Profile Attributes', type=Attribute) + profile_def: EnumProperty(items=getProfileDef, name="Parameterized Profile Def", update=refreshProfileAttributes) + profile_attributes: CollectionProperty(name="Profile Attributes", type=Attribute) class SweptSolid(PropertyGroup): @@ -1464,10 +1658,10 @@ class BIMMeshProperties(PropertyGroup): is_native: BoolProperty(name="Is Native", default=False) is_swept_solid: BoolProperty(name="Is Swept Solid") swept_solids: CollectionProperty(name="Swept Solids", type=SweptSolid) - is_parametric: BoolProperty(name='Is Parametric', default=False) + is_parametric: BoolProperty(name="Is Parametric", default=False) presentation_layer: StringProperty(name="Presentation Layer") geometry_type: StringProperty(name="Geometry Type") ifc_definition: StringProperty(name="IFC Definition") ifc_definition_id: IntProperty(name="IFC Definition ID") ifc_parameters: CollectionProperty(name="IFC Parameters", type=IfcParameter) - active_representation_item_index: IntProperty(name='Active Representation Item Index') + active_representation_item_index: IntProperty(name="Active Representation Item Index") diff --git a/src/ifcblenderexport/blenderbim/bim/qto.py b/src/ifcblenderexport/blenderbim/bim/qto.py index 9767ae67d2..b1fa9f160d 100644 --- a/src/ifcblenderexport/blenderbim/bim/qto.py +++ b/src/ifcblenderexport/blenderbim/bim/qto.py @@ -1,33 +1,29 @@ from mathutils import Vector -class QtoCalculator(): + +class QtoCalculator: def guess_quantity(self, prop_name, alternative_prop_names, obj): prop_name = prop_name.lower() alternative_prop_names = [p.lower() for p in alternative_prop_names] - if 'length' in prop_name \ - and 'width' not in alternative_prop_names \ - and 'height' not in alternative_prop_names: + if "length" in prop_name and "width" not in alternative_prop_names and "height" not in alternative_prop_names: return self.get_linear_length(obj) - elif 'length' in prop_name: + elif "length" in prop_name: return self.get_length(obj) - elif 'width' in prop_name \ - and 'length' not in alternative_prop_names: + elif "width" in prop_name and "length" not in alternative_prop_names: return self.get_length(obj) - elif 'width' in prop_name: + elif "width" in prop_name: return self.get_width(obj) - elif 'height' in prop_name or 'depth' in prop_name: + elif "height" in prop_name or "depth" in prop_name: return self.get_height(obj) - elif 'perimeter' in prop_name: + elif "perimeter" in prop_name: return self.get_perimeter(obj) - elif 'area' in prop_name \ - and ('footprint' in prop_name or 'section' in prop_name or 'floor' in prop_name): + elif "area" in prop_name and ("footprint" in prop_name or "section" in prop_name or "floor" in prop_name): return self.get_footprint_area(obj) - elif 'area' in prop_name \ - and 'side' in prop_name: + elif "area" in prop_name and "side" in prop_name: return self.get_side_area(obj) - elif 'area' in prop_name: + elif "area" in prop_name: return self.get_area(obj) - elif 'volume' in prop_name: + elif "volume" in prop_name: return self.get_volume(obj) def get_units(self, o, vg_index): @@ -45,10 +41,14 @@ class QtoCalculator(): y = (Vector(o.bound_box[3]) - Vector(o.bound_box[0])).length return max(x, y) length = 0 - edges = [e for e in o.data.edges if ( - vg_index in [g.group for g in o.data.vertices[e.vertices[0]].groups] and - vg_index in [g.group for g in o.data.vertices[e.vertices[1]].groups] - )] + edges = [ + e + for e in o.data.edges + if ( + vg_index in [g.group for g in o.data.vertices[e.vertices[0]].groups] + and vg_index in [g.group for g in o.data.vertices[e.vertices[1]].groups] + ) + ] for e in edges: length += self.get_edge_distance(o, e) return length @@ -139,10 +139,13 @@ class QtoCalculator(): for tf in me.loop_triangles: tfv = tf.vertices if len(tf.vertices) == 3: - tf_tris = (me.vertices[tfv[0]], me.vertices[tfv[1]], me.vertices[tfv[2]]), + tf_tris = ((me.vertices[tfv[0]], me.vertices[tfv[1]], me.vertices[tfv[2]]),) else: - tf_tris = (me.vertices[tfv[0]], me.vertices[tfv[1]], me.vertices[tfv[2]]), \ - (me.vertices[tfv[2]], me.vertices[tfv[3]], me.vertices[tfv[0]]) + tf_tris = (me.vertices[tfv[0]], me.vertices[tfv[1]], me.vertices[tfv[2]]), ( + me.vertices[tfv[2]], + me.vertices[tfv[3]], + me.vertices[tfv[0]], + ) for tf_iter in tf_tris: v1 = ob_mat @ tf_iter[0].co diff --git a/src/ifcblenderexport/blenderbim/bim/scheduler.py b/src/ifcblenderexport/blenderbim/bim/scheduler.py index d6a7f72626..578848ba25 100644 --- a/src/ifcblenderexport/blenderbim/bim/scheduler.py +++ b/src/ifcblenderexport/blenderbim/bim/scheduler.py @@ -6,19 +6,20 @@ from odf.table import Table, TableRow, TableColumn, TableCell from odf.text import P from odf.style import Style -class Scheduler(): + +class Scheduler: def schedule(self, infile, outfile): self.svg = svgwrite.Drawing( outfile, debug=False, - id='root', + id="root", ) self.padding = 1 self.margin = 1 doc = load(infile) styles = {} for style in doc.getElementsByType(Style): - name = style.getAttribute('name') + name = style.getAttribute("name") if not style.firstChild: continue styles[name] = {key[1]: value for key, value in style.firstChild.attributes.items()} @@ -26,14 +27,14 @@ class Scheduler(): table = doc.getElementsByType(Table)[0] column_widths = [] for col in table.getElementsByType(TableColumn): - style_name = col.getAttribute('stylename') - repeat = col.getAttribute('numbercolumnsrepeated') + style_name = col.getAttribute("stylename") + repeat = col.getAttribute("numbercolumnsrepeated") repeat = int(repeat) if repeat else 1 for i in range(0, repeat): - if not style_name or 'column-width' not in styles[style_name]: + if not style_name or "column-width" not in styles[style_name]: column_widths.append(50) else: - column_widths.append(self.convert_to_mm(styles[style_name]['column-width'])) + column_widths.append(self.convert_to_mm(styles[style_name]["column-width"])) y = self.margin for tri, tr in enumerate(table.getElementsByType(TableRow)): @@ -41,54 +42,66 @@ class Scheduler(): height = 6 tdi = 0 for td in tr.getElementsByType(TableCell): - repeat = td.getAttribute('numbercolumnsrepeated') + repeat = td.getAttribute("numbercolumnsrepeated") repeat = int(repeat) if repeat else 1 for i in range(0, repeat): width = column_widths[tdi] - self.svg.add(self.svg.rect(insert=(x, y), size=(width, height), style='fill: #ffffff; stroke-width:.125; stroke: #000000;')) + self.svg.add( + self.svg.rect( + insert=(x, y), + size=(width, height), + style="fill: #ffffff; stroke-width:.125; stroke: #000000;", + ) + ) value = td.getElementsByType(P) if value: - self.add_text(value[0], x+self.padding, y+self.padding) + self.add_text(value[0], x + self.padding, y + self.padding) x += width tdi += 1 y += height total_width = sum(column_widths) + (self.margin * 2) - self.svg['width'] = '{}mm'.format(total_width) - self.svg['height'] = '{}mm'.format(y) - self.svg['viewBox'] = '0 0 {} {}'.format(total_width, y) + self.svg["width"] = "{}mm".format(total_width) + self.svg["height"] = "{}mm".format(y) + self.svg["viewBox"] = "0 0 {} {}".format(total_width, y) self.svg.save(pretty=True) def add_text(self, text, x, y): - self.svg.add(self.svg.text(str(text).upper(), insert=tuple((x, y)), **{ - 'font-size': 4.13, - 'font-family': 'OpenGost Type B TT', - 'text-anchor': 'start', - 'alignment-baseline': 'baseline', - 'dominant-baseline': 'hanging' - })) + self.svg.add( + self.svg.text( + str(text).upper(), + insert=tuple((x, y)), + **{ + "font-size": 4.13, + "font-family": "OpenGost Type B TT", + "text-anchor": "start", + "alignment-baseline": "baseline", + "dominant-baseline": "hanging", + } + ) + ) def convert_to_mm(self, value): # XSL is what defines the units of measurements in ODF # https://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#datatype-positiveLength # https://www.w3.org/TR/2001/REC-xsl-20011015/slice5.html#section-N8185-Definitions-of-Units-of-Measure - if 'cm' in value: + if "cm" in value: return float(value[0:-2]) * 10 - elif 'mm' in value: + elif "mm" in value: return float(value[0:-2]) - elif 'in' in value: + elif "in" in value: return float(value[0:-2]) * 25.4 - elif 'pt' in value: - return float(value[0:-2]) * (1/72) * 25.4 - elif 'pc' in value: - return float(value[0:-2]) * 12 * (1/72) * 25.4 - elif 'px' in value: + elif "pt" in value: + return float(value[0:-2]) * (1 / 72) * 25.4 + elif "pc" in value: + return float(value[0:-2]) * 12 * (1 / 72) * 25.4 + elif "px" in value: # implementors may instead simply pick a fixed conversion factor, # treating 'px' as an absolute unit of measurement (such as 1/92" or # 1/72"). <-- We're picking 1/96 to match SVG. Let me know if it # breaks anything. - return float(value[0:-2]) * (1/96) * 2.54 * 10 - elif 'em' in value: + return float(value[0:-2]) * (1 / 96) * 2.54 * 10 + elif "em" in value: # This is a funny one. Since the scheduler at the moment enforces a # font size of 2.5mm (vertically, FWIW), I'm writing this. Hopefully # this code doesn't hurt anybody. - return float(value[0:-2]) * (1/96) * 2.54 * 10 + return float(value[0:-2]) * (1 / 96) * 2.54 * 10 diff --git a/src/ifcblenderexport/blenderbim/bim/schema.py b/src/ifcblenderexport/blenderbim/bim/schema.py index f7eda28a83..4a34a042d3 100644 --- a/src/ifcblenderexport/blenderbim/bim/schema.py +++ b/src/ifcblenderexport/blenderbim/bim/schema.py @@ -6,31 +6,32 @@ from pathlib import Path cwd = os.path.dirname(os.path.realpath(__file__)) -class IfcSchema(): + +class IfcSchema: def __init__(self): - self.schema_dir = os.path.join(cwd, 'schema') # TODO: make configurable - self.data_dir = os.path.join(cwd, 'data') # TODO: make configurable + self.schema_dir = os.path.join(cwd, "schema") # TODO: make configurable + self.data_dir = os.path.join(cwd, "data") # TODO: make configurable # TODO: Make it less troublesome self.products = [ - 'IfcContext', - 'IfcElement', - 'IfcSpatialElement', - 'IfcGroup', - 'IfcStructural', - 'IfcPositioningElement', - 'IfcMaterialDefinition', - 'IfcParameterizedProfileDef', - 'IfcBoundaryCondition', - 'IfcElementType', - 'IfcAnnotation' + "IfcContext", + "IfcElement", + "IfcSpatialElement", + "IfcGroup", + "IfcStructural", + "IfcPositioningElement", + "IfcMaterialDefinition", + "IfcParameterizedProfileDef", + "IfcBoundaryCondition", + "IfcElementType", + "IfcAnnotation", ] self.elements = {} self.property_files = [] - property_paths = Path(os.path.join(self.data_dir, 'pset')).glob('*.ifc') + property_paths = Path(os.path.join(self.data_dir, "pset")).glob("*.ifc") for path in property_paths: self.property_files.append(ifcopenshell.open(path)) - self.property_files.append(ifcopenshell.open(os.path.join(self.schema_dir, 'Pset_IFC4_ADD2.ifc'))) + self.property_files.append(ifcopenshell.open(os.path.join(self.schema_dir, "Pset_IFC4_ADD2.ifc"))) self.classification_files = {} self.psets = {} @@ -42,55 +43,49 @@ class IfcSchema(): def load(self): for product in self.products: - with open(os.path.join(self.schema_dir, f'{product}_IFC4.json')) as f: + with open(os.path.join(self.schema_dir, f"{product}_IFC4.json")) as f: setattr(self, product, json.load(f)) self.elements.update(getattr(self, product)) - with open(os.path.join(self.schema_dir, 'ifc_types_IFC4.json')) as f: + with open(os.path.join(self.schema_dir, "ifc_types_IFC4.json")) as f: self.type_map = json.load(f) for property_file in self.property_files: - for prop in property_file.by_type('IfcPropertySetTemplate'): - if prop.Name[0:4] == 'Qto_': - self.qtos[prop.Name] = { - 'HasPropertyTemplates': {p.Name: p for p in prop.HasPropertyTemplates}} - entity = prop.ApplicableEntity if prop.ApplicableEntity else 'IfcRoot' + for prop in property_file.by_type("IfcPropertySetTemplate"): + if prop.Name[0:4] == "Qto_": + self.qtos[prop.Name] = {"HasPropertyTemplates": {p.Name: p for p in prop.HasPropertyTemplates}} + entity = prop.ApplicableEntity if prop.ApplicableEntity else "IfcRoot" self.applicable_qtos.setdefault(entity, []).append(prop.Name) else: - self.psets[prop.Name] = { - 'HasPropertyTemplates': {p.Name: p for p in prop.HasPropertyTemplates}} - entity = prop.ApplicableEntity if prop.ApplicableEntity else 'IfcRoot' + self.psets[prop.Name] = {"HasPropertyTemplates": {p.Name: p for p in prop.HasPropertyTemplates}} + entity = prop.ApplicableEntity if prop.ApplicableEntity else "IfcRoot" self.applicable_psets.setdefault(entity, []).append(prop.Name) def load_classification(self, name, classification_index=None): if name not in self.classifications: if classification_index is not None: self.classification_files[name] = ifcopenshell.file.from_string( - bpy.context.scene.BIMProperties.classifications[classification_index].data) + bpy.context.scene.BIMProperties.classifications[classification_index].data + ) else: - classification_path = os.path.join(self.schema_dir, 'classifications', '{}.ifc'.format(name)) + classification_path = os.path.join(self.schema_dir, "classifications", "{}.ifc".format(name)) self.classification_files[name] = ifcopenshell.open(classification_path) - self.classifications[name] = self.classification_files[name].by_type('IfcClassification')[0] + self.classifications[name] = self.classification_files[name].by_type("IfcClassification")[0] classification = self.classifications[name] bpy.context.scene.BIMProperties.active_classification_name = self.classifications[name].Name - return { - 'name': '', - 'description': '', - 'children': self.get_classification_references(classification) - } + return {"name": "", "description": "", "children": self.get_classification_references(classification)} def get_classification_references(self, classification): references = {} - if not hasattr(classification, 'HasReferences') \ - or not classification.HasReferences: + if not hasattr(classification, "HasReferences") or not classification.HasReferences: return references for reference in classification.HasReferences: references[reference.Identification] = { - 'location': reference.Location, - 'identification': reference.Identification, - 'name': reference.Name, - 'description': reference.Description, - 'children': self.get_classification_references(reference) + "location": reference.Location, + "identification": reference.Identification, + "name": reference.Name, + "description": reference.Description, + "children": self.get_classification_references(reference), } return references diff --git a/src/ifcblenderexport/blenderbim/bim/sheeter.py b/src/ifcblenderexport/blenderbim/bim/sheeter.py index 0fd7a3573f..6fc62364fd 100644 --- a/src/ifcblenderexport/blenderbim/bim/sheeter.py +++ b/src/ifcblenderexport/blenderbim/bim/sheeter.py @@ -6,48 +6,50 @@ import os from shutil import copy from xml.dom import minidom + class SheetBuilder: def __init__(self): self.data_dir = None - self.scale = 'NTS' + self.scale = "NTS" def create(self, name, titleblock_name): - sheet_path = '{}sheets/{}.svg'.format(self.data_dir, name) - root = ET.Element('svg') - root.attrib['xmlns'] = 'http://www.w3.org/2000/svg' - root.attrib['xmlns:xlink'] = 'http://www.w3.org/1999/xlink' - root.attrib['id'] = 'root' - root.attrib['version'] = '1.1' + sheet_path = "{}sheets/{}.svg".format(self.data_dir, name) + root = ET.Element("svg") + root.attrib["xmlns"] = "http://www.w3.org/2000/svg" + root.attrib["xmlns:xlink"] = "http://www.w3.org/1999/xlink" + root.attrib["id"] = "root" + root.attrib["version"] = "1.1" view_root = ET.parse( - os.path.join(self.data_dir, 'templates', 'titleblocks', titleblock_name + '.svg')).getroot() - view_width = self.convert_to_mm(view_root.attrib.get('width')) - view_height = self.convert_to_mm(view_root.attrib.get('height')) - view = ET.SubElement(root, 'g') - view.attrib['data-type'] = 'titleblock' - titleblock = ET.SubElement(view, 'image') - titleblock.attrib['xlink:href'] = '../templates/titleblocks/' + titleblock_name + '.svg' - titleblock.attrib['x'] = '0' - titleblock.attrib['y'] = '0' - titleblock.attrib['width'] = str(view_width) - titleblock.attrib['height'] = str(view_height) + os.path.join(self.data_dir, "templates", "titleblocks", titleblock_name + ".svg") + ).getroot() + view_width = self.convert_to_mm(view_root.attrib.get("width")) + view_height = self.convert_to_mm(view_root.attrib.get("height")) + view = ET.SubElement(root, "g") + view.attrib["data-type"] = "titleblock" + titleblock = ET.SubElement(view, "image") + titleblock.attrib["xlink:href"] = "../templates/titleblocks/" + titleblock_name + ".svg" + titleblock.attrib["x"] = "0" + titleblock.attrib["y"] = "0" + titleblock.attrib["width"] = str(view_width) + titleblock.attrib["height"] = str(view_height) - root.attrib['width'] = '{}mm'.format(view_width) - root.attrib['height'] = '{}mm'.format(view_height) - root.attrib['viewBox'] = '0 0 {} {}'.format(view_width, view_height) + root.attrib["width"] = "{}mm".format(view_width) + root.attrib["height"] = "{}mm".format(view_height) + root.attrib["viewBox"] = "0 0 {} {}".format(view_width, view_height) - with open(sheet_path, 'w') as f: - f.write(minidom.parseString(ET.tostring(root)).toprettyxml(indent=' ')) + with open(sheet_path, "w") as f: + f.write(minidom.parseString(ET.tostring(root)).toprettyxml(indent=" ")) def add_drawing(self, view_name, sheet_name): - sheet_path = os.path.join(self.data_dir, 'sheets', sheet_name + '.svg') - view_path = os.path.join(self.data_dir, 'diagrams', view_name + '.svg') + sheet_path = os.path.join(self.data_dir, "sheets", sheet_name + ".svg") + view_path = os.path.join(self.data_dir, "diagrams", view_name + ".svg") if not os.path.isfile(view_path): raise FileNotFoundError - ET.register_namespace('', 'http://www.w3.org/2000/svg') - ET.register_namespace('xlink', 'http://www.w3.org/1999/xlink') + ET.register_namespace("", "http://www.w3.org/2000/svg") + ET.register_namespace("xlink", "http://www.w3.org/1999/xlink") sheet_tree = ET.parse(sheet_path) sheet_root = sheet_tree.getroot() @@ -58,115 +60,112 @@ class SheetBuilder: # The view is placed into a group with a background image element. # Although the foreground SVG already has a background, it is duplicated # here to accommodate browsers which do not nest images. - view = ET.SubElement(sheet_root, 'g') - view.attrib['data-type'] = 'drawing' - view_width = self.convert_to_mm(view_root.attrib.get('width')) - view_height = self.convert_to_mm(view_root.attrib.get('height')) + view = ET.SubElement(sheet_root, "g") + view.attrib["data-type"] = "drawing" + view_width = self.convert_to_mm(view_root.attrib.get("width")) + view_height = self.convert_to_mm(view_root.attrib.get("height")) - background = ET.SubElement(view, 'image') - background.attrib['xlink:href'] = '../diagrams/{}.png'.format(view_name) - background.attrib['x'] = '30' - background.attrib['y'] = '30' - background.attrib['width'] = str(view_width) - background.attrib['height'] = str(view_height) + background = ET.SubElement(view, "image") + background.attrib["xlink:href"] = "../diagrams/{}.png".format(view_name) + background.attrib["x"] = "30" + background.attrib["y"] = "30" + background.attrib["width"] = str(view_width) + background.attrib["height"] = str(view_height) - foreground = ET.SubElement(view, 'image') - foreground.attrib['xlink:href'] = '../diagrams/{}.svg'.format(view_name) - foreground.attrib['x'] = '30' - foreground.attrib['y'] = '30' - foreground.attrib['width'] = str(view_width) - foreground.attrib['height'] = str(view_height) + foreground = ET.SubElement(view, "image") + foreground.attrib["xlink:href"] = "../diagrams/{}.svg".format(view_name) + foreground.attrib["x"] = "30" + foreground.attrib["y"] = "30" + foreground.attrib["width"] = str(view_width) + foreground.attrib["height"] = str(view_height) - self.add_view_title(30, view_height+35, view) + self.add_view_title(30, view_height + 35, view) sheet_tree.write(sheet_path) def add_schedule(self, schedule_name, sheet_name): - sheet_path = os.path.join(self.data_dir, 'sheets', sheet_name + '.svg') - view_path = os.path.join(self.data_dir, 'schedules', schedule_name + '.svg') + sheet_path = os.path.join(self.data_dir, "sheets", sheet_name + ".svg") + view_path = os.path.join(self.data_dir, "schedules", schedule_name + ".svg") - ET.register_namespace('', 'http://www.w3.org/2000/svg') - ET.register_namespace('xlink', 'http://www.w3.org/1999/xlink') + ET.register_namespace("", "http://www.w3.org/2000/svg") + ET.register_namespace("xlink", "http://www.w3.org/1999/xlink") sheet_tree = ET.parse(sheet_path) sheet_root = sheet_tree.getroot() view_tree = ET.parse(view_path) view_root = view_tree.getroot() - view_width = self.convert_to_mm(view_root.attrib.get('width')) - view_height = self.convert_to_mm(view_root.attrib.get('height')) + view_width = self.convert_to_mm(view_root.attrib.get("width")) + view_height = self.convert_to_mm(view_root.attrib.get("height")) - group = ET.SubElement(sheet_root, 'g') - group.attrib['data-type'] = 'schedule' + group = ET.SubElement(sheet_root, "g") + group.attrib["data-type"] = "schedule" - foreground = ET.SubElement(group, 'image') - foreground.attrib['xlink:href'] = '../schedules/{}.svg'.format(schedule_name) - foreground.attrib['x'] = '30' - foreground.attrib['y'] = '30' - foreground.attrib['width'] = str(view_width) - foreground.attrib['height'] = str(view_height) + foreground = ET.SubElement(group, "image") + foreground.attrib["xlink:href"] = "../schedules/{}.svg".format(schedule_name) + foreground.attrib["x"] = "30" + foreground.attrib["y"] = "30" + foreground.attrib["width"] = str(view_width) + foreground.attrib["height"] = str(view_height) - self.add_view_title(30, view_height+35, group) + self.add_view_title(30, view_height + 35, group) sheet_tree.write(sheet_path) def add_view_title(self, x, y, parent): - title_tree = ET.parse(os.path.join(self.data_dir, 'templates', 'view-title.svg')) + title_tree = ET.parse(os.path.join(self.data_dir, "templates", "view-title.svg")) title_root = title_tree.getroot() - title = ET.SubElement(parent, 'image') - title.attrib['xlink:href'] = '../templates/view-title.svg' - title.attrib['x'] = str(x) - title.attrib['y'] = str(y) - title.attrib['width'] = str(self.convert_to_mm(title_root.attrib.get('width'))) - title.attrib['height'] = str(self.convert_to_mm(title_root.attrib.get('height'))) - + title = ET.SubElement(parent, "image") + title.attrib["xlink:href"] = "../templates/view-title.svg" + title.attrib["x"] = str(x) + title.attrib["y"] = str(y) + title.attrib["width"] = str(self.convert_to_mm(title_root.attrib.get("width"))) + title.attrib["height"] = str(self.convert_to_mm(title_root.attrib.get("height"))) def build(self, sheet_name): - os.makedirs('{}build/{}/'.format(self.data_dir, sheet_name), exist_ok=True) + os.makedirs("{}build/{}/".format(self.data_dir, sheet_name), exist_ok=True) - sheet_path = '{}sheets/{}.svg'.format(self.data_dir, sheet_name) + sheet_path = "{}sheets/{}.svg".format(self.data_dir, sheet_name) - ET.register_namespace('', 'http://www.w3.org/2000/svg') - ET.register_namespace('xlink', 'http://www.w3.org/1999/xlink') + ET.register_namespace("", "http://www.w3.org/2000/svg") + ET.register_namespace("xlink", "http://www.w3.org/1999/xlink") tree = ET.parse(sheet_path) root = tree.getroot() titleblock = root.findall('{http://www.w3.org/2000/svg}g[@data-type="titleblock"]')[0] - image = titleblock.findall('{http://www.w3.org/2000/svg}image')[0] - titleblock.append(self.parse_embedded_svg(image, { - 'number': sheet_name, - 'revision': 'A' - })) + image = titleblock.findall("{http://www.w3.org/2000/svg}image")[0] + titleblock.append(self.parse_embedded_svg(image, {"number": sheet_name, "revision": "A"})) titleblock.remove(image) self.group_number = 1 self.build_drawings(root.findall('{http://www.w3.org/2000/svg}g[@data-type="drawing"]'), sheet_name) self.build_schedules(root.findall('{http://www.w3.org/2000/svg}g[@data-type="schedule"]')) - with open('{}build/{}/{}.svg'.format(self.data_dir, sheet_name, sheet_name), 'wb') as output: + with open("{}build/{}/{}.svg".format(self.data_dir, sheet_name, sheet_name), "wb") as output: tree.write(output) def build_drawings(self, drawings, sheet_name): for view in drawings: - images = view.findall('{http://www.w3.org/2000/svg}image') + images = view.findall("{http://www.w3.org/2000/svg}image") background = images[0] foreground = images[1] view_title = images[2] - self.scale = 'NTS' + self.scale = "NTS" # Add foreground view.append(self.parse_embedded_svg(foreground, {})) # Add background - background_path = '{}sheets/{}'.format(self.data_dir, self.get_href(background)) - copy(background_path, '{}build/{}/'.format(self.data_dir, sheet_name)) + background_path = "{}sheets/{}".format(self.data_dir, self.get_href(background)) + copy(background_path, "{}build/{}/".format(self.data_dir, sheet_name)) # Add view title foreground_path = self.get_href(foreground) - view.append(self.parse_embedded_svg(view_title, { - 'no' : self.group_number, - 'name': ntpath.basename(foreground_path)[0:-4], - 'scale': self.scale - })) + view.append( + self.parse_embedded_svg( + view_title, + {"no": self.group_number, "name": ntpath.basename(foreground_path)[0:-4], "scale": self.scale}, + ) + ) for image in images: view.remove(image) @@ -175,19 +174,19 @@ class SheetBuilder: def build_schedules(self, schedules): for group in schedules: - images = group.findall('{http://www.w3.org/2000/svg}image') + images = group.findall("{http://www.w3.org/2000/svg}image") schedule = images[0] group_title = images[1] - self.scale = 'NTS' + self.scale = "NTS" group.append(self.parse_embedded_svg(schedule, {})) path = self.get_href(schedule) - group.append(self.parse_embedded_svg(group_title, { - 'no' : self.group_number, - 'name': ntpath.basename(path)[0:-4], - 'scale': self.scale - })) + group.append( + self.parse_embedded_svg( + group_title, {"no": self.group_number, "name": ntpath.basename(path)[0:-4], "scale": self.scale} + ) + ) for image in images: group.remove(image) @@ -195,24 +194,24 @@ class SheetBuilder: self.group_number += 1 def get_href(self, element): - return urllib.parse.unquote(element.attrib.get('{http://www.w3.org/1999/xlink}href')) + return urllib.parse.unquote(element.attrib.get("{http://www.w3.org/1999/xlink}href")) def parse_embedded_svg(self, image, data): - group = ET.Element('g') - group.attrib['transform'] = 'translate({},{})'.format( - self.convert_to_mm(image.attrib.get('x')), - self.convert_to_mm(image.attrib.get('y'))) + group = ET.Element("g") + group.attrib["transform"] = "translate({},{})".format( + self.convert_to_mm(image.attrib.get("x")), self.convert_to_mm(image.attrib.get("y")) + ) svg_path = self.get_href(image) - with open('{}sheets/{}'.format(self.data_dir, svg_path), 'r') as template: + with open("{}sheets/{}".format(self.data_dir, svg_path), "r") as template: embedded = ET.fromstring(pystache.render(template.read(), data)) # viewBox should not be nested - embedded.attrib['viewBox'] = '' + embedded.attrib["viewBox"] = "" # TODO: This should not be in this function - self.scale = embedded.attrib.get('data-scale') - images = embedded.findall('{http://www.w3.org/2000/svg}image') + self.scale = embedded.attrib.get("data-scale") + images = embedded.findall("{http://www.w3.org/2000/svg}image") for image in images: - new_href = ntpath.basename(image.attrib.get('{http://www.w3.org/1999/xlink}href')) - image.attrib['{http://www.w3.org/1999/xlink}href'] = new_href + new_href = ntpath.basename(image.attrib.get("{http://www.w3.org/1999/xlink}href")) + image.attrib["{http://www.w3.org/1999/xlink}href"] = new_href group.append(embedded) return group @@ -221,20 +220,20 @@ class SheetBuilder: # https://www.w3.org/TR/SVG/refs.html#ref-css-values-3 # https://www.w3.org/TR/css-values-3/#absolute-lengths # The relative units are not implemented. Go fish. - if 'cm' in value: + if "cm" in value: return float(value[0:-2]) * 10 - elif 'mm' in value: + elif "mm" in value: return float(value[0:-2]) - elif 'Q' in value: - return float(value[0:-1]) * (1/40) * 10 - elif 'in' in value: + elif "Q" in value: + return float(value[0:-1]) * (1 / 40) * 10 + elif "in" in value: return float(value[0:-2]) * 2.54 * 10 - elif 'pc' in value: - return float(value[0:-2]) * (1/6) * 2.54 * 10 - elif 'pt' in value: - return float(value[0:-2]) * (1/72) * 2.54 * 10 - elif 'px' in value: - return float(value[0:-2]) * (1/96) * 2.54 * 10 + elif "pc" in value: + return float(value[0:-2]) * (1 / 6) * 2.54 * 10 + elif "pt" in value: + return float(value[0:-2]) * (1 / 72) * 2.54 * 10 + elif "px" in value: + return float(value[0:-2]) * (1 / 96) * 2.54 * 10 return float(value) def mm_to_px(self, value): diff --git a/src/ifcblenderexport/blenderbim/bim/svgwriter.py b/src/ifcblenderexport/blenderbim/bim/svgwriter.py index 814cb14372..d3cd79438f 100644 --- a/src/ifcblenderexport/blenderbim/bim/svgwriter.py +++ b/src/ifcblenderexport/blenderbim/bim/svgwriter.py @@ -16,12 +16,13 @@ try: except ImportError: from OCC import BRep, BRepTools, TopExp, TopAbs + class External(svgwrite.container.Group): def __init__(self, xml, **extra): self.xml = xml # Remove namespace - ns = u'{http://www.w3.org/2000/svg}' + ns = u"{http://www.w3.org/2000/svg}" nsl = len(ns) for elem in self.xml.getiterator(): if elem.tag.startswith(ns): @@ -33,26 +34,22 @@ class External(svgwrite.container.Group): return self.xml -class SvgWriter(): +class SvgWriter: def __init__(self, ifc_cutter): self.ifc_cutter = ifc_cutter - self.human_scale = 'NTS' - self.scale = 1 / 100 # 1:100 + self.human_scale = "NTS" + self.scale = 1 / 100 # 1:100 def write(self): self.calculate_scale() - self.output = os.path.join( - self.ifc_cutter.data_dir, - 'diagrams', - self.ifc_cutter.diagram_name + '.svg' - ) + self.output = os.path.join(self.ifc_cutter.data_dir, "diagrams", self.ifc_cutter.diagram_name + ".svg") self.svg = svgwrite.Drawing( self.output, debug=False, - size=('{}mm'.format(self.width), '{}mm'.format(self.height)), - viewBox=('0 0 {} {}'.format(self.width, self.height)), - id='root', - data_scale=self.human_scale + size=("{}mm".format(self.width), "{}mm".format(self.height)), + viewBox=("0 0 {} {}".format(self.width, self.height)), + id="root", + data_scale=self.human_scale, ) self.add_stylesheet() @@ -66,57 +63,57 @@ class SvgWriter(): self.svg.save(pretty=True) def calculate_scale(self): - self.scale *= 1000 # IFC is in meters, SVG is in mm - self.raw_width = self.ifc_cutter.section_box['x'] - self.raw_height = self.ifc_cutter.section_box['y'] + self.scale *= 1000 # IFC is in meters, SVG is in mm + self.raw_width = self.ifc_cutter.section_box["x"] + self.raw_height = self.ifc_cutter.section_box["y"] self.width = self.raw_width * self.scale self.height = self.raw_height * self.scale def add_stylesheet(self): - with open('{}styles/{}.css'.format(self.ifc_cutter.data_dir, self.ifc_cutter.vector_style), 'r') as stylesheet: + with open("{}styles/{}.css".format(self.ifc_cutter.data_dir, self.ifc_cutter.vector_style), "r") as stylesheet: self.svg.defs.add(self.svg.style(stylesheet.read())) def add_markers(self): - tree = ET.parse('{}templates/markers.svg'.format(self.ifc_cutter.data_dir)) + tree = ET.parse("{}templates/markers.svg".format(self.ifc_cutter.data_dir)) root = tree.getroot() for child in root.getchildren(): self.svg.defs.add(External(child)) def add_symbols(self): - tree = ET.parse('{}templates/symbols.svg'.format(self.ifc_cutter.data_dir)) + tree = ET.parse("{}templates/symbols.svg".format(self.ifc_cutter.data_dir)) root = tree.getroot() for child in root.getchildren(): self.svg.defs.add(External(child)) def add_patterns(self): - tree = ET.parse('{}templates/patterns.svg'.format(self.ifc_cutter.data_dir)) + tree = ET.parse("{}templates/patterns.svg".format(self.ifc_cutter.data_dir)) root = tree.getroot() for child in root.getchildren(): self.svg.defs.add(External(child)) def draw_background_image(self): - self.svg.add(self.svg.image( - os.path.join('..', 'diagrams', os.path.basename(self.ifc_cutter.background_image)), **{ - 'width': self.width, - 'height': self.height - } - )) + self.svg.add( + self.svg.image( + os.path.join("..", "diagrams", os.path.basename(self.ifc_cutter.background_image)), + **{"width": self.width, "height": self.height} + ) + ) def draw_background_elements(self): for element in self.ifc_cutter.background_elements: - if element['type'] == 'polygon': - self.draw_polygon(element, 'background') - elif element['type'] == 'polyline': - self.draw_polyline(element, 'background') - elif element['type'] == 'line': - self.draw_line(element, 'background') + if element["type"] == "polygon": + self.draw_polygon(element, "background") + elif element["type"] == "polyline": + self.draw_polyline(element, "background") + elif element["type"] == "line": + self.draw_line(element, "background") def draw_annotations(self): x_offset = self.raw_width / 2 y_offset = self.raw_height / 2 for obj in self.ifc_cutter.equal_objs: - self.draw_dimension_annotations(obj, text_override='EQ') + self.draw_dimension_annotations(obj, text_override="EQ") for obj in self.ifc_cutter.dimension_objs: self.draw_dimension_annotations(obj) self.draw_measureit_arch_dimension_annotations() @@ -127,7 +124,7 @@ class SvgWriter(): for grid_obj in self.ifc_cutter.grid_objs: matrix_world = grid_obj.matrix_world for edge in grid_obj.data.edges: - classes = ['annotation', 'grid'] + classes = ["annotation", "grid"] v0_global = matrix_world @ grid_obj.data.vertices[edge.vertices[0]].co.xyz v1_global = matrix_world @ grid_obj.data.vertices[edge.vertices[1]].co.xyz v0 = self.project_point_onto_camera(v0_global) @@ -135,148 +132,198 @@ class SvgWriter(): start = Vector(((x_offset + v0.x), (y_offset - v0.y))) end = Vector(((x_offset + v1.x), (y_offset - v1.y))) vector = end - start - line = self.svg.add(self.svg.line(start=tuple(start * self.scale), - end=tuple(end * self.scale), class_=' '.join(classes))) - line['marker-start'] = 'url(#grid-marker)' - line['marker-end'] = 'url(#grid-marker)' - line['stroke-dasharray'] = '12.5, 3, 3, 3' - axis_tag = grid_obj.BIMObjectProperties.attributes.get('AxisTag') + line = self.svg.add( + self.svg.line( + start=tuple(start * self.scale), end=tuple(end * self.scale), class_=" ".join(classes) + ) + ) + line["marker-start"] = "url(#grid-marker)" + line["marker-end"] = "url(#grid-marker)" + line["stroke-dasharray"] = "12.5, 3, 3, 3" + axis_tag = grid_obj.BIMObjectProperties.attributes.get("AxisTag") if axis_tag: axis_tag = axis_tag.string_value else: - axis_tag = grid_obj.name.split('/')[1] - self.svg.add(self.svg.text(axis_tag, insert=tuple(start * self.scale), **{ - 'font-size': annotation.Annotator.get_svg_text_size(5.0), - 'font-family': 'OpenGost Type B TT', - 'text-anchor': 'middle', - 'alignment-baseline': 'middle', - 'dominant-baseline': 'middle' - })) - self.svg.add(self.svg.text(axis_tag, insert=tuple(end * self.scale), **{ - 'font-size': annotation.Annotator.get_svg_text_size(5.0), - 'font-family': 'OpenGost Type B TT', - 'text-anchor': 'middle', - 'alignment-baseline': 'middle', - 'dominant-baseline': 'middle' - })) + axis_tag = grid_obj.name.split("/")[1] + self.svg.add( + self.svg.text( + axis_tag, + insert=tuple(start * self.scale), + **{ + "font-size": annotation.Annotator.get_svg_text_size(5.0), + "font-family": "OpenGost Type B TT", + "text-anchor": "middle", + "alignment-baseline": "middle", + "dominant-baseline": "middle", + } + ) + ) + self.svg.add( + self.svg.text( + axis_tag, + insert=tuple(end * self.scale), + **{ + "font-size": annotation.Annotator.get_svg_text_size(5.0), + "font-family": "OpenGost Type B TT", + "text-anchor": "middle", + "alignment-baseline": "middle", + "dominant-baseline": "middle", + } + ) + ) self.draw_ifc_annotation() for obj in self.ifc_cutter.misc_objs: - self.draw_misc_annotation(obj, ['IfcAnnotation']) + self.draw_misc_annotation(obj, ["IfcAnnotation"]) for obj_data in self.ifc_cutter.hidden_objs: - self.draw_line_annotation(obj_data, ['hidden']) + self.draw_line_annotation(obj_data, ["hidden"]) for obj_data in self.ifc_cutter.solid_objs: - self.draw_line_annotation(obj_data, ['solid']) + self.draw_line_annotation(obj_data, ["solid"]) if self.ifc_cutter.leader_obj: - self.draw_line_annotation(self.ifc_cutter.leader_obj, ['leader']) + self.draw_line_annotation(self.ifc_cutter.leader_obj, ["leader"]) if self.ifc_cutter.plan_level_obj: matrix_world = self.ifc_cutter.plan_level_obj.matrix_world for spline in self.ifc_cutter.plan_level_obj.data.splines: - classes = ['annotation', 'plan-level'] + classes = ["annotation", "plan-level"] points = self.get_spline_points(spline) projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points] - d = ' '.join(['L {} {}'.format( - (x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale) - for p in projected_points]) - d = 'M{}'.format(d[1:]) - path = self.svg.add(self.svg.path(d=d, class_=' '.join(classes))) - path['marker-end'] = 'url(#plan-level-marker)' - text_position = Vector(( - (x_offset + projected_points[0].x) * self.scale, - ((y_offset - projected_points[0].y) * self.scale) - 2.5 - )) + d = " ".join( + [ + "L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale) + for p in projected_points + ] + ) + d = "M{}".format(d[1:]) + path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes))) + path["marker-end"] = "url(#plan-level-marker)" + text_position = Vector( + ( + (x_offset + projected_points[0].x) * self.scale, + ((y_offset - projected_points[0].y) * self.scale) - 2.5, + ) + ) # TODO: allow metric to be configurable - rl = ((matrix_world @ - points[0].co).xyz + self.ifc_cutter.plan_level_obj.location).z - if bpy.context.scene.unit_settings.system == 'IMPERIAL': + rl = ((matrix_world @ points[0].co).xyz + self.ifc_cutter.plan_level_obj.location).z + if bpy.context.scene.unit_settings.system == "IMPERIAL": rl = helper.format_distance(rl) else: - rl = '{:.3f}m'.format(rl) + rl = "{:.3f}m".format(rl) if projected_points[0].x > projected_points[-1].x: - text_anchor = 'end' + text_anchor = "end" else: - text_anchor = 'start' - self.svg.add(self.svg.text('RL +{}'.format(rl), insert=tuple(text_position), **{ - 'font-size': annotation.Annotator.get_svg_text_size(2.5), - 'font-family': 'OpenGost Type B TT', - 'text-anchor': text_anchor, - 'alignment-baseline': 'baseline', - 'dominant-baseline': 'baseline' - })) + text_anchor = "start" + self.svg.add( + self.svg.text( + "RL +{}".format(rl), + insert=tuple(text_position), + **{ + "font-size": annotation.Annotator.get_svg_text_size(2.5), + "font-family": "OpenGost Type B TT", + "text-anchor": text_anchor, + "alignment-baseline": "baseline", + "dominant-baseline": "baseline", + } + ) + ) if self.ifc_cutter.section_level_obj: matrix_world = self.ifc_cutter.section_level_obj.matrix_world for spline in self.ifc_cutter.section_level_obj.data.splines: - classes = ['annotation', 'section-level'] + classes = ["annotation", "section-level"] points = self.get_spline_points(spline) projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points] - d = ' '.join(['L {} {}'.format( - (x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale) - for p in projected_points]) - d = 'M{}'.format(d[1:]) - path = self.svg.add(self.svg.path(d=d, class_=' '.join(classes))) - path['marker-start'] = 'url(#section-level-marker)' - path['stroke-dasharray'] = '12.5, 3, 3, 3' - text_position = Vector(( - (x_offset + projected_points[0].x) * self.scale, - ((y_offset - projected_points[0].y) * self.scale) - 3.5 - )) + d = " ".join( + [ + "L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale) + for p in projected_points + ] + ) + d = "M{}".format(d[1:]) + path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes))) + path["marker-start"] = "url(#section-level-marker)" + path["stroke-dasharray"] = "12.5, 3, 3, 3" + text_position = Vector( + ( + (x_offset + projected_points[0].x) * self.scale, + ((y_offset - projected_points[0].y) * self.scale) - 3.5, + ) + ) # TODO: allow metric to be configurable rl = (matrix_world @ points[0].co.xyz).z - if bpy.context.scene.unit_settings.system == 'IMPERIAL': + if bpy.context.scene.unit_settings.system == "IMPERIAL": rl = helper.format_distance(rl) else: - rl = '{:.3f}m'.format(rl) - self.svg.add(self.svg.text('RL +{}'.format(rl), insert=tuple(text_position), **{ - 'font-size': annotation.Annotator.get_svg_text_size(2.5), - 'font-family': 'OpenGost Type B TT', - 'text-anchor': 'start', - 'alignment-baseline': 'baseline', - 'dominant-baseline': 'baseline' - })) + rl = "{:.3f}m".format(rl) + self.svg.add( + self.svg.text( + "RL +{}".format(rl), + insert=tuple(text_position), + **{ + "font-size": annotation.Annotator.get_svg_text_size(2.5), + "font-family": "OpenGost Type B TT", + "text-anchor": "start", + "alignment-baseline": "baseline", + "dominant-baseline": "baseline", + } + ) + ) if self.ifc_cutter.stair_obj: matrix_world = self.ifc_cutter.stair_obj.matrix_world for spline in self.ifc_cutter.stair_obj.data.splines: - classes = ['annotation', 'stair'] + classes = ["annotation", "stair"] points = self.get_spline_points(spline) projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points] - d = ' '.join(['L {} {}'.format( - (x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale) - for p in projected_points]) - d = 'M{}'.format(d[1:]) + d = " ".join( + [ + "L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale) + for p in projected_points + ] + ) + d = "M{}".format(d[1:]) start = Vector(((x_offset + projected_points[0].x), (y_offset - projected_points[0].y))) next_point = Vector(((x_offset + projected_points[1].x), (y_offset - projected_points[1].y))) text_position = (start * self.scale) - ((next_point - start).normalized() * 5) - path = self.svg.add(self.svg.path(d=d, class_=' '.join(classes))) - self.svg.add(self.svg.text('UP', insert=tuple(text_position), **{ - 'font-size': annotation.Annotator.get_svg_text_size(2.5), - 'font-family': 'OpenGost Type B TT', - 'text-anchor': 'middle', - 'alignment-baseline': 'middle', - 'dominant-baseline': 'middle' - })) + path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes))) + self.svg.add( + self.svg.text( + "UP", + insert=tuple(text_position), + **{ + "font-size": annotation.Annotator.get_svg_text_size(2.5), + "font-family": "OpenGost Type B TT", + "text-anchor": "middle", + "alignment-baseline": "middle", + "dominant-baseline": "middle", + } + ) + ) self.draw_text_annotations() def draw_ifc_annotation(self): x_offset = self.raw_width / 2 y_offset = self.raw_height / 2 for annotation in self.ifc_cutter.annotation_objs: - for edge in annotation['edges']: - v0_global = annotation['vertices'][edge[0]] - v1_global = annotation['vertices'][edge[1]] + for edge in annotation["edges"]: + v0_global = annotation["vertices"][edge[0]] + v1_global = annotation["vertices"][edge[1]] v0 = self.project_point_onto_camera(v0_global) v1 = self.project_point_onto_camera(v1_global) start = Vector(((x_offset + v0.x), (y_offset - v0.y))) end = Vector(((x_offset + v1.x), (y_offset - v1.y))) vector = end - start - line = self.svg.add(self.svg.line(start=tuple(start * self.scale), - end=tuple(end * self.scale), class_=' '.join(annotation['classes']))) + line = self.svg.add( + self.svg.line( + start=tuple(start * self.scale), + end=tuple(end * self.scale), + class_=" ".join(annotation["classes"]), + ) + ) def draw_misc_annotation(self, obj, classes): # We have to decide whether this should come from Blender or from IFC. @@ -292,51 +339,50 @@ class SvgWriter(): for polygon in obj.data.polygons: points = [obj.data.vertices[v] for v in polygon.vertices] projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points] - d = ' '.join(['L {} {}'.format( - (x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale) - for p in projected_points]) - d = 'M{}'.format(d[1:]) - path = self.svg.add(self.svg.path(d=d, class_=' '.join(classes))) + d = " ".join( + [ + "L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale) + for p in projected_points + ] + ) + d = "M{}".format(d[1:]) + path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes))) def get_attribute_classes(self, obj): - classes = [obj.name.split('/')[0]] + classes = [obj.name.split("/")[0]] for slot in obj.material_slots: if slot.material: - classes.append('material-{}'.format( - re.sub('[^0-9a-zA-Z]+', '', slot.material.name) - )) - result = obj.BIMObjectProperties.attributes.get('GlobalId') + classes.append("material-{}".format(re.sub("[^0-9a-zA-Z]+", "", slot.material.name))) + result = obj.BIMObjectProperties.attributes.get("GlobalId") if not result: result = obj.BIMObjectProperties.attributes.add() - result.name = 'GlobalId' + result.name = "GlobalId" result.string_value = ifcopenshell.guid.new() - classes.append('globalid-{}'.format(result.string_value)) + classes.append("globalid-{}".format(result.string_value)) for attribute in self.ifc_cutter.attributes: result = self.get_obj_value(obj, attribute) if result: - classes.append('{}-{}'.format( - re.sub('[^0-9a-zA-Z]+', '', attribute), - re.sub('[^0-9a-zA-Z]+', '', result) - )) + classes.append( + "{}-{}".format(re.sub("[^0-9a-zA-Z]+", "", attribute), re.sub("[^0-9a-zA-Z]+", "", result)) + ) return classes def get_obj_value(self, obj, key): # This is a duplicate implementation of the IFC selector key in Blender # In the future if all this becomes purely IFC based this can be deleted - if '.' in key \ - and key.split('.')[0] == 'type': + if "." in key and key.split(".")[0] == "type": try: obj = obj.BIMObjectProperties.relating_type except: return - key = '.'.join(key.split('.')[1:]) + key = ".".join(key.split(".")[1:]) result = obj.BIMObjectProperties.attributes.get(key) if result: return result.string_value - elif key == 'Name': - return obj.name.split('/')[1] - elif '.' in key: - pset_name, prop = key.split('.') + elif key == "Name": + return obj.name.split("/")[1] + elif "." in key: + pset_name, prop = key.split(".") pset = obj.BIMObjectProperties.psets.get(pset_name) if not pset: pset = obj.BIMObjectProperties.qtos.get(pset_name) @@ -353,18 +399,21 @@ class SvgWriter(): obj, data = obj_data - classes.extend(['annotation']) + classes.extend(["annotation"]) matrix_world = obj.matrix_world if isinstance(data, bpy.types.Curve): for spline in data.splines: points = self.get_spline_points(spline) projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points] - d = ' '.join(['L {} {}'.format( - (x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale) - for p in projected_points]) - d = 'M{}'.format(d[1:]) - path = self.svg.add(self.svg.path(d=d, class_=' '.join(classes))) + d = " ".join( + [ + "L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale) + for p in projected_points + ] + ) + d = "M{}".format(d[1:]) + path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes))) elif isinstance(data, bpy.types.Mesh): self.draw_edge_annotation(obj, classes) @@ -380,8 +429,9 @@ class SvgWriter(): start = Vector(((x_offset + v0.x), (y_offset - v0.y))) end = Vector(((x_offset + v1.x), (y_offset - v1.y))) vector = end - start - line = self.svg.add(self.svg.line(start=tuple(start * self.scale), - end=tuple(end * self.scale), class_=' '.join(classes))) + line = self.svg.add( + self.svg.line(start=tuple(start * self.scale), end=tuple(end * self.scale), class_=" ".join(classes)) + ) def draw_text_annotations(self): x_offset = self.raw_width / 2 @@ -393,53 +443,61 @@ class SvgWriter(): local_x_axis = text_obj.matrix_world.to_quaternion() @ Vector((1, 0, 0)) projected_x_axis = self.project_point_onto_camera(text_obj.location + local_x_axis) - angle = math.degrees((Vector((x_offset + projected_x_axis.x, y_offset - - projected_x_axis.y)) - text_position).angle_signed(Vector((1, 0)))) + angle = math.degrees( + (Vector((x_offset + projected_x_axis.x, y_offset - projected_x_axis.y)) - text_position).angle_signed( + Vector((1, 0)) + ) + ) - transform = 'rotate({}, {}, {})'.format( + transform = "rotate({}, {}, {})".format( angle, (text_position * self.scale)[0], (text_position * self.scale)[1], ) - if text_obj.data.BIMTextProperties.symbol != 'None': - self.svg.add(self.svg.use( - '#{}'.format(text_obj.data.BIMTextProperties.symbol), - insert=tuple(text_position * self.scale) - )) + if text_obj.data.BIMTextProperties.symbol != "None": + self.svg.add( + self.svg.use( + "#{}".format(text_obj.data.BIMTextProperties.symbol), insert=tuple(text_position * self.scale) + ) + ) - if text_obj.data.align_x == 'CENTER': - text_anchor = 'middle' - elif text_obj.data.align_x == 'RIGHT': - text_anchor = 'end' + if text_obj.data.align_x == "CENTER": + text_anchor = "middle" + elif text_obj.data.align_x == "RIGHT": + text_anchor = "end" else: - text_anchor = 'start' + text_anchor = "start" - if text_obj.data.align_y == 'CENTER': - alignment_baseline = 'middle' - elif text_obj.data.align_y == 'TOP': - alignment_baseline = 'hanging' + if text_obj.data.align_y == "CENTER": + alignment_baseline = "middle" + elif text_obj.data.align_y == "TOP": + alignment_baseline = "hanging" else: - alignment_baseline = 'baseline' + alignment_baseline = "baseline" text_body = text_obj.data.body if text_obj.name in self.ifc_cutter.template_variables: text_body = pystache.render(text_body, self.ifc_cutter.template_variables[text_obj.name]) - for line_number, text_line in enumerate(text_body.split('\n')): - self.svg.add(self.svg.text( - text_line, - insert=tuple((text_position * self.scale) + Vector((0, 3.5*line_number))), - class_=' '.join(self.get_attribute_classes(text_obj)), - **{ - 'font-size': annotation.Annotator.get_svg_text_size(text_obj.data.BIMTextProperties.font_size), - 'font-family': 'OpenGost Type B TT', - 'text-anchor': text_anchor, - 'alignment-baseline': alignment_baseline, - 'dominant-baseline': alignment_baseline, - 'transform': transform - } - )) + for line_number, text_line in enumerate(text_body.split("\n")): + self.svg.add( + self.svg.text( + text_line, + insert=tuple((text_position * self.scale) + Vector((0, 3.5 * line_number))), + class_=" ".join(self.get_attribute_classes(text_obj)), + **{ + "font-size": annotation.Annotator.get_svg_text_size( + text_obj.data.BIMTextProperties.font_size + ), + "font-family": "OpenGost Type B TT", + "text-anchor": text_anchor, + "alignment-baseline": alignment_baseline, + "dominant-baseline": alignment_baseline, + "transform": transform, + } + ) + ) def draw_break_annotations(self, break_obj): x_offset = self.raw_width / 2 @@ -449,36 +507,41 @@ class SvgWriter(): for polygon in break_obj.data.polygons: points = [break_obj.data.vertices[v] for v in polygon.vertices] projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points] - d = ' '.join(['L {} {}'.format( - (x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale) - for p in projected_points]) - d = 'M{}'.format(d[1:]) - path = self.svg.add(self.svg.path(d=d, class_=' '.join(['break']))) + d = " ".join( + [ + "L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale) + for p in projected_points + ] + ) + d = "M{}".format(d[1:]) + path = self.svg.add(self.svg.path(d=d, class_=" ".join(["break"]))) break_points = [ projected_points[0], - ((projected_points[1]-projected_points[0])/2)+projected_points[0], - projected_points[1]] - d = ' '.join(['L {} {}'.format( - (x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale) - for p in break_points]) - d = 'M{}'.format(d[1:]) - path = self.svg.add(self.svg.path(d=d, class_=' '.join(['breakline']))) + ((projected_points[1] - projected_points[0]) / 2) + projected_points[0], + projected_points[1], + ] + d = " ".join( + ["L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale) for p in break_points] + ) + d = "M{}".format(d[1:]) + path = self.svg.add(self.svg.path(d=d, class_=" ".join(["breakline"]))) def draw_dimension_annotations(self, dimension_obj, text_override=None): matrix_world = dimension_obj.matrix_world for spline in dimension_obj.data.splines: points = self.get_spline_points(spline) for i, p in enumerate(points): - if i+1 >= len(points): + if i + 1 >= len(points): continue v0_global = matrix_world @ points[i].co.xyz - v1_global = matrix_world @ points[i+1].co.xyz + v1_global = matrix_world @ points[i + 1].co.xyz self.draw_dimension_annotation(v0_global, v1_global, text_override) def draw_measureit_arch_dimension_annotations(self): try: import MeasureIt_ARCH.measureit_arch_external_utils + coords = MeasureIt_ARCH.measureit_arch_external_utils.blenderBIM_get_coords(bpy.context) except: return @@ -486,7 +549,7 @@ class SvgWriter(): self.draw_dimension_annotation(Vector(coord[0]), Vector(coord[1])) def draw_dimension_annotation(self, v0_global, v1_global, text_override=None): - classes = ['annotation', 'dimension'] + classes = ["annotation", "dimension"] x_offset = self.raw_width / 2 y_offset = self.raw_height / 2 v0 = self.project_point_onto_camera(v0_global) @@ -498,71 +561,74 @@ class SvgWriter(): perpendicular = Vector((vector.y, -vector.x)).normalized() dimension = (v1_global - v0_global).length dimension = helper.format_distance(dimension) - sheet_dimension = ((end*self.scale) - (start*self.scale)).length - if sheet_dimension < 5: # annotation can't fit + sheet_dimension = ((end * self.scale) - (start * self.scale)).length + if sheet_dimension < 5: # annotation can't fit # offset text to right of marker text_position = (end * self.scale) + perpendicular + (3 * vector.normalized()) else: text_position = (mid * self.scale) + perpendicular rotation = math.degrees(vector.angle_signed(Vector((1, 0)))) - line = self.svg.add(self.svg.line(start=tuple(start * self.scale), - end=tuple(end * self.scale), class_=' '.join(classes))) - line['marker-start'] = 'url(#dimension-marker-start)' - line['marker-end'] = 'url(#dimension-marker-end)' + line = self.svg.add( + self.svg.line(start=tuple(start * self.scale), end=tuple(end * self.scale), class_=" ".join(classes)) + ) + line["marker-start"] = "url(#dimension-marker-start)" + line["marker-end"] = "url(#dimension-marker-end)" if text_override is not None: text = text_override else: text = str(dimension) - self.svg.add(self.svg.text(text, insert=tuple(text_position), **{ - 'transform': 'rotate({} {} {})'.format( - rotation, - text_position.x, - text_position.y - ), - 'font-size': annotation.Annotator.get_svg_text_size(2.5), - 'font-family': 'OpenGost Type B TT', - 'text-anchor': 'middle' - })) + self.svg.add( + self.svg.text( + text, + insert=tuple(text_position), + **{ + "transform": "rotate({} {} {})".format(rotation, text_position.x, text_position.y), + "font-size": annotation.Annotator.get_svg_text_size(2.5), + "font-family": "OpenGost Type B TT", + "text-anchor": "middle", + } + ) + ) def project_point_onto_camera(self, point): return self.ifc_cutter.camera_obj.matrix_world.inverted() @ geometry.intersect_line_plane( point.xyz, - point.xyz-Vector(self.ifc_cutter.section_box['projection']), + point.xyz - Vector(self.ifc_cutter.section_box["projection"]), self.ifc_cutter.camera_obj.location, - Vector(self.ifc_cutter.section_box['projection']) - ) + Vector(self.ifc_cutter.section_box["projection"]), + ) def get_spline_points(self, spline): return spline.bezier_points if spline.bezier_points else spline.points def draw_cut_polygons(self): for polygon in self.ifc_cutter.cut_polygons: - self.draw_polygon(polygon, 'cut') + self.draw_polygon(polygon, "cut") def draw_polyline(self, element, position): - classes = self.get_classes(element['raw'], position) - exp = BRepTools.BRepTools_WireExplorer(element['geometry']) + classes = self.get_classes(element["raw"], position) + exp = BRepTools.BRepTools_WireExplorer(element["geometry"]) points = [] while exp.More(): point = BRep.BRep_Tool.Pnt(exp.CurrentVertex()) points.append((point.X() * self.scale, -point.Y() * self.scale)) exp.Next() - self.svg.add(self.svg.polyline(points=points, class_=' '.join(classes))) + self.svg.add(self.svg.polyline(points=points, class_=" ".join(classes))) def draw_line(self, element, position): - classes = self.get_classes(element['raw'], position) - exp = TopExp.TopExp_Explorer(element['geometry'], TopAbs.TopAbs_VERTEX) + classes = self.get_classes(element["raw"], position) + exp = TopExp.TopExp_Explorer(element["geometry"], TopAbs.TopAbs_VERTEX) points = [] while exp.More(): point = BRep.BRep_Tool.Pnt(topods.Vertex(exp.Current())) points.append((point.X() * self.scale, -point.Y() * self.scale)) exp.Next() - self.svg.add(self.svg.line(start=points[0], end=points[1], class_=' '.join(classes))) + self.svg.add(self.svg.line(start=points[0], end=points[1], class_=" ".join(classes))) def draw_polygon(self, polygon, position): - points = [(p[0] * self.scale, p[1] * self.scale) for p in polygon['points']] - if 'classes' in polygon['metadata']: - classes = ' '.join(polygon['metadata']['classes']) + points = [(p[0] * self.scale, p[1] * self.scale) for p in polygon["points"]] + if "classes" in polygon["metadata"]: + classes = " ".join(polygon["metadata"]["classes"]) else: - classes = '' + classes = "" self.svg.add(self.svg.polygon(points=points, class_=classes)) diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 10da7149ee..dfca8e7705 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -4,11 +4,11 @@ from bpy.props import StringProperty class BIM_PT_object(Panel): - bl_label = 'IFC Object' - bl_idname = 'BIM_PT_object' - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' - bl_context = 'object' + bl_label = "IFC Object" + bl_idname = "BIM_PT_object" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "object" @classmethod def poll(cls, context): @@ -28,87 +28,87 @@ class BIM_PT_object(Panel): if bim_properties.ifc_predefined_type: row = layout.row() row.prop(bim_properties, "ifc_predefined_type") - if bim_properties.ifc_predefined_type == 'USERDEFINED': + if bim_properties.ifc_predefined_type == "USERDEFINED": row = layout.row() row.prop(bim_properties, "ifc_userdefined_type") row = layout.row(align=True) - if 'Ifc' not in context.active_object.name: + if "Ifc" not in context.active_object.name: op = row.operator("bim.assign_class") else: - op = row.operator("bim.assign_class", text='Reassign IFC Class') + op = row.operator("bim.assign_class", text="Reassign IFC Class") op.object_name = context.active_object.name - op = row.operator("bim.unassign_class", icon='X', text='') + op = row.operator("bim.unassign_class", icon="X", text="") op.object_name = context.active_object.name - if 'Ifc' not in context.active_object.name: + if "Ifc" not in context.active_object.name: return layout.label(text="Attributes:") row = layout.row(align=True) - row.prop(props, 'applicable_attributes', text='') - row.operator('bim.add_attribute') + row.prop(props, "applicable_attributes", text="") + row.operator("bim.add_attribute") for index, attribute in enumerate(props.attributes): row = layout.row(align=True) - row.prop(attribute, 'name', text='') - row.prop(attribute, 'string_value', text='') - if attribute.name == 'GlobalId': - row.operator('bim.generate_global_id', icon='FILE_REFRESH', text='') - op = row.operator('bim.copy_attributes_to_selection', icon='COPYDOWN', text='') - op.prop_base = 'BIMObjectProperties.attributes' + row.prop(attribute, "name", text="") + row.prop(attribute, "string_value", text="") + if attribute.name == "GlobalId": + row.operator("bim.generate_global_id", icon="FILE_REFRESH", text="") + op = row.operator("bim.copy_attributes_to_selection", icon="COPYDOWN", text="") + op.prop_base = "BIMObjectProperties.attributes" op.prop_name = attribute.name op.collection_element = True - row.operator('bim.remove_attribute', icon='X', text='').attribute_index = index + row.operator("bim.remove_attribute", icon="X", text="").attribute_index = index row = layout.row() - row.prop(props, 'attributes') + row.prop(props, "attributes") - if 'IfcSite/' in context.active_object.name or 'IfcBuilding/' in context.active_object.name: + if "IfcSite/" in context.active_object.name or "IfcBuilding/" in context.active_object.name: self.draw_addresses_ui() row = layout.row(align=True) - row.prop(props, 'relating_type') - row.operator('bim.select_similar_type', icon='RESTRICT_SELECT_OFF', text='') + row.prop(props, "relating_type") + row.operator("bim.select_similar_type", icon="RESTRICT_SELECT_OFF", text="") row = layout.row() - row.prop(props, 'relating_structure') + row.prop(props, "relating_structure") row = layout.row() - row.prop(props, 'material_type') + row.prop(props, "material_type") def draw_addresses_ui(self): layout = self.layout layout.label(text="Address:") address = bpy.context.active_object.BIMObjectProperties.address row = layout.row() - row.prop(address, 'purpose') - if address.purpose == 'USERDEFINED': + row.prop(address, "purpose") + if address.purpose == "USERDEFINED": row = layout.row() - row.prop(address, 'user_defined_purpose') + row.prop(address, "user_defined_purpose") row = layout.row() - row.prop(address, 'description') + row.prop(address, "description") row = layout.row() - row.prop(address, 'internal_location') + row.prop(address, "internal_location") row = layout.row() - row.prop(address, 'address_lines') + row.prop(address, "address_lines") row = layout.row() - row.prop(address, 'postal_box') + row.prop(address, "postal_box") row = layout.row() - row.prop(address, 'town') + row.prop(address, "town") row = layout.row() - row.prop(address, 'region') + row.prop(address, "region") row = layout.row() - row.prop(address, 'postal_code') + row.prop(address, "postal_code") row = layout.row() - row.prop(address, 'country') + row.prop(address, "country") class BIM_PT_object_psets(Panel): - bl_label = 'IFC Object Property Sets' - bl_idname = 'BIM_PT_object_psets' - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' - bl_context = 'object' + bl_label = "IFC Object Property Sets" + bl_idname = "BIM_PT_object_psets" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "object" @classmethod def poll(cls, context): @@ -120,28 +120,29 @@ class BIM_PT_object_psets(Panel): layout = self.layout props = context.active_object.BIMObjectProperties row = layout.row(align=True) - row.prop(props, 'pset_name', text='') - row.operator('bim.add_pset') + row.prop(props, "pset_name", text="") + row.operator("bim.add_pset") for index, pset in enumerate(props.psets): row = layout.row(align=True) - row.prop(pset, 'name', text='') - row.operator('bim.remove_pset', icon='X', text='').pset_index = index + row.prop(pset, "name", text="") + row.operator("bim.remove_pset", icon="X", text="").pset_index = index for prop in pset.properties: row = layout.row(align=True) - row.prop(prop, 'name', text='') - row.prop(prop, 'string_value', text='') - op = row.operator('bim.copy_property_to_selection', icon='COPYDOWN', text='') + row.prop(prop, "name", text="") + row.prop(prop, "string_value", text="") + op = row.operator("bim.copy_property_to_selection", icon="COPYDOWN", text="") op.pset_name = pset.name op.prop_name = prop.name op.prop_value = prop.string_value + class BIM_PT_object_qto(Panel): - bl_label = 'IFC Object Quantity Sets' - bl_idname = 'BIM_PT_object_qto' - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' - bl_context = 'object' + bl_label = "IFC Object Quantity Sets" + bl_idname = "BIM_PT_object_qto" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "object" @classmethod def poll(cls, context): @@ -153,42 +154,44 @@ class BIM_PT_object_qto(Panel): layout = self.layout props = context.active_object.BIMObjectProperties row = layout.row(align=True) - row.prop(props, 'qto_name', text='') - row.operator('bim.add_qto') + row.prop(props, "qto_name", text="") + row.operator("bim.add_qto") for index, qto in enumerate(props.qtos): row = layout.row(align=True) - row.prop(qto, 'name', text='') - row.operator('bim.remove_qto', icon='X', text='').index = index + row.prop(qto, "name", text="") + row.operator("bim.remove_qto", icon="X", text="").index = index for index2, prop in enumerate(qto.properties): row = layout.row(align=True) - row.prop(prop, 'name', text='') - row.prop(prop, 'string_value', text='') - if 'length' in prop.name.lower() \ - or 'width' in prop.name.lower() \ - or 'height' in prop.name.lower() \ - or 'depth' in prop.name.lower() \ - or 'perimeter' in prop.name.lower(): - op = row.operator('bim.guess_quantity', icon='IPO_EASE_IN_OUT', text='') + row.prop(prop, "name", text="") + row.prop(prop, "string_value", text="") + if ( + "length" in prop.name.lower() + or "width" in prop.name.lower() + or "height" in prop.name.lower() + or "depth" in prop.name.lower() + or "perimeter" in prop.name.lower() + ): + op = row.operator("bim.guess_quantity", icon="IPO_EASE_IN_OUT", text="") op.qto_index = index op.prop_index = index2 - elif 'area' in prop.name.lower(): - op = row.operator('bim.guess_quantity', icon='MESH_CIRCLE', text='') + elif "area" in prop.name.lower(): + op = row.operator("bim.guess_quantity", icon="MESH_CIRCLE", text="") op.qto_index = index op.prop_index = index2 - elif 'volume' in prop.name.lower(): - op = row.operator('bim.guess_quantity', icon='SPHERE', text='') + elif "volume" in prop.name.lower(): + op = row.operator("bim.guess_quantity", icon="SPHERE", text="") op.qto_index = index op.prop_index = index2 class BIM_PT_object_structural(Panel): - bl_label = 'IFC Structural Relationships' - bl_idname = 'BIM_PT_object_structural' - bl_options = {'DEFAULT_CLOSED'} - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' - bl_context = 'object' + bl_label = "IFC Structural Relationships" + bl_idname = "BIM_PT_object_structural" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "object" @classmethod def poll(cls, context): @@ -200,27 +203,27 @@ class BIM_PT_object_structural(Panel): layout = self.layout props = context.active_object.BIMObjectProperties row = layout.row() - row.prop(props, 'has_boundary_condition') + row.prop(props, "has_boundary_condition") if bpy.context.active_object.BIMObjectProperties.has_boundary_condition: row = layout.row() - row.prop(props.boundary_condition, 'name') + row.prop(props.boundary_condition, "name") for index, attribute in enumerate(props.boundary_condition.attributes): row = layout.row(align=True) - row.prop(attribute, 'name', text='') - row.prop(attribute, 'string_value', text='') + row.prop(attribute, "name", text="") + row.prop(attribute, "string_value", text="") row = layout.row() - row.prop(props, 'structural_member_connection') + row.prop(props, "structural_member_connection") class BIM_PT_document_information(Panel): - bl_label = 'IFC Documents' - bl_idname = 'BIM_PT_document_information' - bl_options = {'DEFAULT_CLOSED'} - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' - bl_context = 'scene' + bl_label = "IFC Documents" + bl_idname = "BIM_PT_document_information" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" def draw(self, context): layout = self.layout @@ -228,78 +231,93 @@ class BIM_PT_document_information(Panel): props = context.scene.BIMProperties row = layout.row() - row.operator('bim.add_document_information') + row.operator("bim.add_document_information") if props.document_information: - layout.template_list('BIM_UL_document_information', '', props, 'document_information', props, 'active_document_information_index') + layout.template_list( + "BIM_UL_document_information", + "", + props, + "document_information", + props, + "active_document_information_index", + ) if props.active_document_information_index < len(props.document_information): information = props.document_information[props.active_document_information_index] row = layout.row(align=True) - row.prop(information, 'name') - row.operator('bim.remove_document_information', icon='X', text='').index = props.active_document_information_index + row.prop(information, "name") + row.operator( + "bim.remove_document_information", icon="X", text="" + ).index = props.active_document_information_index row = layout.row() - row.prop(information, 'human_name') + row.prop(information, "human_name") row = layout.row() - row.prop(information, 'description') + row.prop(information, "description") row = layout.row() - row.prop(information, 'location') + row.prop(information, "location") row = layout.row() - row.prop(information, 'purpose') + row.prop(information, "purpose") row = layout.row() - row.prop(information, 'intended_use') + row.prop(information, "intended_use") row = layout.row() - row.prop(information, 'scope') + row.prop(information, "scope") row = layout.row() - row.prop(information, 'revision') + row.prop(information, "revision") row = layout.row() - row.prop(information, 'creation_time') + row.prop(information, "creation_time") row = layout.row() - row.prop(information, 'last_revision_time') + row.prop(information, "last_revision_time") row = layout.row() - row.prop(information, 'electronic_format') + row.prop(information, "electronic_format") row = layout.row() - row.prop(information, 'valid_from') + row.prop(information, "valid_from") row = layout.row() - row.prop(information, 'valid_until') + row.prop(information, "valid_until") row = layout.row() - row.prop(information, 'confidentiality') + row.prop(information, "confidentiality") row = layout.row() - row.prop(information, 'status') + row.prop(information, "status") row = layout.row() - row.operator('bim.add_document_reference') + row.operator("bim.add_document_reference") if props.document_references: - layout.template_list('BIM_UL_document_references', '', props, 'document_references', props, 'active_document_reference_index') + layout.template_list( + "BIM_UL_document_references", "", props, "document_references", props, "active_document_reference_index" + ) if props.active_document_reference_index < len(props.document_references): reference = props.document_references[props.active_document_reference_index] row = layout.row(align=True) - row.prop(reference, 'name') - row.operator('bim.remove_document_reference', icon='X', text='').index = props.active_document_reference_index + row.prop(reference, "name") + row.operator( + "bim.remove_document_reference", icon="X", text="" + ).index = props.active_document_reference_index row = layout.row() - row.prop(reference, 'human_name') + row.prop(reference, "human_name") row = layout.row() - row.prop(reference, 'location') + row.prop(reference, "location") row = layout.row() - row.prop(reference, 'description') + row.prop(reference, "description") row = layout.row(align=True) - row.prop(reference, 'referenced_document') - row.operator('bim.assign_document_information', icon='LINKED', text='').index = props.active_document_reference_index + row.prop(reference, "referenced_document") + row.operator( + "bim.assign_document_information", icon="LINKED", text="" + ).index = props.active_document_reference_index row = layout.row(align=True) - row.operator('bim.assign_document_reference', text='Assign Reference') - row.operator('bim.unassign_document_reference', text='Unassign Reference') + row.operator("bim.assign_document_reference", text="Assign Reference") + row.operator("bim.unassign_document_reference", text="Unassign Reference") class BIM_PT_constraints(Panel): - bl_label = 'IFC Constraints' - bl_idname = 'BIM_PT_constraints' - bl_options = {'DEFAULT_CLOSED'} - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' - bl_context = 'scene' + bl_label = "IFC Constraints" + bl_idname = "BIM_PT_constraints" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" def draw(self, context): layout = self.layout @@ -307,43 +325,43 @@ class BIM_PT_constraints(Panel): props = context.scene.BIMProperties row = layout.row() - row.operator('bim.add_constraint') + row.operator("bim.add_constraint") if props.constraints: - layout.template_list('BIM_UL_constraints', '', props, 'constraints', props, 'active_constraint_index') + layout.template_list("BIM_UL_constraints", "", props, "constraints", props, "active_constraint_index") if props.active_constraint_index < len(props.constraints): constraint = props.constraints[props.active_constraint_index] row = layout.row(align=True) - row.prop(constraint, 'name') - row.operator('bim.remove_constraint', icon='X', text='').index = props.active_constraint_index + row.prop(constraint, "name") + row.operator("bim.remove_constraint", icon="X", text="").index = props.active_constraint_index row = layout.row() - row.prop(constraint, 'description') + row.prop(constraint, "description") row = layout.row() - row.prop(constraint, 'constraint_grade') - if constraint.constraint_grade == 'USERDEFINED': + row.prop(constraint, "constraint_grade") + if constraint.constraint_grade == "USERDEFINED": row = layout.row() - row.prop(constraint, 'user_defined_grade') + row.prop(constraint, "user_defined_grade") row = layout.row() - row.prop(constraint, 'constraint_source') + row.prop(constraint, "constraint_source") row = layout.row() - row.prop(constraint, 'objective_qualifier') - if constraint.objective_qualifier == 'USERDEFINED': + row.prop(constraint, "objective_qualifier") + if constraint.objective_qualifier == "USERDEFINED": row = layout.row() - row.prop(constraint, 'user_defined_qualifier') + row.prop(constraint, "user_defined_qualifier") row = layout.row(align=True) - row.operator('bim.assign_constraint', text='Assign Constraint') - row.operator('bim.unassign_constraint', text='Unassign Constraint') + row.operator("bim.assign_constraint", text="Assign Constraint") + row.operator("bim.unassign_constraint", text="Unassign Constraint") class BIM_PT_documents(Panel): - bl_label = 'IFC Documents' - bl_idname = 'BIM_PT_documents' - bl_options = {'DEFAULT_CLOSED'} - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' - bl_context = 'object' + bl_label = "IFC Documents" + bl_idname = "BIM_PT_documents" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "object" def draw(self, context): layout = self.layout @@ -354,37 +372,41 @@ class BIM_PT_documents(Panel): layout.label(text="No documents found") row = layout.row() - row.operator('bim.fetch_object_passport') + row.operator("bim.fetch_object_passport") if props.document_references: - layout.template_list('BIM_UL_document_references', '', props, 'document_references', props, 'active_document_reference_index') + layout.template_list( + "BIM_UL_document_references", "", props, "document_references", props, "active_document_reference_index" + ) if props.active_document_reference_index < len(props.document_references): reference = props.document_references[props.active_document_reference_index] row = layout.row(align=True) - row.prop(reference, 'name') + row.prop(reference, "name") if reference.name in bpy.context.scene.BIMProperties.document_references: reference = bpy.context.scene.BIMProperties.document_references[reference.name] - row.operator('bim.remove_object_document_reference', icon='X', text='').index = props.active_document_reference_index + row.operator( + "bim.remove_object_document_reference", icon="X", text="" + ).index = props.active_document_reference_index row = layout.row() - row.prop(reference, 'human_name') + row.prop(reference, "human_name") row = layout.row() - row.prop(reference, 'location') + row.prop(reference, "location") row = layout.row() - row.prop(reference, 'description') + row.prop(reference, "description") row = layout.row() - row.prop(reference, 'referenced_document') + row.prop(reference, "referenced_document") else: layout.label(text="Reference is invalid") class BIM_PT_constraint_relations(Panel): - bl_label = 'IFC Constraints' - bl_idname = 'BIM_PT_constraint_relations' - bl_options = {'DEFAULT_CLOSED'} - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' - bl_context = 'object' + bl_label = "IFC Constraints" + bl_idname = "BIM_PT_constraint_relations" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "object" def draw(self, context): layout = self.layout @@ -395,40 +417,42 @@ class BIM_PT_constraint_relations(Panel): layout.label(text="No constraints found") if props.constraints: - layout.template_list('BIM_UL_constraints', '', props, 'constraints', props, 'active_constraint_index') + layout.template_list("BIM_UL_constraints", "", props, "constraints", props, "active_constraint_index") if props.active_constraint_index < len(props.constraints): constraint = props.constraints[props.active_constraint_index] row = layout.row(align=True) - row.prop(constraint, 'name') + row.prop(constraint, "name") if constraint.name in bpy.context.scene.BIMProperties.constraints: constraint = bpy.context.scene.BIMProperties.constraints[constraint.name] - row.operator('bim.remove_object_constraint', icon='X', text='').index = props.active_constraint_index + row.operator( + "bim.remove_object_constraint", icon="X", text="" + ).index = props.active_constraint_index row = layout.row() - row.prop(constraint, 'description') + row.prop(constraint, "description") row = layout.row() - row.prop(constraint, 'constraint_grade') - if constraint.constraint_grade == 'USERDEFINED': + row.prop(constraint, "constraint_grade") + if constraint.constraint_grade == "USERDEFINED": row = layout.row() - row.prop(constraint, 'user_defined_grade') + row.prop(constraint, "user_defined_grade") row = layout.row() - row.prop(constraint, 'constraint_source') + row.prop(constraint, "constraint_source") row = layout.row() - row.prop(constraint, 'objective_qualifier') - if constraint.objective_qualifier == 'USERDEFINED': + row.prop(constraint, "objective_qualifier") + if constraint.objective_qualifier == "USERDEFINED": row = layout.row() - row.prop(constraint, 'user_defined_qualifier') + row.prop(constraint, "user_defined_qualifier") else: layout.label(text="Constraint is invalid") class BIM_PT_representations(Panel): - bl_label = 'IFC Representations' - bl_idname = 'BIM_PT_representations' - bl_options = {'DEFAULT_CLOSED'} - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' - bl_context = 'object' + bl_label = "IFC Representations" + bl_idname = "BIM_PT_representations" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "object" def draw(self, context): layout = self.layout @@ -438,32 +462,32 @@ class BIM_PT_representations(Panel): layout.label(text="No representations found") row = layout.row(align=True) - row.prop(bpy.context.scene.BIMProperties, 'available_contexts', text='') - row.prop(bpy.context.scene.BIMProperties, 'available_subcontexts', text='') - row.prop(bpy.context.scene.BIMProperties, 'available_target_views', text='') - op = row.operator('bim.switch_context', icon='ADD', text='') + row.prop(bpy.context.scene.BIMProperties, "available_contexts", text="") + row.prop(bpy.context.scene.BIMProperties, "available_subcontexts", text="") + row.prop(bpy.context.scene.BIMProperties, "available_target_views", text="") + op = row.operator("bim.switch_context", icon="ADD", text="") op.has_target_context = False for index, subcontext in enumerate(props.representation_contexts): row = layout.row(align=True) - row.prop(subcontext, 'context', text='') - row.prop(subcontext, 'name', text='') - row.prop(subcontext, 'target_view', text='') - op = row.operator('bim.switch_context', icon='OUTLINER_DATA_MESH', text='') + row.prop(subcontext, "context", text="") + row.prop(subcontext, "name", text="") + row.prop(subcontext, "target_view", text="") + op = row.operator("bim.switch_context", icon="OUTLINER_DATA_MESH", text="") op.has_target_context = True op.context_name = subcontext.context op.subcontext_name = subcontext.name op.target_view_name = subcontext.target_view - row.operator('bim.remove_context', icon='X', text='').index = index + row.operator("bim.remove_context", icon="X", text="").index = index class BIM_PT_classification_references(Panel): - bl_label = 'IFC Classification References' - bl_idname = 'BIM_PT_classification_references' - bl_options = {'DEFAULT_CLOSED'} - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' - bl_context = 'object' + bl_label = "IFC Classification References" + bl_idname = "BIM_PT_classification_references" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "object" def draw(self, context): layout = self.layout @@ -475,77 +499,77 @@ class BIM_PT_classification_references(Panel): for index, classification in enumerate(props.classifications): row = layout.row(align=True) - row.prop(classification, 'name') - row.operator('bim.remove_classification_reference', icon='X', text='').classification_index = index + row.prop(classification, "name") + row.operator("bim.remove_classification_reference", icon="X", text="").classification_index = index row = layout.row(align=True) - row.prop(classification, 'human_name') + row.prop(classification, "human_name") row = layout.row(align=True) - row.prop(classification, 'location') + row.prop(classification, "location") row = layout.row(align=True) - row.prop(classification, 'description') + row.prop(classification, "description") row = layout.row(align=True) - row.prop(classification, 'referenced_source') + row.prop(classification, "referenced_source") class BIM_PT_psets(Panel): - bl_label = 'IFC Property Sets' - bl_idname = 'BIM_PT_psets' - bl_options = {'DEFAULT_CLOSED'} - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' - bl_context = 'scene' + bl_label = "IFC Property Sets" + bl_idname = "BIM_PT_psets" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" def draw(self, context): layout = self.layout props = context.scene.BIMProperties row = layout.row(align=True) - row.prop(props, 'pset_template_files', text='') + row.prop(props, "pset_template_files", text="") row = layout.row(align=True) - row.prop(props, 'property_set_templates', text='') - row.operator('bim.add_property_set_template', text='', icon='ADD') - row.operator('bim.remove_property_set_template', text='', icon='PANEL_CLOSE') - row.operator('bim.edit_property_set_template', text='', icon='IMPORT') - row.operator('bim.save_property_set_template', text='', icon='EXPORT') + row.prop(props, "property_set_templates", text="") + row.operator("bim.add_property_set_template", text="", icon="ADD") + row.operator("bim.remove_property_set_template", text="", icon="PANEL_CLOSE") + row.operator("bim.edit_property_set_template", text="", icon="IMPORT") + row.operator("bim.save_property_set_template", text="", icon="EXPORT") row = layout.row(align=True) - row.prop(props.active_property_set_template, 'name') + row.prop(props.active_property_set_template, "name") row = layout.row(align=True) - row.prop(props.active_property_set_template, 'description') + row.prop(props.active_property_set_template, "description") row = layout.row(align=True) - row.prop(props.active_property_set_template, 'template_type') + row.prop(props.active_property_set_template, "template_type") row = layout.row(align=True) - row.prop(props.active_property_set_template, 'applicable_entity') + row.prop(props.active_property_set_template, "applicable_entity") - layout.label(text='Property Templates:') + layout.label(text="Property Templates:") row = layout.row(align=True) - row.operator('bim.add_property_template') + row.operator("bim.add_property_template") for index, template in enumerate(props.property_templates): row = layout.row(align=True) - row.prop(template, 'name', text='') - row.prop(template, 'description', text='') - row.prop(template, 'primary_measure_type', text='') - row.operator('bim.remove_property_template', icon='X', text='').index = index + row.prop(template, "name", text="") + row.prop(template, "description", text="") + row.prop(template, "primary_measure_type", text="") + row.operator("bim.remove_property_template", icon="X", text="").index = index class BIM_PT_classifications(Panel): - bl_label = 'IFC Classifications' - bl_idname = 'BIM_PT_classifications' - bl_options = {'DEFAULT_CLOSED'} - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' - bl_context = 'scene' + bl_label = "IFC Classifications" + bl_idname = "BIM_PT_classifications" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" def draw(self, context): layout = self.layout props = context.scene.BIMProperties row = layout.row(align=True) - row.prop(props, "classification", text='') - row.operator("bim.add_classification", text='', icon='ADD') + row.prop(props, "classification", text="") + row.operator("bim.add_classification", text="", icon="ADD") if context.scene.BIMProperties.classification_references.raw_data: context.scene.BIMProperties.classification_references.draw_stub(context, layout) @@ -554,7 +578,7 @@ class BIM_PT_classifications(Panel): row.operator("bim.unassign_classification") else: row = layout.row(align=True) - row.operator('bim.load_classification').is_file = True + row.operator("bim.load_classification").is_file = True if not props.classifications: return @@ -563,36 +587,40 @@ class BIM_PT_classifications(Panel): for index, classification in enumerate(props.classifications): row = layout.row(align=True) - row.prop(classification, 'name') - row.operator('bim.load_classification', icon='IMPORT', text='').classification_index = index - row.operator('bim.remove_classification', icon='X', text='').classification_index = index + row.prop(classification, "name") + row.operator("bim.load_classification", icon="IMPORT", text="").classification_index = index + row.operator("bim.remove_classification", icon="X", text="").classification_index = index row = layout.row(align=True) - row.prop(classification, 'source') + row.prop(classification, "source") row = layout.row(align=True) - row.prop(classification, 'edition') + row.prop(classification, "edition") row = layout.row(align=True) - row.prop(classification, 'edition_date') + row.prop(classification, "edition_date") row = layout.row(align=True) - row.prop(classification, 'description') + row.prop(classification, "description") row = layout.row(align=True) - row.prop(classification, 'location') + row.prop(classification, "location") row = layout.row(align=True) - row.prop(classification, 'reference_tokens') + row.prop(classification, "reference_tokens") row = layout.row() - row.prop(props, 'classifications') + row.prop(props, "classifications") + class BIM_PT_mesh(Panel): - bl_label = 'IFC Representations' - bl_idname = 'BIM_PT_mesh' - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' - bl_context = 'data' + bl_label = "IFC Representations" + bl_idname = "BIM_PT_mesh" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "data" @classmethod def poll(cls, context): - return context.active_object is not None and context.active_object.type == "MESH" and \ - hasattr(context.active_object.data, "BIMMeshProperties") + return ( + context.active_object is not None + and context.active_object.type == "MESH" + and hasattr(context.active_object.data, "BIMMeshProperties") + ) def draw(self, context): if not context.active_object.data: @@ -601,59 +629,59 @@ class BIM_PT_mesh(Panel): props = context.active_object.data.BIMMeshProperties row = layout.row(align=True) - row.operator('bim.push_representation') + row.operator("bim.push_representation") row = layout.row() - row.prop(props, 'geometry_type') + row.prop(props, "geometry_type") row = layout.row() - row.prop(props, 'ifc_definition') + row.prop(props, "ifc_definition") layout.label(text="IFC Parameters:") row = layout.row() - row.operator('bim.get_representation_ifc_parameters') + row.operator("bim.get_representation_ifc_parameters") for index, ifc_parameter in enumerate(props.ifc_parameters): row = layout.row(align=True) - row.prop(ifc_parameter, 'name', text='') - row.prop(ifc_parameter, 'step_id') - row.prop(ifc_parameter, 'index') - row.prop(ifc_parameter, 'value', text='') - row.operator('bim.update_ifc_representation', icon='FILE_REFRESH', text='').index = index + row.prop(ifc_parameter, "name", text="") + row.prop(ifc_parameter, "step_id") + row.prop(ifc_parameter, "index") + row.prop(ifc_parameter, "value", text="") + row.operator("bim.update_ifc_representation", icon="FILE_REFRESH", text="").index = index row = layout.row() - row.prop(props, 'presentation_layer') + row.prop(props, "presentation_layer") row = layout.row() - row.prop(props, 'is_parametric') + row.prop(props, "is_parametric") row = layout.row() - row.prop(props, 'is_native') + row.prop(props, "is_native") row = layout.row() - row.prop(props, 'is_swept_solid') + row.prop(props, "is_swept_solid") row = layout.row() - row.operator('bim.add_swept_solid') + row.operator("bim.add_swept_solid") for index, swept_solid in enumerate(props.swept_solids): row = layout.row(align=True) - row.prop(swept_solid, 'name', text='') - row.operator('bim.remove_swept_solid', icon='X', text='').index = index + row.prop(swept_solid, "name", text="") + row.operator("bim.remove_swept_solid", icon="X", text="").index = index row = layout.row() sub = row.row(align=True) - sub.operator('bim.assign_swept_solid_outer_curve').index = index - sub.operator('bim.select_swept_solid_outer_curve', icon='RESTRICT_SELECT_OFF', text='').index = index + sub.operator("bim.assign_swept_solid_outer_curve").index = index + sub.operator("bim.select_swept_solid_outer_curve", icon="RESTRICT_SELECT_OFF", text="").index = index sub = row.row(align=True) - sub.operator('bim.add_swept_solid_inner_curve').index = index - sub.operator('bim.select_swept_solid_inner_curves', icon='RESTRICT_SELECT_OFF', text='').index = index + sub.operator("bim.add_swept_solid_inner_curve").index = index + sub.operator("bim.select_swept_solid_inner_curves", icon="RESTRICT_SELECT_OFF", text="").index = index row = layout.row(align=True) - row.operator('bim.assign_swept_solid_extrusion').index = index - row.operator('bim.select_swept_solid_extrusion', icon='RESTRICT_SELECT_OFF', text='').index = index + row.operator("bim.assign_swept_solid_extrusion").index = index + row.operator("bim.select_swept_solid_extrusion", icon="RESTRICT_SELECT_OFF", text="").index = index row = layout.row() - row.prop(props, 'swept_solids') + row.prop(props, "swept_solids") class BIM_PT_material(Panel): - bl_label = 'IFC Materials' - bl_idname = 'BIM_PT_material' - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' - bl_context = 'material' + bl_label = "IFC Materials" + bl_idname = "BIM_PT_material" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "material" @classmethod def poll(cls, context): @@ -665,111 +693,111 @@ class BIM_PT_material(Panel): props = context.active_object.active_material.BIMMaterialProperties layout = self.layout row = layout.row() - row.prop(props, 'is_external') + row.prop(props, "is_external") row = layout.row(align=True) - row.prop(props, 'location') - row.operator('bim.select_external_material_dir', icon="FILE_FOLDER", text="") + row.prop(props, "location") + row.operator("bim.select_external_material_dir", icon="FILE_FOLDER", text="") row = layout.row() - row.prop(props, 'identification') + row.prop(props, "identification") row = layout.row() - row.prop(props, 'name') + row.prop(props, "name") row = layout.row() - row.operator('bim.fetch_external_material') + row.operator("bim.fetch_external_material") layout.label(text="Attributes:") row = layout.row(align=True) - row.prop(props, 'applicable_attributes', text='') - row.operator('bim.add_material_attribute') + row.prop(props, "applicable_attributes", text="") + row.operator("bim.add_material_attribute") for index, attribute in enumerate(props.attributes): row = layout.row(align=True) - row.prop(attribute, 'name', text='') - row.prop(attribute, 'string_value', text='') - row.operator('bim.remove_material_attribute', icon='X', text='').attribute_index = index + row.prop(attribute, "name", text="") + row.prop(attribute, "string_value", text="") + row.operator("bim.remove_material_attribute", icon="X", text="").attribute_index = index row = layout.row() - row.prop(props, 'attributes') + row.prop(props, "attributes") layout.label(text="Property Sets:") row = layout.row(align=True) - row.prop(props, 'available_material_psets', text='') - row.operator('bim.add_material_pset') + row.prop(props, "available_material_psets", text="") + row.operator("bim.add_material_pset") for index, pset in enumerate(props.psets): row = layout.row(align=True) - row.prop(pset, 'name', text='') - row.operator('bim.remove_material_pset', icon='X', text='').pset_index = index - op = row.operator('bim.copy_property_to_selection', icon='COPYDOWN', text='') + row.prop(pset, "name", text="") + row.operator("bim.remove_material_pset", icon="X", text="").pset_index = index + op = row.operator("bim.copy_property_to_selection", icon="COPYDOWN", text="") for prop in pset.properties: row = layout.row(align=True) - row.prop(prop, 'name', text='') - row.prop(prop, 'string_value', text='') - op = row.operator('bim.copy_property_to_selection', icon='COPYDOWN', text='') + row.prop(prop, "name", text="") + row.prop(prop, "string_value", text="") + op = row.operator("bim.copy_property_to_selection", icon="COPYDOWN", text="") op.pset_name = pset.name op.prop_name = prop.name op.prop_value = prop.string_value row = layout.row() - row.prop(props, 'psets', text='') + row.prop(props, "psets", text="") - if context.active_object.BIMObjectProperties.material_type == 'IfcMaterialProfileSet': + if context.active_object.BIMObjectProperties.material_type == "IfcMaterialProfileSet": layout.label(text="Profile Definition:") row = layout.row() - row.prop(props, 'profile_def') + row.prop(props, "profile_def") for index, attribute in enumerate(props.profile_attributes): row = layout.row(align=True) - row.prop(attribute, 'name', text='') - row.prop(attribute, 'string_value', text='') + row.prop(attribute, "name", text="") + row.prop(attribute, "string_value", text="") class BIM_PT_gis(Panel): - bl_label = 'IFC Georeferencing' + bl_label = "IFC Georeferencing" bl_idname = "BIM_PT_gis" - bl_options = {'DEFAULT_CLOSED'} - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" bl_context = "scene" def draw(self, context): layout = self.layout layout.use_property_split = True scene = context.scene - layout.row().prop(scene.BIMProperties, 'has_georeferencing') + layout.row().prop(scene.BIMProperties, "has_georeferencing") layout.label(text="Map Conversion:") - layout.row().prop(scene.MapConversion, 'eastings') - layout.row().prop(scene.MapConversion, 'northings') - layout.row().prop(scene.MapConversion, 'orthogonal_height') - layout.row().prop(scene.MapConversion, 'x_axis_abscissa') - layout.row().prop(scene.MapConversion, 'x_axis_ordinate') - layout.row().prop(scene.MapConversion, 'scale') + layout.row().prop(scene.MapConversion, "eastings") + layout.row().prop(scene.MapConversion, "northings") + layout.row().prop(scene.MapConversion, "orthogonal_height") + layout.row().prop(scene.MapConversion, "x_axis_abscissa") + layout.row().prop(scene.MapConversion, "x_axis_ordinate") + layout.row().prop(scene.MapConversion, "scale") layout.label(text="Target CRS:") - layout.row().prop(scene.TargetCRS, 'name') - layout.row().prop(scene.TargetCRS, 'description') - layout.row().prop(scene.TargetCRS, 'geodetic_datum') - layout.row().prop(scene.TargetCRS, 'vertical_datum') - layout.row().prop(scene.TargetCRS, 'map_projection') - layout.row().prop(scene.TargetCRS, 'map_zone') - layout.row().prop(scene.TargetCRS, 'map_unit') + layout.row().prop(scene.TargetCRS, "name") + layout.row().prop(scene.TargetCRS, "description") + layout.row().prop(scene.TargetCRS, "geodetic_datum") + layout.row().prop(scene.TargetCRS, "vertical_datum") + layout.row().prop(scene.TargetCRS, "map_projection") + layout.row().prop(scene.TargetCRS, "map_zone") + layout.row().prop(scene.TargetCRS, "map_unit") row = layout.row(align=True) - row.operator('bim.convert_local_to_global') + row.operator("bim.convert_local_to_global") - if hasattr(bpy.context.scene, 'sun_pos_properties'): + if hasattr(bpy.context.scene, "sun_pos_properties"): row = layout.row(align=True) - row.operator('bim.get_north_offset') - row.operator('bim.set_north_offset') + row.operator("bim.get_north_offset") + row.operator("bim.set_north_offset") class BIM_PT_drawings(Panel): bl_label = "SVG Drawings" bl_idname = "BIM_PT_drawings" - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' - bl_context = 'output' + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "output" def draw(self, context): layout = self.layout @@ -777,32 +805,32 @@ class BIM_PT_drawings(Panel): props = bpy.context.scene.DocProperties row = layout.row(align=True) - row.operator('bim.add_drawing') - row.operator('bim.refresh_drawing_list', icon='FILE_REFRESH', text='') + row.operator("bim.add_drawing") + row.operator("bim.refresh_drawing_list", icon="FILE_REFRESH", text="") if props.drawings: if props.active_drawing_index < len(props.drawings): - op = row.operator('bim.open_view', icon='URL', text='') + op = row.operator("bim.open_view", icon="URL", text="") op.view = props.drawings[props.active_drawing_index].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') + 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") row = layout.row() - row.operator('bim.add_ifc_file') + row.operator("bim.add_ifc_file") for index, ifc_file in enumerate(props.ifc_files): row = layout.row(align=True) - row.prop(ifc_file, 'name', text='IFC #{}'.format(index + 1)) - row.operator('bim.select_doc_ifc_file', icon='FILE_FOLDER', text='') - row.operator('bim.remove_ifc_file', icon='X', text='').index = index + row.prop(ifc_file, "name", text="IFC #{}".format(index + 1)) + row.operator("bim.select_doc_ifc_file", icon="FILE_FOLDER", text="") + row.operator("bim.remove_ifc_file", icon="X", text="").index = index class BIM_PT_schedules(Panel): bl_label = "ODS Schedules" bl_idname = "BIM_PT_schedules" - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' - bl_context = 'output' + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "output" def draw(self, context): layout = self.layout @@ -810,53 +838,53 @@ class BIM_PT_schedules(Panel): props = bpy.context.scene.DocProperties row = layout.row(align=True) - row.operator('bim.add_schedule') + row.operator("bim.add_schedule") if props.schedules: - row.operator('bim.build_schedule', icon='LINENUMBERS_ON', text='') - row.operator('bim.remove_schedule', icon='X', text='').index = props.active_schedule_index + row.operator("bim.build_schedule", icon="LINENUMBERS_ON", text="") + row.operator("bim.remove_schedule", icon="X", text="").index = props.active_schedule_index - layout.template_list('BIM_UL_generic', '', props, 'schedules', props, 'active_schedule_index') + 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.operator('bim.select_schedule_file', icon='FILE_FOLDER', text='') + row.prop(props.schedules[props.active_schedule_index], "file") + row.operator("bim.select_schedule_file", icon="FILE_FOLDER", text="") class BIM_PT_sheets(Panel): bl_label = "SVG Sheets" bl_idname = "BIM_PT_sheets" - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' - bl_context = 'output' + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "output" def draw(self, context): layout = self.layout props = bpy.context.scene.DocProperties row = layout.row(align=True) - row.prop(props, 'titleblock', text='') - row.operator('bim.add_sheet') + row.prop(props, "titleblock", text="") + row.operator("bim.add_sheet") if props.sheets: - row.operator('bim.open_sheet', icon='URL', text='') - row.operator('bim.remove_sheet', icon='X', text='').index = props.active_sheet_index + row.operator("bim.open_sheet", icon="URL", text="") + row.operator("bim.remove_sheet", icon="X", text="").index = props.active_sheet_index - layout.template_list('BIM_UL_generic', '', props, 'sheets', props, 'active_sheet_index') + layout.template_list("BIM_UL_generic", "", props, "sheets", props, "active_sheet_index") row = layout.row(align=True) - row.operator('bim.add_drawing_to_sheet') - row.operator('bim.add_schedule_to_sheet') + row.operator("bim.add_drawing_to_sheet") + row.operator("bim.add_schedule_to_sheet") row = layout.row() - row.operator('bim.create_sheets') + row.operator("bim.create_sheets") class BIM_PT_section_plane(Panel): bl_label = "Temporary Section Cutaways" bl_idname = "BIM_PT_section_plane" - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' - bl_context = 'output' + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "output" def draw(self, context): layout = self.layout @@ -864,33 +892,32 @@ class BIM_PT_section_plane(Panel): props = bpy.context.scene.BIMProperties row = layout.row() - row.prop(props, 'should_section_selected_objects') + row.prop(props, "should_section_selected_objects") row = layout.row() - row.prop(props, 'section_plane_colour') + row.prop(props, "section_plane_colour") row = layout.row(align=True) - row.operator('bim.add_section_plane') - row.operator('bim.remove_section_plane') + row.operator("bim.add_section_plane") + row.operator("bim.remove_section_plane") class BIM_PT_camera(Panel): bl_label = "Drawing Generation" bl_idname = "BIM_PT_camera" - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' - bl_context = 'data' + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "data" @classmethod def poll(cls, context): engine = context.engine - return context.camera and \ - hasattr(context.active_object.data, "BIMCameraProperties") + return context.camera and hasattr(context.active_object.data, "BIMCameraProperties") def draw(self, context): layout = self.layout - if '/' not in context.active_object.name: + if "/" not in context.active_object.name: layout.label(text="This is not a BIM camera.") return @@ -901,93 +928,93 @@ class BIM_PT_camera(Panel): layout.label(text="Generation Options:") row = layout.row() - row.prop(dprops, 'should_recut') + row.prop(dprops, "should_recut") row = layout.row() - row.prop(dprops, 'should_recut_selected') + row.prop(dprops, "should_recut_selected") row = layout.row() - row.prop(dprops, 'should_extract') + row.prop(dprops, "should_extract") row = layout.row() - row.prop(props, 'is_nts') + row.prop(props, "is_nts") row = layout.row() - row.operator('bim.generate_references') + row.operator("bim.generate_references") row = layout.row() - row.operator('bim.resize_text') + row.operator("bim.resize_text") row = layout.row() - row.prop(props, 'target_view') + row.prop(props, "target_view") row = layout.row() - row.prop(props, 'cut_objects') - if props.cut_objects == 'CUSTOM': + row.prop(props, "cut_objects") + if props.cut_objects == "CUSTOM": row = layout.row() - row.prop(props, 'cut_objects_custom') + row.prop(props, "cut_objects_custom") row = layout.row() - row.prop(props, 'raster_x') + row.prop(props, "raster_x") row = layout.row() - row.prop(props, 'raster_y') + row.prop(props, "raster_y") row = layout.row() - row.prop(props, 'diagram_scale') - if props.diagram_scale == 'CUSTOM': + row.prop(props, "diagram_scale") + if props.diagram_scale == "CUSTOM": row = layout.row() - row.prop(props, 'custom_diagram_scale') + row.prop(props, "custom_diagram_scale") layout.label(text="Drawing Styles:") row = layout.row(align=True) - row.operator('bim.add_drawing_style') + row.operator("bim.add_drawing_style") if dprops.drawing_styles: - layout.template_list('BIM_UL_generic', '', dprops, 'drawing_styles', props, 'active_drawing_style_index') + layout.template_list("BIM_UL_generic", "", dprops, "drawing_styles", props, "active_drawing_style_index") if props.active_drawing_style_index < len(dprops.drawing_styles): drawing_style = dprops.drawing_styles[props.active_drawing_style_index] row = layout.row(align=True) - row.prop(drawing_style, 'name') - row.operator('bim.remove_drawing_style', icon='X', text='').index = props.active_drawing_style_index + row.prop(drawing_style, "name") + row.operator("bim.remove_drawing_style", icon="X", text="").index = props.active_drawing_style_index row = layout.row() - row.prop(drawing_style, 'render_type') + row.prop(drawing_style, "render_type") row = layout.row(align=True) - row.prop(drawing_style, 'vector_style') - row.operator('bim.edit_vector_style', text='', icon='GREASEPENCIL') + row.prop(drawing_style, "vector_style") + row.operator("bim.edit_vector_style", text="", icon="GREASEPENCIL") row = layout.row(align=True) - row.prop(drawing_style, 'include_query') + row.prop(drawing_style, "include_query") row = layout.row(align=True) - row.prop(drawing_style, 'exclude_query') + row.prop(drawing_style, "exclude_query") row = layout.row() - row.operator('bim.add_drawing_style_attribute') + row.operator("bim.add_drawing_style_attribute") for index, attribute in enumerate(drawing_style.attributes): row = layout.row(align=True) - row.prop(attribute, 'name', text='') - row.operator('bim.remove_drawing_style_attribute', icon='X', text='').index = index + row.prop(attribute, "name", text="") + row.operator("bim.remove_drawing_style_attribute", icon="X", text="").index = index row = layout.row(align=True) - row.operator('bim.save_drawing_style') - row.operator('bim.activate_drawing_style') + row.operator("bim.save_drawing_style") + row.operator("bim.activate_drawing_style") row = layout.row(align=True) - row.operator('bim.cut_section', text='Create Drawing') - op = row.operator('bim.open_view', icon='URL', text='') - op.view = context.active_object.name.split('/')[1] + row.operator("bim.cut_section", text="Create Drawing") + op = row.operator("bim.open_view", icon="URL", text="") + op.view = context.active_object.name.split("/")[1] class BIM_PT_text(Panel): bl_label = "Text Paper Space" bl_idname = "BIM_PT_text" - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' - bl_context = 'data' + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "data" @classmethod def poll(cls, context): - return (type(context.curve) is bpy.types.TextCurve) + return type(context.curve) is bpy.types.TextCurve def draw(self, context): layout = self.layout @@ -995,32 +1022,32 @@ class BIM_PT_text(Panel): props = context.active_object.data.BIMTextProperties row = layout.row() - row.operator('bim.propagate_text_data') + row.operator("bim.propagate_text_data") row = layout.row() - row.prop(props, 'font_size') + row.prop(props, "font_size") row = layout.row() - row.prop(props, 'symbol') + row.prop(props, "symbol") row = layout.row() - row.prop(props, 'related_element') + row.prop(props, "related_element") row = layout.row() - row.operator('bim.add_variable') + row.operator("bim.add_variable") for index, variable in enumerate(props.variables): row = layout.row(align=True) - row.prop(variable, 'name') - row.operator('bim.remove_variable', icon='X', text='').index = index + row.prop(variable, "name") + row.operator("bim.remove_variable", icon="X", text="").index = index row = layout.row() - row.prop(variable, 'prop_key') + row.prop(variable, "prop_key") class BIM_PT_owner(Panel): bl_label = "IFC Owner History" bl_idname = "BIM_PT_owner" - bl_options = {'DEFAULT_CLOSED'} - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" bl_context = "scene" def draw(self, context): @@ -1034,21 +1061,21 @@ class BIM_PT_owner(Panel): layout.label(text="No people found.") else: row = layout.row() - row.prop(props, 'person') + row.prop(props, "person") if not props.organisation: layout.label(text="No organisations found.") else: row = layout.row() - row.prop(props, 'organisation') + row.prop(props, "organisation") class BIM_PT_people(Panel): bl_label = "IFC People" bl_idname = "BIM_PT_people" - bl_options = {'DEFAULT_CLOSED'} - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" bl_context = "scene" def draw(self, context): @@ -1058,39 +1085,38 @@ class BIM_PT_people(Panel): props = context.scene.BIMProperties row = layout.row() - row.operator('bim.add_person') + row.operator("bim.add_person") if props.people: - layout.template_list('BIM_UL_generic', '', - props, 'people', props, 'active_person_index') + layout.template_list("BIM_UL_generic", "", props, "people", props, "active_person_index") if props.active_person_index < len(props.people): person = props.people[props.active_person_index] row = layout.row() - row.prop(person, 'name') - row.operator('bim.remove_person', icon='X', text='').index = props.active_person_index + row.prop(person, "name") + row.operator("bim.remove_person", icon="X", text="").index = props.active_person_index row = layout.row() - row.prop(person, 'family_name') + row.prop(person, "family_name") row = layout.row() - row.prop(person, 'given_name') + row.prop(person, "given_name") row = layout.row() - row.prop(person, 'middle_names') + row.prop(person, "middle_names") row = layout.row() - row.prop(person, 'prefix_titles') + row.prop(person, "prefix_titles") row = layout.row() - row.prop(person, 'suffix_titles') + row.prop(person, "suffix_titles") layout.label(text="Roles:") - draw_roles_ui(layout, person, 'person') + draw_roles_ui(layout, person, "person") layout.label(text="Addresses:") - draw_addresses_ui(layout, person, 'person') + draw_addresses_ui(layout, person, "person") class BIM_PT_organisations(Panel): bl_label = "IFC Organisations" bl_idname = "BIM_PT_organisations" - bl_options = {'DEFAULT_CLOSED'} - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" bl_context = "scene" def draw(self, context): @@ -1100,102 +1126,99 @@ class BIM_PT_organisations(Panel): props = context.scene.BIMProperties row = layout.row() - row.operator('bim.add_organisation') + row.operator("bim.add_organisation") if props.organisations: - layout.template_list('BIM_UL_generic', '', - props, 'organisations', props, 'active_organisation_index') + layout.template_list("BIM_UL_generic", "", props, "organisations", props, "active_organisation_index") if props.active_organisation_index < len(props.organisations): organisation = props.organisations[props.active_organisation_index] row = layout.row() - row.prop(organisation, 'name') - row.operator('bim.remove_organisation', icon='X', text='').index = props.active_organisation_index + row.prop(organisation, "name") + row.operator("bim.remove_organisation", icon="X", text="").index = props.active_organisation_index row = layout.row() - row.prop(organisation, 'description') + row.prop(organisation, "description") layout.label(text="Roles:") - draw_roles_ui(layout, organisation, 'organisation') + draw_roles_ui(layout, organisation, "organisation") layout.label(text="Addresses:") - draw_addresses_ui(layout, organisation, 'organisation') + draw_addresses_ui(layout, organisation, "organisation") def draw_roles_ui(layout, parent, parent_type): row = layout.row() - row.operator(f'bim.add_{parent_type}_role') + row.operator(f"bim.add_{parent_type}_role") if parent.roles: - layout.template_list('BIM_UL_generic', '', - parent, 'roles', parent, 'active_role_index') + layout.template_list("BIM_UL_generic", "", parent, "roles", parent, "active_role_index") if parent.active_role_index < len(parent.roles): role = parent.roles[parent.active_role_index] row = layout.row() - row.prop(role, 'name') - row.operator(f'bim.remove_{parent_type}_role', icon='X', text='').index = parent.active_role_index - if role.name == 'USERDEFINED': + row.prop(role, "name") + row.operator(f"bim.remove_{parent_type}_role", icon="X", text="").index = parent.active_role_index + if role.name == "USERDEFINED": row = layout.row() - row.prop(role, 'user_defined_role') + row.prop(role, "user_defined_role") row = layout.row() - row.prop(role, 'description') + row.prop(role, "description") def draw_addresses_ui(layout, parent, parent_type): row = layout.row() - row.operator(f'bim.add_{parent_type}_address') + row.operator(f"bim.add_{parent_type}_address") if parent.addresses: - layout.template_list('BIM_UL_generic', '', - parent, 'addresses', parent, 'active_address_index') + layout.template_list("BIM_UL_generic", "", parent, "addresses", parent, "active_address_index") if parent.active_address_index < len(parent.addresses): address = parent.addresses[parent.active_address_index] row = layout.row() - row.prop(address, 'name') - row.operator(f'bim.remove_{parent_type}_address', icon='X', text='').index = parent.active_address_index + row.prop(address, "name") + row.operator(f"bim.remove_{parent_type}_address", icon="X", text="").index = parent.active_address_index row = layout.row() - row.prop(address, 'purpose') - if address.purpose == 'USERDEFINED': + row.prop(address, "purpose") + if address.purpose == "USERDEFINED": row = layout.row() - row.prop(address, 'user_defined_purpose') + row.prop(address, "user_defined_purpose") row = layout.row() - row.prop(address, 'description') + row.prop(address, "description") - if 'IfcPostalAddress' in address.name: + if "IfcPostalAddress" in address.name: row = layout.row() - row.prop(address, 'internal_location') + row.prop(address, "internal_location") row = layout.row() - row.prop(address, 'address_lines') + row.prop(address, "address_lines") row = layout.row() - row.prop(address, 'postal_box') + row.prop(address, "postal_box") row = layout.row() - row.prop(address, 'town') + row.prop(address, "town") row = layout.row() - row.prop(address, 'region') + row.prop(address, "region") row = layout.row() - row.prop(address, 'postal_code') + row.prop(address, "postal_code") row = layout.row() - row.prop(address, 'country') - elif 'IfcTelecomAddress' in address.name: + row.prop(address, "country") + elif "IfcTelecomAddress" in address.name: row = layout.row() - row.prop(address, 'telephone_numbers') + row.prop(address, "telephone_numbers") row = layout.row() - row.prop(address, 'fascimile_numbers') + row.prop(address, "fascimile_numbers") row = layout.row() - row.prop(address, 'pager_number') + row.prop(address, "pager_number") row = layout.row() - row.prop(address, 'electronic_mail_addresses') + row.prop(address, "electronic_mail_addresses") row = layout.row() - row.prop(address, 'www_home_page_url') + row.prop(address, "www_home_page_url") row = layout.row() - row.prop(address, 'messaging_ids') + row.prop(address, "messaging_ids") class BIM_PT_context(Panel): bl_label = "IFC Geometric Representation Contexts" bl_idname = "BIM_PT_context" - bl_options = {'DEFAULT_CLOSED'} - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" bl_context = "scene" def draw(self, context): @@ -1204,31 +1227,33 @@ class BIM_PT_context(Panel): scene = context.scene props = scene.BIMProperties - for context in ['model', 'plan']: + for context in ["model", "plan"]: row = layout.row(align=True) - row.prop(props, f'has_{context}_context') + row.prop(props, f"has_{context}_context") - if not getattr(props, f'has_{context}_context'): + if not getattr(props, f"has_{context}_context"): continue layout.label(text="Geometric Representation Subcontexts:") row = layout.row(align=True) - row.prop(props, 'available_subcontexts', text='') - row.prop(props, 'available_target_views', text='') - row.operator('bim.add_subcontext', icon='ADD', text='').context = context + row.prop(props, "available_subcontexts", text="") + row.prop(props, "available_target_views", text="") + row.operator("bim.add_subcontext", icon="ADD", text="").context = context - for subcontext_index, subcontext in enumerate(getattr(props, '{}_subcontexts'.format(context))): + for subcontext_index, subcontext in enumerate(getattr(props, "{}_subcontexts".format(context))): row = layout.row(align=True) - row.prop(subcontext, 'name', text='') - row.prop(subcontext, 'target_view', text='') - row.operator('bim.remove_subcontext', icon='X', text='').indexes = '{}-{}'.format(context, subcontext_index) + row.prop(subcontext, "name", text="") + row.prop(subcontext, "target_view", text="") + row.operator("bim.remove_subcontext", icon="X", text="").indexes = "{}-{}".format( + context, subcontext_index + ) class BIM_PT_bim(Panel): bl_label = "Building Information Modeling" bl_idname = "BIM_PT_bim" - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" bl_context = "scene" def draw(self, context): @@ -1241,7 +1266,7 @@ class BIM_PT_bim(Panel): layout.label(text="System Setup:") row = layout.row() - row.operator('bim.quick_project_setup') + row.operator("bim.quick_project_setup") row = layout.row(align=True) row.prop(bim_properties, "schema_dir") @@ -1252,13 +1277,13 @@ class BIM_PT_bim(Panel): row.operator("bim.select_data_dir", icon="FILE_FOLDER", text="") row = layout.row(align=True) - row.prop(bim_properties, 'ifc_file') - row.operator('bim.reload_ifc_file', icon='FILE_REFRESH', text='') - row.operator('bim.validate_ifc_file', icon='CHECKMARK', text='') - row.operator('bim.select_ifc_file', icon='FILE_FOLDER', text='') + row.prop(bim_properties, "ifc_file") + row.operator("bim.reload_ifc_file", icon="FILE_REFRESH", text="") + row.operator("bim.validate_ifc_file", icon="CHECKMARK", text="") + row.operator("bim.select_ifc_file", icon="FILE_FOLDER", text="") row = layout.row(align=True) - row.prop(bim_properties, 'ifc_cache') + row.prop(bim_properties, "ifc_cache") layout.label(text="IFC Categorisation:") @@ -1269,14 +1294,14 @@ class BIM_PT_bim(Panel): if bim_properties.ifc_predefined_type: row = layout.row() row.prop(bim_properties, "ifc_predefined_type") - if bim_properties.ifc_predefined_type == 'USERDEFINED': + if bim_properties.ifc_predefined_type == "USERDEFINED": row = layout.row() row.prop(bim_properties, "ifc_userdefined_type") row = layout.row(align=True) op = row.operator("bim.assign_class") - op.object_name = '' - op = row.operator("bim.unassign_class", icon='X', text='') - op.object_name = '' + op.object_name = "" + op = row.operator("bim.unassign_class", icon="X", text="") + op.object_name = "" row = layout.row(align=True) row.operator("bim.select_class") @@ -1300,9 +1325,9 @@ class BIM_PT_bim(Panel): class BIM_PT_search(Panel): bl_label = "IFC Search" bl_idname = "BIM_PT_search" - bl_options = {'DEFAULT_CLOSED'} - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" bl_context = "scene" def draw(self, context): @@ -1312,37 +1337,37 @@ class BIM_PT_search(Panel): props = scene.BIMProperties row = layout.row() - row.prop(props, 'search_regex') + row.prop(props, "search_regex") row = layout.row() - row.prop(props, 'search_ignorecase') + row.prop(props, "search_ignorecase") layout.label(text="Global ID:") row = layout.row(align=True) - row.prop(props, 'global_id', text='') - row.operator('bim.select_global_id', text='', icon='VIEWZOOM') + row.prop(props, "global_id", text="") + row.operator("bim.select_global_id", text="", icon="VIEWZOOM") layout.label(text="Attribute:") row = layout.row(align=True) - row.prop(props, 'search_attribute_name', text='') - row.prop(props, 'search_attribute_value', text='') - row.operator('bim.select_attribute', text='', icon='VIEWZOOM') - row.operator('bim.colour_by_attribute', text='', icon='BRUSH_DATA') + row.prop(props, "search_attribute_name", text="") + row.prop(props, "search_attribute_value", text="") + row.operator("bim.select_attribute", text="", icon="VIEWZOOM") + row.operator("bim.colour_by_attribute", text="", icon="BRUSH_DATA") layout.label(text="Pset:") row = layout.row(align=True) - row.prop(props, 'search_pset_name', text='') - row.prop(props, 'search_prop_name', text='') - row.prop(props, 'search_pset_value', text='') - row.operator('bim.select_pset', text='', icon='VIEWZOOM') - row.operator('bim.colour_by_pset', text='', icon='BRUSH_DATA') + row.prop(props, "search_pset_name", text="") + row.prop(props, "search_prop_name", text="") + row.prop(props, "search_pset_value", text="") + row.operator("bim.select_pset", text="", icon="VIEWZOOM") + row.operator("bim.colour_by_pset", text="", icon="BRUSH_DATA") class BIM_PT_ifccsv(Panel): bl_label = "IFC CSV Import/Export" bl_idname = "BIM_PT_ifccsv" - bl_options = {'DEFAULT_CLOSED'} - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" bl_context = "scene" def draw(self, context): @@ -1352,28 +1377,28 @@ class BIM_PT_ifccsv(Panel): props = scene.BIMProperties row = layout.row(align=True) - row.prop(props, 'ifc_selector') - row.operator('bim.eyedrop_ifccsv', icon='EYEDROPPER', text='') + row.prop(props, "ifc_selector") + row.operator("bim.eyedrop_ifccsv", icon="EYEDROPPER", text="") row = layout.row() - row.operator('bim.add_csv_attribute') + row.operator("bim.add_csv_attribute") for index, attribute in enumerate(props.csv_attributes): row = layout.row(align=True) - row.prop(attribute, 'name', text='') - row.operator('bim.remove_csv_attribute', icon='X', text='').index = index + row.prop(attribute, "name", text="") + row.operator("bim.remove_csv_attribute", icon="X", text="").index = index row = layout.row(align=True) - row.operator('bim.export_ifccsv', icon='EXPORT') - row.operator('bim.import_ifccsv', icon='IMPORT') + row.operator("bim.export_ifccsv", icon="EXPORT") + row.operator("bim.import_ifccsv", icon="IMPORT") class BIM_PT_bcf(Panel): bl_label = "BIM Collaboration Format (BCF)" bl_idname = "BIM_PT_bcf" - bl_options = {'DEFAULT_CLOSED'} - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" bl_context = "scene" def draw(self, context): @@ -1391,104 +1416,109 @@ class BIM_PT_bcf(Panel): row.operator("bim.get_bcf_topics") props = bpy.context.scene.BCFProperties - layout.template_list('BIM_UL_topics', '', props, 'topics', props, 'active_topic_index') + layout.template_list("BIM_UL_topics", "", props, "topics", props, "active_topic_index") row = layout.row() - row.prop(props, 'topic_description', text='') + row.prop(props, "topic_description", text="") row = layout.row() - row.prop(props, 'viewpoints') - row.operator('bim.activate_bcf_viewpoint', icon='SCENE', text='') + row.prop(props, "viewpoints") + row.operator("bim.activate_bcf_viewpoint", icon="SCENE", text="") row = layout.row() - row.prop(props, 'topic_type', text='Type') + row.prop(props, "topic_type", text="Type") row = layout.row() - row.prop(props, 'topic_status', text='Status') + row.prop(props, "topic_status", text="Status") row = layout.row() - row.prop(props, 'topic_priority', text='Priority') + row.prop(props, "topic_priority", text="Priority") row = layout.row() - row.prop(props, 'topic_stage', text='Stage') + row.prop(props, "topic_stage", text="Stage") row = layout.row() - row.prop(props, 'topic_creation_date', text='Date') + row.prop(props, "topic_creation_date", text="Date") row = layout.row() - row.prop(props, 'topic_creation_author', text='Author') + row.prop(props, "topic_creation_author", text="Author") row = layout.row() - row.prop(props, 'topic_modified_date', text='Modified On') + row.prop(props, "topic_modified_date", text="Modified On") row = layout.row() - row.prop(props, 'topic_modified_author', text='Modified By') + row.prop(props, "topic_modified_author", text="Modified By") row = layout.row() - row.prop(props, 'topic_assigned_to', text='Assigned To') + row.prop(props, "topic_assigned_to", text="Assigned To") row = layout.row() - row.prop(props, 'topic_due_date', text='Due Date') + row.prop(props, "topic_due_date", text="Due Date") layout.label(text="Header Files:") for index, f in enumerate(props.topic_files): row = layout.row() - row.prop(f, 'name', text='File {} Name'.format(index + 1)) + row.prop(f, "name", text="File {} Name".format(index + 1)) row = layout.row() - row.prop(f, 'reference', text='File {} URI'.format(index + 1)) + row.prop(f, "reference", text="File {} URI".format(index + 1)) if f.is_external: - row.operator('bim.open_bcf_file_reference', icon='URL', text='').data = index + row.operator("bim.open_bcf_file_reference", icon="URL", text="").data = index else: - row.operator('bim.open_bcf_file_reference', icon='FILE_FOLDER', text='').data = '{}/{}'.format( - props.topic_guid, index) + row.operator("bim.open_bcf_file_reference", icon="FILE_FOLDER", text="").data = "{}/{}".format( + props.topic_guid, index + ) row = layout.row() - row.prop(f, 'date', text='File {} Date'.format(index + 1)) + row.prop(f, "date", text="File {} Date".format(index + 1)) row = layout.row() - row.prop(f, 'ifc_project', text='File {} Project'.format(index + 1)) + row.prop(f, "ifc_project", text="File {} Project".format(index + 1)) row = layout.row() - row.prop(f, 'ifc_spatial', text='File {} Spatial'.format(index + 1)) + row.prop(f, "ifc_spatial", text="File {} Spatial".format(index + 1)) layout.label(text="Reference Links:") for index, label in enumerate(props.topic_links): row = layout.row() - row.prop(label, 'name', text='Link {}'.format(index + 1)) - row.operator('bim.open_bcf_reference_link', icon='URL', text='').index = index + row.prop(label, "name", text="Link {}".format(index + 1)) + row.operator("bim.open_bcf_reference_link", icon="URL", text="").index = index layout.label(text="Labels:") for index, label in enumerate(props.topic_labels): row = layout.row(align=True) - row.prop(label, 'name', text='') + row.prop(label, "name", text="") layout.label(text="BIM Snippet:") if props.topic_has_snippet: row = layout.row(align=True) - row.prop(props, 'topic_snippet_type') + row.prop(props, "topic_snippet_type") if props.topic_snippet_schema: - row.operator('bim.open_bcf_bim_snippet_schema', icon='URL', text='') + row.operator("bim.open_bcf_bim_snippet_schema", icon="URL", text="") row = layout.row(align=True) - row.prop(props, 'topic_snippet_reference') + row.prop(props, "topic_snippet_reference") if props.topic_snippet_is_external: - row.operator('bim.open_bcf_bim_snippet_reference', icon='URL', text='') + row.operator("bim.open_bcf_bim_snippet_reference", icon="URL", text="") else: - row.operator('bim.open_bcf_bim_snippet_reference', icon='FILE_FOLDER', text='').topic_guid = props.topic_guid + row.operator( + "bim.open_bcf_bim_snippet_reference", icon="FILE_FOLDER", text="" + ).topic_guid = props.topic_guid layout.label(text="Document References:") for index, doc in enumerate(props.topic_document_references): row = layout.row(align=True) - row.prop(doc, 'name', text=f'File {index+1} URI') + row.prop(doc, "name", text=f"File {index+1} URI") if doc.is_external: - row.operator('bim.open_bcf_document_reference', icon='URL', text='').data = '{}/{}'.format( - props.topic_guid, index) + row.operator("bim.open_bcf_document_reference", icon="URL", text="").data = "{}/{}".format( + props.topic_guid, index + ) else: - row.operator('bim.open_bcf_document_reference', icon='FILE_FOLDER', text='').data = '{}/{}'.format( - props.topic_guid, index) + row.operator("bim.open_bcf_document_reference", icon="FILE_FOLDER", text="").data = "{}/{}".format( + props.topic_guid, index + ) row = layout.row(align=True) - row.prop(doc, 'description', text=f'File {index+1} Description:') + row.prop(doc, "description", text=f"File {index+1} Description:") layout.label(text="Related Topics:") for topic in props.topic_related_topics: row = layout.row(align=True) - row.operator('bim.view_bcf_topic', text=topic.name).topic_guid = topic.guid + row.operator("bim.view_bcf_topic", text=topic.name).topic_guid = topic.guid class BIM_PT_qa(Panel): bl_label = "BIMTester Quality Auditing" bl_idname = "BIM_PT_qa" - bl_options = {'DEFAULT_CLOSED'} - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" bl_context = "scene" def draw(self, context): @@ -1540,12 +1570,13 @@ class BIM_PT_qa(Panel): row = layout.row() row.operator("bim.select_audited") + class BIM_PT_library(Panel): bl_label = "IFC BIM Server Library" bl_idname = "BIM_PT_library" - bl_options = {'DEFAULT_CLOSED'} - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" bl_context = "scene" def draw(self, context): @@ -1555,23 +1586,23 @@ class BIM_PT_library(Panel): scene = context.scene bim_properties = scene.BIMProperties - layout.row().prop(scene.BIMProperties, 'has_library') + layout.row().prop(scene.BIMProperties, "has_library") layout.label(text="Project Library:") - layout.row().prop(scene.BIMLibrary, 'location') + layout.row().prop(scene.BIMLibrary, "location") layout.row().operator("bim.fetch_library_information") - layout.row().prop(scene.BIMLibrary, 'name') - layout.row().prop(scene.BIMLibrary, 'version') - layout.row().prop(scene.BIMLibrary, 'version_date') - layout.row().prop(scene.BIMLibrary, 'description') + layout.row().prop(scene.BIMLibrary, "name") + layout.row().prop(scene.BIMLibrary, "version") + layout.row().prop(scene.BIMLibrary, "version_date") + layout.row().prop(scene.BIMLibrary, "description") class BIM_PT_diff(Panel): bl_label = "IFC Diff" bl_idname = "BIM_PT_diff" - bl_options = {'DEFAULT_CLOSED'} - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" bl_context = "scene" def draw(self, context): @@ -1600,15 +1631,15 @@ class BIM_PT_diff(Panel): row.prop(bim_properties, "diff_relationships") row = layout.row() - row.operator('bim.execute_ifc_diff') + row.operator("bim.execute_ifc_diff") class BIM_PT_cobie(Panel): bl_label = "IFC COBie" bl_idname = "BIM_PT_cobie" - bl_options = {'DEFAULT_CLOSED'} - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" bl_context = "scene" def draw(self, context): @@ -1632,20 +1663,20 @@ class BIM_PT_cobie(Panel): row.operator("bim.select_cobie_json_file", icon="FILE_FOLDER", text="") row = layout.row() - op = row.operator('bim.execute_ifc_cobie', text='CSV') - op.file_format = 'csv' - op = row.operator('bim.execute_ifc_cobie', text='ODS') - op.file_format = 'ods' - op = row.operator('bim.execute_ifc_cobie', text='XLSX') - op.file_format = 'xlsx' + op = row.operator("bim.execute_ifc_cobie", text="CSV") + op.file_format = "csv" + op = row.operator("bim.execute_ifc_cobie", text="ODS") + op.file_format = "ods" + op = row.operator("bim.execute_ifc_cobie", text="XLSX") + op.file_format = "xlsx" class BIM_PT_patch(Panel): bl_label = "IFC Patch" bl_idname = "BIM_PT_patch" - bl_options = {'DEFAULT_CLOSED'} - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" bl_context = "scene" def draw(self, context): @@ -1656,25 +1687,25 @@ class BIM_PT_patch(Panel): props = scene.BIMProperties row = layout.row() - row.prop(props, 'ifc_patch_recipes') + row.prop(props, "ifc_patch_recipes") row = layout.row(align=True) - row.prop(props, 'ifc_patch_input') + row.prop(props, "ifc_patch_input") row.operator("bim.select_ifc_patch_input", icon="FILE_FOLDER", text="") row = layout.row(align=True) - row.prop(props, 'ifc_patch_output') + row.prop(props, "ifc_patch_output") row.operator("bim.select_ifc_patch_output", icon="FILE_FOLDER", text="") row = layout.row() - row.prop(props, 'ifc_patch_args') + row.prop(props, "ifc_patch_args") row = layout.row() - op = row.operator('bim.execute_ifc_patch') + op = row.operator("bim.execute_ifc_patch") class BIM_PT_mvd(Panel): bl_label = "Model View Definitions (MVD)" bl_idname = "BIM_PT_mvd" - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" bl_context = "scene" def draw(self, context): @@ -1684,112 +1715,112 @@ class BIM_PT_mvd(Panel): bim_properties = scene.BIMProperties row = layout.row() - row.prop(bim_properties, 'export_schema') + row.prop(bim_properties, "export_schema") row = layout.row() - row.prop(bim_properties, 'export_json_version') + row.prop(bim_properties, "export_json_version") row = layout.row() - row.prop(bim_properties, 'ifc_import_filter') + row.prop(bim_properties, "ifc_import_filter") row = layout.row() - row.prop(bim_properties, 'ifc_selector') + row.prop(bim_properties, "ifc_selector") - layout.label(text='Custom MVD:') + layout.label(text="Custom MVD:") row = layout.row() - row.prop(bim_properties, 'export_has_representations') + row.prop(bim_properties, "export_has_representations") row = layout.row() - row.prop(bim_properties, 'export_should_guess_quantities') + row.prop(bim_properties, "export_should_guess_quantities") row = layout.row() - row.prop(bim_properties, 'export_should_force_faceted_brep') + row.prop(bim_properties, "export_should_force_faceted_brep") row = layout.row() - row.prop(bim_properties, 'import_should_import_type_representations') + row.prop(bim_properties, "import_should_import_type_representations") row = layout.row() - row.prop(bim_properties, 'import_should_import_curves') + row.prop(bim_properties, "import_should_import_curves") row = layout.row() - row.prop(bim_properties, 'import_should_import_opening_elements') + row.prop(bim_properties, "import_should_import_opening_elements") row = layout.row() - row.prop(bim_properties, 'import_should_import_spaces') + row.prop(bim_properties, "import_should_import_spaces") - layout.label(text='Experimental Modes:') + layout.label(text="Experimental Modes:") row = layout.row() - row.prop(bim_properties, 'import_should_use_legacy') + row.prop(bim_properties, "import_should_use_legacy") row = layout.row() - row.prop(bim_properties, 'import_should_import_native') + row.prop(bim_properties, "import_should_import_native") row = layout.row() - row.prop(bim_properties, 'import_export_should_roundtrip_native') + row.prop(bim_properties, "import_export_should_roundtrip_native") row = layout.row() - row.prop(bim_properties, 'import_should_use_cpu_multiprocessing') + row.prop(bim_properties, "import_should_use_cpu_multiprocessing") row = layout.row() - row.prop(bim_properties, 'import_should_import_with_profiling') + row.prop(bim_properties, "import_should_import_with_profiling") row = layout.row() - row.prop(bim_properties, 'import_deflection_tolerance') + row.prop(bim_properties, "import_deflection_tolerance") row = layout.row() - row.prop(bim_properties, 'import_angular_tolerance') + row.prop(bim_properties, "import_angular_tolerance") row = layout.row() - row.prop(bim_properties, 'export_json_compact') + row.prop(bim_properties, "export_json_compact") - layout.label(text='Simplifications:') + layout.label(text="Simplifications:") row = layout.row() - row.prop(bim_properties, 'import_should_import_aggregates') + row.prop(bim_properties, "import_should_import_aggregates") row = layout.row() - row.prop(bim_properties, 'import_should_merge_aggregates') + row.prop(bim_properties, "import_should_merge_aggregates") row = layout.row() - row.prop(bim_properties, 'import_should_merge_by_class') + row.prop(bim_properties, "import_should_merge_by_class") row = layout.row() - row.prop(bim_properties, 'import_should_merge_by_material') + row.prop(bim_properties, "import_should_merge_by_material") row = layout.row() - row.prop(bim_properties, 'import_should_merge_materials_by_colour') + row.prop(bim_properties, "import_should_merge_materials_by_colour") row = layout.row() - row.prop(bim_properties, 'import_should_clean_mesh') + row.prop(bim_properties, "import_should_clean_mesh") - layout.label(text='Vendor Workarounds:') + layout.label(text="Vendor Workarounds:") row = layout.row() - row.prop(bim_properties, 'import_should_auto_set_workarounds') + row.prop(bim_properties, "import_should_auto_set_workarounds") - layout.label(text='Tekla Workarounds:') + layout.label(text="Tekla Workarounds:") row = layout.row() - row.prop(bim_properties, 'import_should_ignore_site_coordinates') + row.prop(bim_properties, "import_should_ignore_site_coordinates") - layout.label(text='ProStructures Workarounds:') + layout.label(text="ProStructures Workarounds:") row = layout.row() - row.prop(bim_properties, 'import_should_allow_non_element_aggregates') + row.prop(bim_properties, "import_should_allow_non_element_aggregates") row = layout.row() - row.prop(bim_properties, 'import_should_offset_model') + row.prop(bim_properties, "import_should_offset_model") row = layout.row() - row.prop(bim_properties, 'import_model_offset_coordinates') + row.prop(bim_properties, "import_model_offset_coordinates") - layout.label(text='12D Workarounds:') + layout.label(text="12D Workarounds:") row = layout.row() - row.prop(bim_properties, 'import_should_reset_absolute_coordinates') + row.prop(bim_properties, "import_should_reset_absolute_coordinates") - layout.label(text='Civil 3D Workarounds:') + layout.label(text="Civil 3D Workarounds:") row = layout.row() - row.prop(bim_properties, 'import_should_reset_absolute_coordinates') + row.prop(bim_properties, "import_should_reset_absolute_coordinates") - layout.label(text='Revit Workarounds:') + layout.label(text="Revit Workarounds:") row = layout.row() - row.prop(bim_properties, 'export_should_use_presentation_style_assignment') + row.prop(bim_properties, "export_should_use_presentation_style_assignment") row = layout.row() - row.prop(bim_properties, 'import_should_ignore_site_coordinates') + row.prop(bim_properties, "import_should_ignore_site_coordinates") row = layout.row() - row.prop(bim_properties, 'import_should_ignore_building_coordinates') + row.prop(bim_properties, "import_should_ignore_building_coordinates") row = layout.row() - row.prop(bim_properties, 'import_should_treat_styled_item_as_material') + row.prop(bim_properties, "import_should_treat_styled_item_as_material") class BIM_UL_generic(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): ob = data if item: - layout.prop(item, 'name', text='', emboss=False) + layout.prop(item, "name", text="", emboss=False) else: layout.label(text="", translate=False) @@ -1798,7 +1829,7 @@ class BIM_UL_topics(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): ob = data if item: - layout.prop(item, 'name', text='', emboss=False) + layout.prop(item, "name", text="", emboss=False) else: layout.label(text="", translate=False) @@ -1807,7 +1838,7 @@ class BIM_UL_clash_sets(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): ob = data if item: - layout.prop(item, 'name', text='', emboss=False) + layout.prop(item, "name", text="", emboss=False) else: layout.label(text="", translate=False) @@ -1816,7 +1847,7 @@ class BIM_UL_constraints(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): ob = data if item: - layout.prop(item, 'name', text='', emboss=False) + layout.prop(item, "name", text="", emboss=False) else: layout.label(text="", translate=False) @@ -1825,7 +1856,7 @@ class BIM_UL_document_information(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): ob = data if item: - layout.prop(item, 'name', text='', emboss=False) + layout.prop(item, "name", text="", emboss=False) else: layout.label(text="", translate=False) @@ -1834,31 +1865,32 @@ class BIM_UL_document_references(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): ob = data if item: - layout.prop(item, 'name', text='', emboss=False) + layout.prop(item, "name", text="", emboss=False) else: layout.label(text="", translate=False) class BIM_UL_classifications(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): - if self.layout_type in {'DEFAULT', 'COMPACT'}: + if self.layout_type in {"DEFAULT", "COMPACT"}: rt = data.root - ch = rt['children'] + ch = rt["children"] itemdata = ch[item.name] - if itemdata.get('children', {}): - op = layout.operator("bim.change_classification_level", text="", - emboss=False, icon="DISCLOSURE_TRI_RIGHT") - op.path_sid = "%r"%active_data.id_data # get id-data - op.path_lst = active_data.path_from_id() # path to view - op.path_itm = item.name # name of child. empty = go up + if itemdata.get("children", {}): + op = layout.operator( + "bim.change_classification_level", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT" + ) + op.path_sid = "%r" % active_data.id_data # get id-data + op.path_lst = active_data.path_from_id() # path to view + op.path_itm = item.name # name of child. empty = go up else: - layout.label(text='', icon='BLANK1') + layout.label(text="", icon="BLANK1") layout.prop(item, "name", text="", emboss=False) - layout.label(text=itemdata['name']) + layout.label(text=itemdata["name"]) class BIM_ADDON_preferences(bpy.types.AddonPreferences): - bl_idname = 'blenderbim' + bl_idname = "blenderbim" svg2pdf_command: StringProperty(name="SVG to PDF Command") svg2dxf_command: StringProperty(name="SVG to DXF Command") svg_command: StringProperty(name="SVG Command") @@ -1867,27 +1899,27 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences): def draw(self, context): layout = self.layout row = layout.row() - row.operator('bim.open_upstream', text='Visit Homepage').page = 'home' - row.operator('bim.open_upstream', text='Visit Documentation').page = 'docs' + row.operator("bim.open_upstream", text="Visit Homepage").page = "home" + row.operator("bim.open_upstream", text="Visit Documentation").page = "docs" row = layout.row() - row.operator('bim.open_upstream', text='Visit Wiki').page = 'wiki' - row.operator('bim.open_upstream', text='Visit Community').page = 'community' + row.operator("bim.open_upstream", text="Visit Wiki").page = "wiki" + row.operator("bim.open_upstream", text="Visit Community").page = "community" row = layout.row() - row.prop(self, 'svg2pdf_command') + row.prop(self, "svg2pdf_command") row = layout.row() - row.prop(self, 'svg2dxf_command') + row.prop(self, "svg2dxf_command") row = layout.row() - row.prop(self, 'svg_command') + row.prop(self, "svg_command") row = layout.row() - row.prop(self, 'pdf_command') + row.prop(self, "pdf_command") class BIM_PT_ifcclash(Panel): bl_label = "IFC Clash Sets" bl_idname = "BIM_PT_ifcclash" - bl_options = {'DEFAULT_CLOSED'} - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" bl_context = "scene" def draw(self, context): @@ -1897,74 +1929,74 @@ class BIM_PT_ifcclash(Panel): props = scene.BIMProperties row = layout.row(align=True) - row.operator('bim.add_clash_set') - row.operator('bim.import_clash_sets', text='', icon='IMPORT') - row.operator('bim.export_clash_sets', text='', icon='EXPORT') + row.operator("bim.add_clash_set") + row.operator("bim.import_clash_sets", text="", icon="IMPORT") + row.operator("bim.export_clash_sets", text="", icon="EXPORT") if not props.clash_sets: return - layout.template_list('BIM_UL_clash_sets', '', props, 'clash_sets', props, 'active_clash_set_index') + 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] row = layout.row(align=True) - row.prop(clash_set, 'name') - row.operator('bim.remove_clash_set', icon='X', text='').index = props.active_clash_set_index + row.prop(clash_set, "name") + row.operator("bim.remove_clash_set", icon="X", text="").index = props.active_clash_set_index row = layout.row(align=True) - row.prop(clash_set, 'tolerance') + row.prop(clash_set, "tolerance") layout.label(text="Group A:") row = layout.row() - row.operator('bim.add_clash_source').group = 'a' + row.operator("bim.add_clash_source").group = "a" for index, source in enumerate(clash_set.a): row = layout.row(align=True) - row.prop(source, 'name', text='') - op = row.operator('bim.select_clash_source', icon='FILE_FOLDER', text='') + row.prop(source, "name", text="") + op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="") op.index = index - op.group = 'a' - op = row.operator('bim.remove_clash_source', icon='X', text='') + op.group = "a" + op = row.operator("bim.remove_clash_source", icon="X", text="") op.index = index - op.group = 'a' + op.group = "a" row = layout.row(align=True) - row.prop(source, 'mode', text='') - row.prop(source, 'selector', text='') + row.prop(source, "mode", text="") + row.prop(source, "selector", text="") layout.label(text="Group B:") row = layout.row() - row.operator('bim.add_clash_source').group = 'b' + row.operator("bim.add_clash_source").group = "b" for index, source in enumerate(clash_set.b): row = layout.row(align=True) - row.prop(source, 'name', text='') - op = row.operator('bim.select_clash_source', icon='FILE_FOLDER', text='') + row.prop(source, "name", text="") + op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="") op.index = index - op.group = 'b' - op = row.operator('bim.remove_clash_source', icon='X', text='') + op.group = "b" + op = row.operator("bim.remove_clash_source", icon="X", text="") op.index = index - op.group = 'b' + op.group = "b" row = layout.row(align=True) - row.prop(source, 'mode', text='') - row.prop(source, 'selector', text='') + row.prop(source, "mode", text="") + row.prop(source, "selector", text="") row = layout.row() - row.operator('bim.execute_ifc_clash') + row.operator("bim.execute_ifc_clash") row = layout.row() - row.operator('bim.select_ifc_clash_results') + row.operator("bim.select_ifc_clash_results") class BIM_PT_modeling_utilities(Panel): bl_idname = "BIM_PT_modeling_utilities" bl_label = "Architectural" - bl_space_type = 'VIEW_3D' + bl_space_type = "VIEW_3D" bl_region_type = "UI" - bl_category = 'BlenderBIM' + bl_category = "BlenderBIM" def draw(self, context): layout = self.layout @@ -1976,64 +2008,64 @@ class BIM_PT_modeling_utilities(Panel): class BIM_PT_annotation_utilities(Panel): bl_idname = "BIM_PT_annotation_utilities" bl_label = "Annotation" - bl_space_type = 'VIEW_3D' + bl_space_type = "VIEW_3D" bl_region_type = "UI" - bl_category = 'BlenderBIM' + bl_category = "BlenderBIM" def draw(self, context): layout = self.layout row = layout.row(align=True) - op = row.operator('bim.add_annotation', text='Dim', icon='ARROW_LEFTRIGHT') - op.obj_name = 'Dimension' - op.data_type = 'curve' - op = row.operator('bim.add_annotation', text='Dim (Eq)', icon='ARROW_LEFTRIGHT') - op.obj_name = 'Equal' - op.data_type = 'curve' + op = row.operator("bim.add_annotation", text="Dim", icon="ARROW_LEFTRIGHT") + op.obj_name = "Dimension" + op.data_type = "curve" + op = row.operator("bim.add_annotation", text="Dim (Eq)", icon="ARROW_LEFTRIGHT") + op.obj_name = "Equal" + op.data_type = "curve" row = layout.row(align=True) - op = row.operator('bim.add_annotation', text='Text', icon='SMALL_CAPS') - op.data_type = 'text' - op = row.operator('bim.add_annotation', text='Leader', icon='TRACKING_BACKWARDS') - op.obj_name = 'Leader' - op.data_type = 'curve' + op = row.operator("bim.add_annotation", text="Text", icon="SMALL_CAPS") + op.data_type = "text" + op = row.operator("bim.add_annotation", text="Leader", icon="TRACKING_BACKWARDS") + op.obj_name = "Leader" + op.data_type = "curve" row = layout.row(align=True) - op = row.operator('bim.add_annotation', text='Stair Arrow', icon='SCREEN_BACK') - op.obj_name = 'Stair' - op.data_type = 'curve' - op = row.operator('bim.add_annotation', text='Hidden', icon='CON_TRACKTO') - op.obj_name = 'Hidden' - op.data_type = 'mesh' + op = row.operator("bim.add_annotation", text="Stair Arrow", icon="SCREEN_BACK") + op.obj_name = "Stair" + op.data_type = "curve" + op = row.operator("bim.add_annotation", text="Hidden", icon="CON_TRACKTO") + op.obj_name = "Hidden" + op.data_type = "mesh" row = layout.row(align=True) - op = row.operator('bim.add_annotation', text='Level (Plan)', icon='SORTBYEXT') - op.obj_name = 'Plan Level' - op.data_type = 'curve' - op = row.operator('bim.add_annotation', text='Level (Section)', icon='TRIA_DOWN') - op.obj_name = 'Section Level' - op.data_type = 'curve' + op = row.operator("bim.add_annotation", text="Level (Plan)", icon="SORTBYEXT") + op.obj_name = "Plan Level" + op.data_type = "curve" + op = row.operator("bim.add_annotation", text="Level (Section)", icon="TRIA_DOWN") + op.obj_name = "Section Level" + op.data_type = "curve" props = bpy.context.scene.DocProperties row = layout.row(align=True) - row.operator('bim.add_drawing') - row.operator('bim.refresh_drawing_list', icon='FILE_REFRESH', text='') + row.operator("bim.add_drawing") + row.operator("bim.refresh_drawing_list", icon="FILE_REFRESH", text="") if props.drawings: if props.active_drawing_index < len(props.drawings): - op = row.operator('bim.open_view', icon='URL', text='') + op = row.operator("bim.open_view", icon="URL", text="") op.view = props.drawings[props.active_drawing_index].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') + 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") class BIM_PT_qto_utilities(Panel): bl_idname = "BIM_PT_qto_utilities" bl_label = "Quantity Take-off" - bl_space_type = 'VIEW_3D' + bl_space_type = "VIEW_3D" bl_region_type = "UI" - bl_category = 'BlenderBIM' + bl_category = "BlenderBIM" def draw(self, context): layout = self.layout @@ -2042,7 +2074,7 @@ class BIM_PT_qto_utilities(Panel): row = layout.row() layout.label(text="Results:") row = layout.row() - row.prop(props, 'qto_result', text='') + row.prop(props, "qto_result", text="") row = layout.row(align=True) row.operator("bim.calculate_edge_lengths") @@ -2055,16 +2087,16 @@ class BIM_PT_qto_utilities(Panel): class BIM_PT_misc_utilities(Panel): bl_idname = "BIM_PT_misc_utilities" bl_label = "Miscellaneous" - bl_space_type = 'VIEW_3D' + bl_space_type = "VIEW_3D" bl_region_type = "UI" - bl_category = 'BlenderBIM' + bl_category = "BlenderBIM" def draw(self, context): layout = self.layout props = context.scene.BIMProperties row = layout.row() - row.prop(props, 'override_colour', text='') + row.prop(props, "override_colour", text="") row = layout.row(align=True) row.operator("bim.set_override_colour") row = layout.row(align=True) @@ -2074,9 +2106,9 @@ class BIM_PT_misc_utilities(Panel): class BIM_PT_debug(Panel): bl_label = "IFC Debug" bl_idname = "BIM_PT_debug" - bl_options = {'DEFAULT_CLOSED'} - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" bl_context = "scene" def draw(self, context): @@ -2087,15 +2119,14 @@ class BIM_PT_debug(Panel): debug_props = scene.BIMDebugProperties row = layout.row() - row.prop(debug_props, 'step_id', text='') + row.prop(debug_props, "step_id", text="") row = layout.row() - row.operator('bim.create_shape_from_step_id') + row.operator("bim.create_shape_from_step_id") row = layout.row() - row.prop(debug_props, 'number_of_polygons', text='') + row.prop(debug_props, "number_of_polygons", text="") row = layout.row() - row.operator('bim.select_high_polygon_meshes') - + row.operator("bim.select_high_polygon_meshes") def ifc_units(self, context): @@ -2105,11 +2136,11 @@ def ifc_units(self, context): layout.use_property_decorate = False layout.use_property_split = True row = layout.row() - row.prop(props, 'area_unit') + row.prop(props, "area_unit") row = layout.row() - row.prop(props, 'volume_unit') + row.prop(props, "volume_unit") row = layout.row() - if bpy.context.scene.unit_settings.system == 'IMPERIAL': - row.prop(props, 'imperial_precision') + if bpy.context.scene.unit_settings.system == "IMPERIAL": + row.prop(props, "imperial_precision") else: - row.prop(props, 'metric_precision') + row.prop(props, "metric_precision") diff --git a/src/ifcblenderexport/docs/conf.py b/src/ifcblenderexport/docs/conf.py index 8261480b75..3c39f711c1 100644 --- a/src/ifcblenderexport/docs/conf.py +++ b/src/ifcblenderexport/docs/conf.py @@ -17,12 +17,12 @@ # -- Project information ----------------------------------------------------- -project = 'IfcOpenShell' -copyright = '2020, IfcOpenShell Contributors' -author = 'IfcOpenShell Contributors' +project = "IfcOpenShell" +copyright = "2020, IfcOpenShell Contributors" +author = "IfcOpenShell Contributors" # The full version, including alpha/beta/rc tags -release = '0.0.1' +release = "0.0.1" # -- General configuration --------------------------------------------------- @@ -30,17 +30,15 @@ release = '0.0.1' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = [ - 'sphinx.ext.autodoc' -] +extensions = ["sphinx.ext.autodoc"] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # -- Options for HTML output ------------------------------------------------- @@ -48,9 +46,9 @@ exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'nature' +html_theme = "nature" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] diff --git a/src/ifcblenderexport/dxf2ifc.py b/src/ifcblenderexport/dxf2ifc.py index 80606de391..ed0726ab5f 100644 --- a/src/ifcblenderexport/dxf2ifc.py +++ b/src/ifcblenderexport/dxf2ifc.py @@ -1,55 +1,84 @@ class Dxf2Ifc: def execute(self): self.create_ifc_file() - doc = ezdxf.readfile('input.dxf') + doc = ezdxf.readfile("input.dxf") model = doc.modelspace() products = [] for entity in model: print(entity) - if entity.get_mode() == 'AcDbPolyFaceMesh': + if entity.get_mode() == "AcDbPolyFaceMesh": ifc_faces = [] for face in entity.faces(): ifc_faces.append( - self.file.createIfcFace([self.file.createIfcFaceOuterBound(self.file.createIfcPolyLoop([ - self.file.createIfcCartesianPoint((v.dxf.location)) for v in face[0:3]]), True)])) - representation = self.file.createIfcProductDefinitionShape(None, None, [self.file.createIfcShapeRepresentation( - self.subcontext, 'Body', 'Brep', [self.file.createIfcFacetedBrep(self.file.createIfcClosedShell(ifc_faces))])]) - products.append(self.file.create_entity('IfcBuildingElementProxy', **{ - 'GlobalId': ifcopenshell.guid.new(), - 'Name': entity.dxf.layer, - 'ObjectPlacement': self.placement, - 'Representation': representation - })) + self.file.createIfcFace( + [ + self.file.createIfcFaceOuterBound( + self.file.createIfcPolyLoop( + [self.file.createIfcCartesianPoint((v.dxf.location)) for v in face[0:3]] + ), + True, + ) + ] + ) + ) + representation = self.file.createIfcProductDefinitionShape( + None, + None, + [ + self.file.createIfcShapeRepresentation( + self.subcontext, + "Body", + "Brep", + [self.file.createIfcFacetedBrep(self.file.createIfcClosedShell(ifc_faces))], + ) + ], + ) + products.append( + self.file.create_entity( + "IfcBuildingElementProxy", + **{ + "GlobalId": ifcopenshell.guid.new(), + "Name": entity.dxf.layer, + "ObjectPlacement": self.placement, + "Representation": representation, + } + ) + ) else: - print('Not yet implemented') - self.file.createIfcRelContainedInSpatialStructure(ifcopenshell.guid.new(), None, None, None, products, self.site) - self.file.write('test.ifc') + print("Not yet implemented") + self.file.createIfcRelContainedInSpatialStructure( + ifcopenshell.guid.new(), None, None, None, products, self.site + ) + self.file.write("test.ifc") def create_ifc_file(self): self.file = ifcopenshell.file() - units = self.file.createIfcUnitAssignment([ - self.file.createIfcSIUnit(None, 'LENGTHUNIT', None, 'METRE') - ]) + units = self.file.createIfcUnitAssignment([self.file.createIfcSIUnit(None, "LENGTHUNIT", None, "METRE")]) self.origin = self.file.createIfcAxis2Placement3D( - self.file.createIfcCartesianPoint((0., 0., 0.)), - self.file.createIfcDirection((0., 0., 1.)), - self.file.createIfcDirection((1., 0., 0.))) + self.file.createIfcCartesianPoint((0.0, 0.0, 0.0)), + self.file.createIfcDirection((0.0, 0.0, 1.0)), + self.file.createIfcDirection((1.0, 0.0, 0.0)), + ) self.placement = self.file.createIfcLocalPlacement(None, self.origin) - self.context = self.file.createIfcGeometricRepresentationContext(None, 'Model', 3, 1.0E-05, self.origin) + self.context = self.file.createIfcGeometricRepresentationContext(None, "Model", 3, 1.0e-05, self.origin) self.subcontext = self.file.createIfcGeometricRepresentationSubcontext( - 'Body', 'Model', None, None, None, None, self.context, None, 'MODEL_VIEW', None) - self.project = self.file.create_entity('IfcProject', **{ - 'GlobalId': ifcopenshell.guid.new(), - 'Name': 'DXF Conversion', - 'RepresentationContexts': [self.context], - 'UnitsInContext': units - }) - self.site = self.file.create_entity('IfcSite', **{ - 'GlobalId': ifcopenshell.guid.new(), - 'Name': 'DXF Conversion Site', - 'ObjectPlacement': self.placement - }) + "Body", "Model", None, None, None, None, self.context, None, "MODEL_VIEW", None + ) + self.project = self.file.create_entity( + "IfcProject", + **{ + "GlobalId": ifcopenshell.guid.new(), + "Name": "DXF Conversion", + "RepresentationContexts": [self.context], + "UnitsInContext": units, + } + ) + self.site = self.file.create_entity( + "IfcSite", + **{"GlobalId": ifcopenshell.guid.new(), "Name": "DXF Conversion Site", "ObjectPlacement": self.placement} + ) self.file.createIfcRelAggregates(ifcopenshell.guid.new(), None, None, None, self.project, [self.site]) + dxf2ifc = Dxf2Ifc() dxf2ifc.execute() diff --git a/src/ifcblenderexport/extract.py b/src/ifcblenderexport/extract.py index b7f930f622..79de9fa269 100644 --- a/src/ifcblenderexport/extract.py +++ b/src/ifcblenderexport/extract.py @@ -1,10 +1,11 @@ import xml.sax, json, copy, pathlib from bs4 import BeautifulSoup import sys + sys.setrecursionlimit(100) -class IfcElementHandler(xml.sax.ContentHandler): +class IfcElementHandler(xml.sax.ContentHandler): def __init__(self): self.elements = {} self.current_element_name = None @@ -13,90 +14,87 @@ class IfcElementHandler(xml.sax.ContentHandler): self.attribute_stack = [] def startElement(self, name, attrs): - if name == 'xs:element' and 'substitutionGroup' in attrs: - self.elements[attrs['name']] = { - 'description': self.get_description(attrs['name']), - 'is_abstract': True if 'abstract' in attrs else False, - 'parent': attrs['substitutionGroup'][len('ifc:'):], - 'attributes': [] + if name == "xs:element" and "substitutionGroup" in attrs: + self.elements[attrs["name"]] = { + "description": self.get_description(attrs["name"]), + "is_abstract": True if "abstract" in attrs else False, + "parent": attrs["substitutionGroup"][len("ifc:") :], + "attributes": [], } - self.current_element_name = attrs['name'] - elif name == 'xs:simpleType' \ - and 'name' in attrs \ - and 'Enum' in attrs['name']: - self.current_enum_name = attrs['name'] + self.current_element_name = attrs["name"] + elif name == "xs:simpleType" and "name" in attrs and "Enum" in attrs["name"]: + self.current_enum_name = attrs["name"] self.enums[self.current_enum_name] = [] - elif name == 'xs:enumeration' and self.current_enum_name: - self.enums[self.current_enum_name].append(attrs['value'].upper()) - elif name == 'xs:attribute' \ - and self.current_element_name \ - and 'name' in attrs \ - and 'type' in attrs: - self.elements[self.current_element_name]['attributes'].append({ - 'name': attrs['name'], - 'type': attrs['type'].replace('ifc:', ''), - }) + elif name == "xs:enumeration" and self.current_enum_name: + self.enums[self.current_enum_name].append(attrs["value"].upper()) + elif name == "xs:attribute" and self.current_element_name and "name" in attrs and "type" in attrs: + self.elements[self.current_element_name]["attributes"].append( + { + "name": attrs["name"], + "type": attrs["type"].replace("ifc:", ""), + } + ) def endDocument(self): elements = {} for name, data in self.elements.items(): - for index, attribute in enumerate(data['attributes']): - data['attributes'][index] = self.resolve_enums(attribute) + for index, attribute in enumerate(data["attributes"]): + data["attributes"][index] = self.resolve_enums(attribute) for name, data in self.elements.items(): - if data['is_abstract']: + if data["is_abstract"]: continue if self.is_an_ifcproduct(data): self.attribute_stack = [] self.get_parent_attributes(data) elements[name] = copy.deepcopy(data) - elements[name]['attributes'] = copy.deepcopy(self.attribute_stack) + elements[name]["attributes"] = copy.deepcopy(self.attribute_stack) self.elements = elements def get_description(self, name): try: - filenames = pathlib.Path( - 'io_export_ifc/schema/ifc4-add2-tc1/ifc4-add2-tc1/html/schema/').glob( - '**/{}.htm'.format(name.lower())) + filenames = pathlib.Path("io_export_ifc/schema/ifc4-add2-tc1/ifc4-add2-tc1/html/schema/").glob( + "**/{}.htm".format(name.lower()) + ) for filename in filenames: - with open(filename, 'r') as file: - soup = BeautifulSoup(file, 'html.parser') - for detail in soup.find_all('details'): - if detail.summary.string == 'Entity definition' \ - and detail.p: - return str(detail.p.text.replace('\n', ' ')) + with open(filename, "r") as file: + soup = BeautifulSoup(file, "html.parser") + for detail in soup.find_all("details"): + if detail.summary.string == "Entity definition" and detail.p: + return str(detail.p.text.replace("\n", " ")) return None except: return None - #print('Failed to get description for {}'.format(name)) + # print('Failed to get description for {}'.format(name)) return None def resolve_enums(self, attribute): - if attribute['type'] in self.enums: - attribute['is_enum'] = True - attribute['enum_values'] = self.enums[attribute['type']] + if attribute["type"] in self.enums: + attribute["is_enum"] = True + attribute["enum_values"] = self.enums[attribute["type"]] return attribute - attribute['is_enum'] = False - attribute['enum_values'] = [] + attribute["is_enum"] = False + attribute["enum_values"] = [] return attribute def get_parent_attributes(self, data): - self.attribute_stack.extend(data['attributes']) - if data['parent'] != 'IfcProduct': # For now, we treat attributes above IfcProduct in a special way - self.get_parent_attributes(self.elements[data['parent']]) + self.attribute_stack.extend(data["attributes"]) + if data["parent"] != "IfcProduct": # For now, we treat attributes above IfcProduct in a special way + self.get_parent_attributes(self.elements[data["parent"]]) def is_an_ifcproduct(self, data): - if data['parent'] == 'IfcProduct': + if data["parent"] == "IfcProduct": return True else: for name, parent_data in self.elements.items(): - if name == data['parent']: + if name == data["parent"]: return self.is_an_ifcproduct(parent_data) return False -xsd_path = 'io_export_ifc/schema/IFC4.xsd' + +xsd_path = "io_export_ifc/schema/IFC4.xsd" handler = IfcElementHandler() parser = xml.sax.make_parser() parser.setContentHandler(handler) diff --git a/src/ifcblenderexport/gbxml.py b/src/ifcblenderexport/gbxml.py index 443da2be77..c96df23a26 100644 --- a/src/ifcblenderexport/gbxml.py +++ b/src/ifcblenderexport/gbxml.py @@ -2,100 +2,101 @@ import bpy import uuid import math import sys -#sys.path.append('C:\Program Files\Python37\Lib\site-packages') + +# sys.path.append('C:\Program Files\Python37\Lib\site-packages') import lxml import bspy from bspy import Gbxml -class GbxmlExporter(): + +class GbxmlExporter: def __init__(self): self.gbxml = Gbxml() self.campus = None def export(self): - print('# Start export') - self.campus = self.gbxml.add_element(self.gbxml.root(), 'Campus') - self.campus.set('id', 'campus-1') - name = self.gbxml.add_element(self.campus, 'Name', 'My project') + print("# Start export") + self.campus = self.gbxml.add_element(self.gbxml.root(), "Campus") + self.campus.set("id", "campus-1") + name = self.gbxml.add_element(self.campus, "Name", "My project") - location = self.gbxml.add_element(self.campus, 'Location') - self.gbxml.add_element(location, 'ZipcodeOrPostalCode', 'G20 0SP') - self.gbxml.add_element(location, 'Name', 'London/Heathrow') - self.gbxml.add_element(location, 'Latitude', '51.480000') - self.gbxml.add_element(location, 'Longitude', '-0.450000') - self.gbxml.add_element(location, 'Elevation', '24.000000') + location = self.gbxml.add_element(self.campus, "Location") + self.gbxml.add_element(location, "ZipcodeOrPostalCode", "G20 0SP") + self.gbxml.add_element(location, "Name", "London/Heathrow") + self.gbxml.add_element(location, "Latitude", "51.480000") + self.gbxml.add_element(location, "Longitude", "-0.450000") + self.gbxml.add_element(location, "Elevation", "24.000000") - building = self.gbxml.add_element(self.campus, 'Building') - building.set('id', str(uuid.uuid4())) - building.set('buildingType', 'Office') + building = self.gbxml.add_element(self.campus, "Building") + building.set("id", str(uuid.uuid4())) + building.set("buildingType", "Office") for object in bpy.context.selected_objects: self.create_space(object, building) # hardcoded test - construction = self.gbxml.add_element(self.gbxml.root(), 'Construction') - construction.set('id', 'defaultconstruction') - self.gbxml.add_element(construction, 'Name', 'test construction name') - u_value = self.gbxml.add_element(construction, 'U-value', '0.42') - u_value.set('unit', 'WPerSquareMeterK') - layer = self.gbxml.add_element(construction, 'LayerId') - layer.set('layerIdRef', 'defaultlayer') + construction = self.gbxml.add_element(self.gbxml.root(), "Construction") + construction.set("id", "defaultconstruction") + self.gbxml.add_element(construction, "Name", "test construction name") + u_value = self.gbxml.add_element(construction, "U-value", "0.42") + u_value.set("unit", "WPerSquareMeterK") + layer = self.gbxml.add_element(construction, "LayerId") + layer.set("layerIdRef", "defaultlayer") - layer = self.gbxml.add_element(self.gbxml.root(), 'Layer') - layer.set('id', 'defaultlayer') - material = self.gbxml.add_element(layer, 'MaterialId') - material.set('materialIdRef', 'defaultmaterial') + layer = self.gbxml.add_element(self.gbxml.root(), "Layer") + layer.set("id", "defaultlayer") + material = self.gbxml.add_element(layer, "MaterialId") + material.set("materialIdRef", "defaultmaterial") - material = self.gbxml.add_element(self.gbxml.root(), 'Material') - material.set('id', 'defaultmaterial') - thickness = self.gbxml.add_element(material, 'Thickness', '0.2') - thickness.set('unit', 'Meters') - self.gbxml.add_element(material, 'Name', 'test material name') - r_value = self.gbxml.add_element(material, 'R-value', '0.13') - r_value.set('unit', 'SquareMeterKPerW') + material = self.gbxml.add_element(self.gbxml.root(), "Material") + material.set("id", "defaultmaterial") + thickness = self.gbxml.add_element(material, "Thickness", "0.2") + thickness.set("unit", "Meters") + self.gbxml.add_element(material, "Name", "test material name") + r_value = self.gbxml.add_element(material, "R-value", "0.13") + r_value.set("unit", "SquareMeterKPerW") - self.append_template('C:/cygwin64/home/moud308/Projects/New Folder/presets/light-schedule.xml') - self.append_template('C:/cygwin64/home/moud308/Projects/New Folder/presets/window-types.xml') + self.append_template("C:/cygwin64/home/moud308/Projects/New Folder/presets/light-schedule.xml") + self.append_template("C:/cygwin64/home/moud308/Projects/New Folder/presets/window-types.xml") - with open('C:/cygwin64/home/moud308/Projects/New Folder/out.xml', 'w') as out: + with open("C:/cygwin64/home/moud308/Projects/New Folder/out.xml", "w") as out: out.write(self.gbxml.xmlstring()) - print('# Validation results: {}'.format(self.gbxml.validate())) - print('# Finish export') + print("# Validation results: {}".format(self.gbxml.validate())) + print("# Finish export") def append_template(self, file): parser = lxml.etree.XMLParser(remove_blank_text=True) - template = lxml.etree.parse(file, parser).findall('.')[0] + template = lxml.etree.parse(file, parser).findall(".")[0] for child in template.getchildren(): self.gbxml.root().append(child) def create_space(self, object, building): - space = self.gbxml.add_element(building, 'Space') - space.set('id', object.name) - space.set('lightScheduleIdRef', 'aim0130') # hardcoded - self.gbxml.add_element(space, 'Name', object.name) + space = self.gbxml.add_element(building, "Space") + space.set("id", object.name) + space.set("lightScheduleIdRef", "aim0130") # hardcoded + self.gbxml.add_element(space, "Name", object.name) - light_power_per_area = self.gbxml.add_element(space, 'LightPowerPerArea', '5') # hardcoded test - light_power_per_area.set('unit', 'WattPerSquareMeter') + light_power_per_area = self.gbxml.add_element(space, "LightPowerPerArea", "5") # hardcoded test + light_power_per_area.set("unit", "WattPerSquareMeter") calculated_area = 0 - shell_geometry = self.gbxml.add_element(space, 'ShellGeometry') - shell_geometry.set('id', 'shellid') - closed_shell = self.gbxml.add_element(shell_geometry, 'ClosedShell') + shell_geometry = self.gbxml.add_element(space, "ShellGeometry") + shell_geometry.set("id", "shellid") + closed_shell = self.gbxml.add_element(shell_geometry, "ClosedShell") vertices_in_vg = self.get_vertices_in_vg(object, 0) for polygon in object.data.polygons: # First vg is reserved for surfaces - if object.vertex_groups \ - and not self.is_polygon_in_vg(polygon, vertices_in_vg): + if object.vertex_groups and not self.is_polygon_in_vg(polygon, vertices_in_vg): continue calculated_area += polygon.area self.create_poly_loop(object, polygon, closed_shell) self.create_space_boundary(object, polygon, space) self.create_surface(object, polygon) - self.gbxml.add_element(space, 'Area', str(calculated_area)) - self.gbxml.add_element(space, 'Volume', str(self.get_volume(object))) + self.gbxml.add_element(space, "Area", str(calculated_area)) + self.gbxml.add_element(space, "Volume", str(self.get_volume(object))) def get_vertices_in_vg(self, object, vg_index): - return [ v.index for v in object.data.vertices if vg_index in [ g.group for g in v.groups ] ] + return [v.index for v in object.data.vertices if vg_index in [g.group for g in v.groups]] # Can move into a common Blender helper class? def is_polygon_in_vg(self, polygon, vertices_in_vg): @@ -105,49 +106,49 @@ class GbxmlExporter(): return True def create_space_boundary(self, object, polygon, parent): - space_boundary = self.gbxml.add_element(parent, 'SpaceBoundary') - space_boundary.set('isSecondLevelBoundary', 'true') - space_boundary.set('surfaceIdRef', 'surface-{}-{}'.format(object.name, polygon.index)) - planar_geometry = self.gbxml.add_element(space_boundary, 'PlanarGeometry') + space_boundary = self.gbxml.add_element(parent, "SpaceBoundary") + space_boundary.set("isSecondLevelBoundary", "true") + space_boundary.set("surfaceIdRef", "surface-{}-{}".format(object.name, polygon.index)) + planar_geometry = self.gbxml.add_element(space_boundary, "PlanarGeometry") self.create_poly_loop(object, polygon, planar_geometry) def create_surface(self, object, polygon): - surface = self.gbxml.add_element(self.campus, 'Surface') - surface.set('id', 'surface-{}-{}'.format(object.name, polygon.index)) - surface.set('surfaceType', 'ExteriorWall') - surface.set('constructionIdRef', 'defaultconstruction') - adjacent_space_id = self.gbxml.add_element(surface, 'AdjacentSpaceId') - adjacent_space_id.set('spaceIdRef', object.name) - rectangular_geometry = self.gbxml.add_element(surface, 'RectangularGeometry') + surface = self.gbxml.add_element(self.campus, "Surface") + surface.set("id", "surface-{}-{}".format(object.name, polygon.index)) + surface.set("surfaceType", "ExteriorWall") + surface.set("constructionIdRef", "defaultconstruction") + adjacent_space_id = self.gbxml.add_element(surface, "AdjacentSpaceId") + adjacent_space_id.set("spaceIdRef", object.name) + rectangular_geometry = self.gbxml.add_element(surface, "RectangularGeometry") self.gbxml.add_element( - rectangular_geometry, 'Azimuth', - str(math.degrees(math.atan2(polygon.normal[0], polygon.normal[1])))) + rectangular_geometry, "Azimuth", str(math.degrees(math.atan2(polygon.normal[0], polygon.normal[1]))) + ) self.gbxml.add_element( - rectangular_geometry, 'Tilt', - str(math.degrees(math.atan2(polygon.normal[2], polygon.normal[1])) - 90)) - planar_geometry = self.gbxml.add_element(surface, 'PlanarGeometry') + rectangular_geometry, "Tilt", str(math.degrees(math.atan2(polygon.normal[2], polygon.normal[1])) - 90) + ) + planar_geometry = self.gbxml.add_element(surface, "PlanarGeometry") self.create_poly_loop(object, polygon, planar_geometry) for vg in object.vertex_groups: - if '/'.join(vg.name.split('/')[0:2]) == 'openings/{}'.format(polygon.index): + if "/".join(vg.name.split("/")[0:2]) == "openings/{}".format(polygon.index): vertices_in_vg = self.get_vertices_in_vg(object, vg.index) for p in object.data.polygons: if self.is_polygon_in_vg(p, vertices_in_vg): self.create_opening(object, p, surface) def create_opening(self, object, polygon, parent): - opening = self.gbxml.add_element(parent, 'Opening') - opening.set('id', 'opening-{}-{}'.format(object.name, polygon.index)) - opening.set('windowTypeIdRef', 'STD_EX11') # harcoded - opening.set('openingType', 'FixedWindow') # hardcoded - planar_geometry = self.gbxml.add_element(opening, 'PlanarGeometry') + opening = self.gbxml.add_element(parent, "Opening") + opening.set("id", "opening-{}-{}".format(object.name, polygon.index)) + opening.set("windowTypeIdRef", "STD_EX11") # harcoded + opening.set("openingType", "FixedWindow") # hardcoded + planar_geometry = self.gbxml.add_element(opening, "PlanarGeometry") self.create_poly_loop(object, polygon, planar_geometry) def create_poly_loop(self, object, polygon, parent): - poly_loop = self.gbxml.add_element(parent, 'PolyLoop') + poly_loop = self.gbxml.add_element(parent, "PolyLoop") for vertice in polygon.vertices: - cartesian_point = self.gbxml.add_element(poly_loop, 'CartesianPoint') + cartesian_point = self.gbxml.add_element(poly_loop, "CartesianPoint") for coord in [0, 1, 2]: - coordinate = self.gbxml.add_element(cartesian_point, 'Coordinate') + coordinate = self.gbxml.add_element(cartesian_point, "Coordinate") coordinate.text = str(object.data.vertices[vertice].co[coord]) def get_volume(self, o): @@ -158,10 +159,13 @@ class GbxmlExporter(): for tf in me.loop_triangles: tfv = tf.vertices if len(tf.vertices) == 3: - tf_tris = (me.vertices[tfv[0]], me.vertices[tfv[1]], me.vertices[tfv[2]]), + tf_tris = ((me.vertices[tfv[0]], me.vertices[tfv[1]], me.vertices[tfv[2]]),) else: - tf_tris = (me.vertices[tfv[0]], me.vertices[tfv[1]], me.vertices[tfv[2]]),\ - (me.vertices[tfv[2]], me.vertices[tfv[3]], me.vertices[tfv[0]]) + tf_tris = (me.vertices[tfv[0]], me.vertices[tfv[1]], me.vertices[tfv[2]]), ( + me.vertices[tfv[2]], + me.vertices[tfv[3]], + me.vertices[tfv[0]], + ) for tf_iter in tf_tris: v1 = ob_mat @ tf_iter[0].co @@ -171,5 +175,6 @@ class GbxmlExporter(): volume += v1.dot(v2.cross(v3)) / 6.0 return volume + gbxml_exporter = GbxmlExporter() gbxml_exporter.export() diff --git a/src/ifcblenderexport/getIfcElements.py b/src/ifcblenderexport/getIfcElements.py index 0339eba73e..f4edf13493 100644 --- a/src/ifcblenderexport/getIfcElements.py +++ b/src/ifcblenderexport/getIfcElements.py @@ -4,33 +4,32 @@ import xml.etree.ElementTree as ET import collections import json + class IFC4Extractor: def __init__(self, xsd_file): self.xsd_file = xsd_file tree = ET.parse(self.xsd_file) self.root = tree.getroot() - self.ns = {'xs': 'http://www.w3.org/2001/XMLSchema'} + self.ns = {"xs": "http://www.w3.org/2001/XMLSchema"} self.elements = {} self.filters = [] self.filtered_elements = {} def extract(self): for element in self.root.findall("xs:element", self.ns): - print('Processing {}'.format(element.attrib['name'])) - if not 'substitutionGroup' in element.attrib \ - or self.is_descendant_from_class(element, 'uos'): + print("Processing {}".format(element.attrib["name"])) + if not "substitutionGroup" in element.attrib or self.is_descendant_from_class(element, "uos"): continue data = { - 'is_abstract': self.is_abstract(element), - 'parent': element.attrib['substitutionGroup'].replace('ifc:', ''), - 'attributes': self.get_attributes(element), - 'complex_attributes': self.get_complex_attributes(element) + "is_abstract": self.is_abstract(element), + "parent": element.attrib["substitutionGroup"].replace("ifc:", ""), + "attributes": self.get_attributes(element), + "complex_attributes": self.get_complex_attributes(element), } - self.elements[element.attrib['name']] = data + self.elements[element.attrib["name"]] = data for filter in self.filters: - if self.is_descendant_from_class(element, filter) \ - and not data['is_abstract']: - self.filtered_elements.setdefault(filter, {})[element.attrib['name']] = data + if self.is_descendant_from_class(element, filter) and not data["is_abstract"]: + self.filtered_elements.setdefault(filter, {})[element.attrib["name"]] = data def export(self, filename): final = {} @@ -40,86 +39,100 @@ class IFC4Extractor: file.write(json.dumps(collections.OrderedDict(sorted(final.items())), indent=4)) def is_descendant_from_class(self, element, class_name): - if element is None \ - or 'substitutionGroup' not in element.attrib \ - or 'type' not in element.attrib: + if element is None or "substitutionGroup" not in element.attrib or "type" not in element.attrib: return False - if element.attrib['substitutionGroup'] == 'ifc:{}'.format(class_name) \ - or element.attrib['type'] == 'ifc:{}'.format(class_name): + if element.attrib["substitutionGroup"] == "ifc:{}".format(class_name) or element.attrib[ + "type" + ] == "ifc:{}".format(class_name): return True return self.is_descendant_from_class(self.get_parent_element(element), class_name) def is_abstract(self, element): - return True if 'abstract' in element.attrib else False + return True if "abstract" in element.attrib else False - def get_attributes(self, element, attributes = None): + def get_attributes(self, element, attributes=None): if attributes is None: attributes = [] - if element.attrib['substitutionGroup'] != self.get_ifcroot_parent_name(): + if element.attrib["substitutionGroup"] != self.get_ifcroot_parent_name(): attributes = self.get_attributes(self.get_parent_element(element), attributes) for attribute in self.root.findall(self.get_attribute_xpath(element), self.ns): try: - attributes.append({ - 'name': attribute.attrib['name'], - 'type': attribute.attrib['type'].replace('ifc:', ''), - 'is_enum': self.is_enum(attribute), - 'enum_values': self.get_enum_values(attribute) - }) + attributes.append( + { + "name": attribute.attrib["name"], + "type": attribute.attrib["type"].replace("ifc:", ""), + "is_enum": self.is_enum(attribute), + "enum_values": self.get_enum_values(attribute), + } + ) except KeyError as e: - print('Attribute {} is missing key {}'.format(attribute.attrib, e)) + print("Attribute {} is missing key {}".format(attribute.attrib, e)) return attributes def get_attribute_xpath(self, element): - return "./xs:complexType[@name='{}']/xs:complexContent/xs:extension/xs:attribute[@name][@type]".format(element.attrib['name']) + return "./xs:complexType[@name='{}']/xs:complexContent/xs:extension/xs:attribute[@name][@type]".format( + element.attrib["name"] + ) def get_ifcroot_parent_name(self): return "ifc:Entity" - def get_complex_attributes(self, element, attributes = None): + def get_complex_attributes(self, element, attributes=None): if attributes is None: attributes = [] - if element.attrib['substitutionGroup'] != self.get_ifcroot_parent_name(): + if element.attrib["substitutionGroup"] != self.get_ifcroot_parent_name(): attributes = self.get_complex_attributes(self.get_parent_element(element), attributes) for attribute in self.root.findall( "./xs:complexType[@name='{}']/xs:complexContent/xs:extension/xs:sequence/xs:element[@name]".format( - element.attrib['name'] - ), self.ns): - if 'type' in attribute.attrib: - attributes.append({ - 'name': attribute.attrib['name'], - 'type': attribute.attrib['type'].replace('ifc:', ''), - 'is_select': False, - 'select_types': [] - }) + element.attrib["name"] + ), + self.ns, + ): + if "type" in attribute.attrib: + attributes.append( + { + "name": attribute.attrib["name"], + "type": attribute.attrib["type"].replace("ifc:", ""), + "is_select": False, + "select_types": [], + } + ) else: - type_element = attribute.find('./xs:complexType/xs:sequence/xs:element[@ref]', self.ns) + type_element = attribute.find("./xs:complexType/xs:sequence/xs:element[@ref]", self.ns) is_select = False select_types = [] if not type_element: # Handle select (i.e. group) attributes - type_element = attribute.find('./xs:complexType/xs:group', self.ns) + type_element = attribute.find("./xs:complexType/xs:group", self.ns) if type_element is not None: is_select = True - select_types = [e.attrib['ref'].replace('ifc:', '').replace('-wrapper', '') for e in - self.root.findall("./xs:group[@name='{}']/xs:choice/xs:element[@ref]".format( - type_element.attrib['ref'].replace('ifc:', '') - ), self.ns)] + select_types = [ + e.attrib["ref"].replace("ifc:", "").replace("-wrapper", "") + for e in self.root.findall( + "./xs:group[@name='{}']/xs:choice/xs:element[@ref]".format( + type_element.attrib["ref"].replace("ifc:", "") + ), + self.ns, + ) + ] if type_element is not None: - attributes.append({ - 'name': attribute.attrib['name'], - 'type': type_element.attrib['ref'].replace('ifc:', ''), - 'is_select': is_select, - 'select_types': select_types - }) + attributes.append( + { + "name": attribute.attrib["name"], + "type": type_element.attrib["ref"].replace("ifc:", ""), + "is_select": is_select, + "select_types": select_types, + } + ) return attributes def get_parent_element(self, element): - return self.root.find("./xs:element[@name='{}']".format( - element.attrib["substitutionGroup"].replace('ifc:', '') - ), self.ns) + return self.root.find( + "./xs:element[@name='{}']".format(element.attrib["substitutionGroup"].replace("ifc:", "")), self.ns + ) def is_enum(self, attribute): - return 'Enum' in attribute.attrib['type'] + return "Enum" in attribute.attrib["type"] def get_enum_values(self, attribute): if not self.is_enum(attribute): @@ -127,43 +140,49 @@ class IFC4Extractor: values = [] for enumeration in self.root.findall( "./xs:simpleType[@name='{}']/xs:restriction/xs:enumeration".format( - attribute.attrib['type'].replace('ifc:', '') - ), self.ns): - values.append(enumeration.attrib['value'].upper()) + attribute.attrib["type"].replace("ifc:", "") + ), + self.ns, + ): + values.append(enumeration.attrib["value"].upper()) return values def is_ifc_version(self, version): return version in self.xsd_file + class IFC2X3Extractor(IFC4Extractor): # IFC2X3 seems to store regular attributes where IFC4 stores complex attributes def get_attribute_xpath(self, element): - return "./xs:complexType[@name='{}']/xs:complexContent/xs:extension/xs:sequence/xs:element[@name]".format(element.attrib['name']) + return "./xs:complexType[@name='{}']/xs:complexContent/xs:extension/xs:sequence/xs:element[@name]".format( + element.attrib["name"] + ) def get_ifcroot_parent_name(self): return "ex:Entity" # IFC2X3 does not seem to store complex attributes in the XSD file - def get_complex_attributes(self, element, attributes = None): + def get_complex_attributes(self, element, attributes=None): return [] + filename_filters = { - 'IfcContext_IFC4.json': ['IfcContext'], - 'IfcElement_IFC4.json': ['IfcElement'], - 'IfcSpatialElement_IFC4.json': ['IfcSpatialElement'], - 'IfcGroup_IFC4.json': ['IfcGroup'], - 'IfcStructural_IFC4.json': ['IfcStructuralActivity', 'IfcStructuralItem'], - 'IfcMaterialDefinition_IFC4.json': ['IfcMaterialDefinition'], - 'IfcParameterizedProfileDef_IFC4.json': ['IfcParameterizedProfileDef'], - 'IfcBoundaryCondition_IFC4.json': ['IfcBoundaryCondition'], - 'IfcElementType_IFC4.json': ['IfcElementType', 'IfcSpatialElementType'], - 'IfcAnnotation_IFC4.json': ['IfcAnnotation'], - 'IfcPositioningElement_IFC4.json': ['IfcGrid', 'IfcGridAxis'] # IfcPositioningElement in the future + "IfcContext_IFC4.json": ["IfcContext"], + "IfcElement_IFC4.json": ["IfcElement"], + "IfcSpatialElement_IFC4.json": ["IfcSpatialElement"], + "IfcGroup_IFC4.json": ["IfcGroup"], + "IfcStructural_IFC4.json": ["IfcStructuralActivity", "IfcStructuralItem"], + "IfcMaterialDefinition_IFC4.json": ["IfcMaterialDefinition"], + "IfcParameterizedProfileDef_IFC4.json": ["IfcParameterizedProfileDef"], + "IfcBoundaryCondition_IFC4.json": ["IfcBoundaryCondition"], + "IfcElementType_IFC4.json": ["IfcElementType", "IfcSpatialElementType"], + "IfcAnnotation_IFC4.json": ["IfcAnnotation"], + "IfcPositioningElement_IFC4.json": ["IfcGrid", "IfcGridAxis"], # IfcPositioningElement in the future } for filename, filters in filename_filters.items(): extractor = IFC4Extractor("IFC4_ADD2.xsd") extractor.filters = filters - #extractor = IFC2X3Extractor("IFC2X3.xsd") + # extractor = IFC2X3Extractor("IFC2X3.xsd") extractor.extract() extractor.export(filename) diff --git a/src/ifcblenderexport/get_description.py b/src/ifcblenderexport/get_description.py index 85b18d1942..2c60f47468 100644 --- a/src/ifcblenderexport/get_description.py +++ b/src/ifcblenderexport/get_description.py @@ -5,12 +5,13 @@ import json from pathlib import Path import ifcopenshell -class Describer(): + +class Describer: def describe(self): # BuildingSMART does not provide a computer interpretable set of # descriptions. They provide HTML docs, which contained malformed / # invalid HTML. Therefore, this dodgy hack was written. - schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name('IFC4') + schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name("IFC4") self.html_sources = {} self.get_html_sources() @@ -27,18 +28,18 @@ class Describer(): continue if isinstance(attribute, str): continue - if 'Enum' in attribute.name() and 'Enumeration' not in attribute.name(): + if "Enum" in attribute.name() and "Enumeration" not in attribute.name(): self.get_enum_descriptions(attribute) - - with open('entity_descriptions.json', 'w') as f: + + with open("entity_descriptions.json", "w") as f: f.write(json.dumps(self.entity_descriptions, indent=4)) - with open('enum_descriptions.json', 'w') as f: + with open("enum_descriptions.json", "w") as f: f.write(json.dumps(self.enum_descriptions, indent=4)) def get_html_sources(self): - html_dir = '/home/dion/Projects/IfcOpenShell/src/ifcblenderexport/descriptions/IFC4_3/RC1/HTML' - for filename in Path(html_dir).rglob('*.htm'): - if 'lexical' not in str(filename): + html_dir = "/home/dion/Projects/IfcOpenShell/src/ifcblenderexport/descriptions/IFC4_3/RC1/HTML" + for filename in Path(html_dir).rglob("*.htm"): + if "lexical" not in str(filename): continue name = os.path.basename(filename)[0:-4] self.html_sources[name] = filename @@ -48,8 +49,10 @@ class Describer(): return with open(self.html_sources[name.lower()]) as f: for line in f: - if 'Entity definition' in line: - self.entity_descriptions[name] = html.unescape(re.sub('<.*?>', '', line.strip().replace('Entity definition', ''))) + if "Entity definition" in line: + self.entity_descriptions[name] = html.unescape( + re.sub("<.*?>", "", line.strip().replace("Entity definition", "")) + ) def get_enum_descriptions(self, enum): if enum.name().lower() not in self.html_sources: @@ -59,8 +62,10 @@ class Describer(): for item in enum.enumeration_items(): with open(self.html_sources[enum.name().lower()]) as f: for line in f: - if ''+item+'' in line: - self.enum_descriptions.setdefault(enum.name(), {})[item] = html.unescape(re.sub('<.*?>', '', line.strip().replace(item, ''))) + if "" + item + "" in line: + self.enum_descriptions.setdefault(enum.name(), {})[item] = html.unescape( + re.sub("<.*?>", "", line.strip().replace(item, "")) + ) describer = Describer() diff --git a/src/ifcblenderexport/occ_utils.py b/src/ifcblenderexport/occ_utils.py index bdbe29ae82..d554b1dff6 100755 --- a/src/ifcblenderexport/occ_utils.py +++ b/src/ifcblenderexport/occ_utils.py @@ -26,41 +26,44 @@ import operator import warnings from collections import namedtuple + try: # python 3.3+ from collections.abc import Iterable -except ModuleNotFoundError: # python 2 +except ModuleNotFoundError: # python 2 from collections import Iterable try: - from OCC.Core import TopoDS, gp, Quantity, BRepTools + from OCC.Core import TopoDS, gp, Quantity, BRepTools + try: from OCC.Core import V3d, AIS, Graphic3d except ImportError: pass except ImportError: - from OCC import TopoDS, gp, Quantity, BRepTools + from OCC import TopoDS, gp, Quantity, BRepTools + try: from OCC import V3d, AIS, Graphic3d except ImportError: pass -shape_tuple = namedtuple('shape_tuple', ('data', 'geometry', 'styles')) +shape_tuple = namedtuple("shape_tuple", ("data", "geometry", "styles")) handle, main_loop, add_menu, add_function_to_menu = None, None, None, None DEFAULT_STYLES = { - "DEFAULT": (.7, .7, .7), - "IfcWall": (.8, .8, .8), - "IfcSite": (.75, .8, .65), - "IfcSlab": (.4, .4, .4), - "IfcWallStandardCase": (.9, .9, .9), - "IfcWall": (.9, .9, .9), - "IfcWindow": (.75, .8, .75, .3), - "IfcDoor": (.55, .3, .15), - "IfcBeam": (.75, .7, .7), - "IfcRailing": (.65, .6, .6), - "IfcMember": (.65, .6, .6), - "IfcPlate": (.8, .8, .8) + "DEFAULT": (0.7, 0.7, 0.7), + "IfcWall": (0.8, 0.8, 0.8), + "IfcSite": (0.75, 0.8, 0.65), + "IfcSlab": (0.4, 0.4, 0.4), + "IfcWallStandardCase": (0.9, 0.9, 0.9), + "IfcWall": (0.9, 0.9, 0.9), + "IfcWindow": (0.75, 0.8, 0.75, 0.3), + "IfcDoor": (0.55, 0.3, 0.15), + "IfcBeam": (0.75, 0.7, 0.7), + "IfcRailing": (0.65, 0.6, 0.6), + "IfcMember": (0.65, 0.6, 0.6), + "IfcPlate": (0.8, 0.8, 0.8), } @@ -119,7 +122,7 @@ def display_shape(shape, clr=None, viewer_handle=None): if representation and not clr: if len(set(representation.styles)) == 1: clr = representation.styles[0] - if min(clr) < 0. or max(clr) > 1.: + if min(clr) < 0.0 or max(clr) > 1.0: clr = DEFAULT_STYLES.get(representation.data.type, DEFAULT_STYLES["DEFAULT"]) if clr: @@ -127,8 +130,9 @@ def display_shape(shape, clr=None, viewer_handle=None): ais.SetMaterial(material) if isinstance(clr, str): - qclr = getattr(Quantity, "Quantity_NOC_%s" % clr.upper(), - getattr(Quantity, "Quantity_NOC_%s1" % clr.upper(), None)) + qclr = getattr( + Quantity, "Quantity_NOC_%s" % clr.upper(), getattr(Quantity, "Quantity_NOC_%s1" % clr.upper(), None) + ) if qclr is None: raise Exception("No color named '%s'" % clr.upper()) elif isinstance(clr, Iterable): @@ -142,8 +146,8 @@ def display_shape(shape, clr=None, viewer_handle=None): raise Exception("Object of type %r cannot be used as a color." % type(clr)) ais.SetColor(qclr) - if isinstance(clr, tuple) and len(clr) == 4 and clr[3] < 1.: - ais.SetTransparency(1. - clr[3]) + if isinstance(clr, tuple) and len(clr) == 4 and clr[3] < 1.0: + ais.SetTransparency(1.0 - clr[3]) elif representation and hasattr(AIS, "AIS_MultipleConnectedShape"): default_style_applied = None @@ -157,13 +161,14 @@ def display_shape(shape, clr=None, viewer_handle=None): else: for shp, stl in zip(subshapes, representation.styles): subshape = AIS.AIS_Shape(shp) - if min(stl) < 0. or max(stl) > 1.: - default_style_applied = stl = DEFAULT_STYLES.get(representation.data.type, - DEFAULT_STYLES["DEFAULT"]) + if min(stl) < 0.0 or max(stl) > 1.0: + default_style_applied = stl = DEFAULT_STYLES.get( + representation.data.type, DEFAULT_STYLES["DEFAULT"] + ) subshape.SetColor(Quantity.Quantity_Color(stl[0], stl[1], stl[2], Quantity.Quantity_TOC_RGB)) subshape.SetMaterial(material) - if len(stl) == 4 and stl[3] < 1.: - subshape.SetTransparency(1. - stl[3]) + if len(stl) == 4 and stl[3] < 1.0: + subshape.SetTransparency(1.0 - stl[3]) ais.Connect(subshape.GetHandle()) # For some reason it is necessary to set transparency here again @@ -171,14 +176,14 @@ def display_shape(shape, clr=None, viewer_handle=None): applied_styles = representation.styles if default_style_applied: if len(default_style_applied) == 3: - default_style_applied += (1.,) + default_style_applied += (1.0,) applied_styles += (default_style_applied,) if len(applied_styles): # The only way for this not to be true if is the entire shape is NULL min_transp = min(map(operator.itemgetter(3), applied_styles)) - if min_transp < 1.: - ais.SetTransparency(1.) + if min_transp < 1.0: + ais.SetTransparency(1.0) else: ais = AIS.AIS_Shape(shape) @@ -201,10 +206,10 @@ def set_shape_transparency(ais, t): def get_bounding_box_center(bbox): - bbmin = [0.] * 3 - bbmax = [0.] * 3 + bbmin = [0.0] * 3 + bbmax = [0.0] * 3 bbmin[0], bbmin[1], bbmin[2], bbmax[0], bbmax[1], bbmax[2] = bbox.Get() - return gp.gp_Pnt(*map(lambda xy: (xy[0] + xy[1]) / 2., zip(bbmin, bbmax))) + return gp.gp_Pnt(*map(lambda xy: (xy[0] + xy[1]) / 2.0, zip(bbmin, bbmax))) def serialize_shape(shape): @@ -228,7 +233,7 @@ def create_shape_from_serialization(brep_object): except BaseException: pass - styles = tuple(styles[i:i + 4] for i in range(0, len(styles), 4)) + styles = tuple(styles[i : i + 4] for i in range(0, len(styles), 4)) if not brep_data: return shape_tuple(brep_object, None, styles)