diff --git a/src/blenderbim/blenderbim/bim/module/drawing/__init__.py b/src/blenderbim/blenderbim/bim/module/drawing/__init__.py index e2e2537834..a2ac21cffc 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/__init__.py @@ -36,6 +36,7 @@ classes = ( operator.CopyGrid, operator.CreateDrawing, operator.CreateSheets, + operator.DisableEditingDrawings, operator.DisableEditingSheets, operator.DisableEditingText, operator.DisableEditingTextProduct, @@ -45,11 +46,10 @@ classes = ( operator.EnableEditingText, operator.EnableEditingTextProduct, operator.GenerateReferences, + operator.LoadDrawings, operator.LoadSheets, operator.OpenSheet, operator.OpenView, - operator.OpenViewCamera, - operator.RefreshDrawingList, operator.RemoveDrawing, operator.RemoveDrawingStyle, operator.RemoveDrawingStyleAttribute, diff --git a/src/blenderbim/blenderbim/bim/module/drawing/data.py b/src/blenderbim/blenderbim/bim/module/drawing/data.py index a402ccba47..2408e9e12e 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/data.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/data.py @@ -24,6 +24,7 @@ import blenderbim.tool as tool def refresh(): TextData.is_loaded = False SheetsData.is_loaded = False + DrawingsData.is_loaded = False class TextData: @@ -69,3 +70,17 @@ class SheetsData: @classmethod def total_sheets(cls): return len([d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "DOCUMENTATION"]) + + +class DrawingsData: + data = {} + is_loaded = False + + @classmethod + def load(cls): + cls.data = {"total_drawings": cls.total_drawings()} + cls.is_loaded = True + + @classmethod + def total_drawings(cls): + return len([e for e in tool.Ifc.get().by_type("IfcAnnotation") if e.ObjectType == "DRAWING"]) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/handler.py b/src/blenderbim/blenderbim/bim/module/drawing/handler.py index 27ea862264..e30ac87b13 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/handler.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/handler.py @@ -45,6 +45,3 @@ def set_active_camera_resolution(scene): 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]) - bpy.ops.bim.activate_view(drawing_index=scene.DocProperties.current_drawing_index) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/helper.py b/src/blenderbim/blenderbim/bim/module/drawing/helper.py index 3d9c6268d2..a776312739 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/helper.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/helper.py @@ -19,6 +19,7 @@ import bpy import math import mathutils.geometry +import blenderbim.tool as tool from mathutils import Vector # Code taken and updated from https://blenderartists.org/t/detecting-intersection-of-bounding-boxes/457520/2 @@ -263,7 +264,8 @@ def get_active_drawing(scene): return None, None try: drawing = props.active_drawing - return drawing.camera.users_collection[0], drawing.camera + camera = tool.Ifc.get_object(tool.Ifc.get().by_id(drawing.ifc_definition_id)) + return camera.users_collection[0], camera except (KeyError, IndexError): raise RuntimeError("missing drawing collection") diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py index 00117f34b8..1dde36578e 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py @@ -79,14 +79,13 @@ class AddDrawing(bpy.types.Operator): def _execute(self, context): self.file = IfcStore.get_file() - new = context.scene.DocProperties.drawings.add() - new.name = "DRAWING {}".format(len(context.scene.DocProperties.drawings)) + drawing_name = tool.Drawing.ensure_unique_drawing_name("UNTITLED") if not bpy.data.collections.get("Views"): 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) + view_collection = bpy.data.collections.new("IfcGroup/" + drawing_name) views_collection.children.link(view_collection) - camera = bpy.data.objects.new(new.name, bpy.data.cameras.new(new.name)) + camera = bpy.data.objects.new(drawing_name, bpy.data.cameras.new(drawing_name)) camera.location = (0, 0, 1.5) # The view shall be 1.5m above the origin camera.data.type = "ORTHO" camera.data.ortho_scale = 50 # The default of 6m is too small @@ -99,13 +98,12 @@ class AddDrawing(bpy.types.Operator): view_collection.objects.link(camera) area = next(area for area in context.screen.areas if area.type == "VIEW_3D") area.spaces[0].region_3d.view_perspective = "CAMERA" - new.camera = camera bpy.ops.bim.assign_class(obj=camera.name, ifc_class="IfcAnnotation", predefined_type="DRAWING") bpy.ops.bim.activate_drawing_style() bpy.ops.bim.add_group() group = self.file.by_id(sorted(GroupData.groups.keys())[-1]) - ifcopenshell.api.run("group.edit_group", self.file, **{"group": group, "attributes": {"Name": new.name}}) + ifcopenshell.api.run("group.edit_group", self.file, **{"group": group, "attributes": {"Name": drawing_name}}) bpy.ops.bim.assign_group(product=camera.name, group=group.id()) pset = ifcopenshell.api.run( "pset.add_pset", @@ -125,6 +123,7 @@ class AddDrawing(bpy.types.Operator): }, ) PsetData.load(IfcStore.get_file(), camera.BIMObjectProperties.ifc_definition_id) + tool.Drawing.import_drawings() return {"FINISHED"} @@ -729,40 +728,14 @@ class OpenView(bpy.types.Operator): return {"FINISHED"} -class OpenViewCamera(bpy.types.Operator): - """Select this drawing's camera object and expand its drawing properties""" - - bl_idname = "bim.open_view_camera" - bl_label = "Open View Camera" - bl_options = {"REGISTER", "UNDO"} - view_name: bpy.props.StringProperty() - - @classmethod - def poll(cls, context): - return context.mode == "OBJECT" - - def execute(self, context): - doc_props = context.scene.DocProperties - doc_props.active_drawing_index = doc_props.drawings.find(self.view_name) - drawing = doc_props.active_drawing - bpy.ops.object.select_all(action="DESELECT") - drawing.camera.select_set(True) - context.view_layer.objects.active = drawing.camera - for area in context.screen.areas: - if area.ui_type == "PROPERTIES": - for space in area.spaces: - space.context = "DATA" - return {"FINISHED"} - - class ActivateView(bpy.types.Operator): bl_idname = "bim.activate_view" bl_label = "Activate View" bl_options = {"REGISTER", "UNDO"} - drawing_index: bpy.props.IntProperty() + drawing: bpy.props.IntProperty() def execute(self, context): - camera = context.scene.DocProperties.drawings[self.drawing_index].camera + camera = tool.Ifc.get_object(tool.Ifc.get().by_id(self.drawing)) if not camera: return {"FINISHED"} area = next(area for area in context.screen.areas if area.type == "VIEW_3D") @@ -1161,23 +1134,6 @@ class RemoveDrawingStyleAttribute(bpy.types.Operator): return {"FINISHED"} -class RefreshDrawingList(bpy.types.Operator): - bl_idname = "bim.refresh_drawing_list" - bl_label = "Refresh Drawing List" - - def execute(self, context): - doc_props = context.scene.DocProperties - doc_props.drawings.clear() - for obj in context.scene.objects: - if not isinstance(obj.data, bpy.types.Camera): - continue - if "IfcAnnotation/" in obj.name: - new = doc_props.drawings.add() - new.name = "/".join(obj.name.split("/")[1:]) - new.camera = obj - return {"FINISHED"} - - class CleanWireframes(bpy.types.Operator): bl_idname = "bim.clean_wireframes" bl_label = "Clean Wireframes" @@ -1429,3 +1385,21 @@ class DisableEditingSheets(bpy.types.Operator, Operator): def _execute(self, context): core.disable_editing_sheets(tool.Drawing) + + +class LoadDrawings(bpy.types.Operator, Operator): + bl_idname = "bim.load_drawings" + bl_label = "Load Drawings" + bl_options = {"REGISTER", "UNDO"} + + def _execute(self, context): + core.load_drawings(tool.Drawing) + + +class DisableEditingDrawings(bpy.types.Operator, Operator): + bl_idname = "bim.disable_editing_drawings" + bl_label = "Disable Editing Text Product" + bl_options = {"REGISTER", "UNDO"} + + def _execute(self, context): + core.disable_editing_drawings(tool.Drawing) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/prop.py b/src/blenderbim/blenderbim/bim/module/drawing/prop.py index 52415ee650..b23b771657 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/prop.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/prop.py @@ -144,10 +144,6 @@ def updateDrawingName(self, context): self.name = unique_name -def refreshActiveDrawingIndex(self, context): - bpy.ops.bim.activate_view(drawing_index=context.scene.DocProperties.active_drawing_index) - - def getTitleblocks(self, context): global titleblocks_enum if len(titleblocks_enum) < 1: @@ -196,6 +192,7 @@ class Variable(PropertyGroup): class Drawing(PropertyGroup): + ifc_definition_id: IntProperty(name="IFC Definition ID") name: StringProperty(name="Name", update=updateDrawingName) camera: PointerProperty(name="Camera", type=bpy.types.Object) @@ -276,8 +273,20 @@ class DocProperties(PropertyGroup): should_use_linework_cache: BoolProperty(name="Use Linework Cache", default=False) should_use_annotation_cache: BoolProperty(name="Use Annotation Cache", default=False) should_extract: BoolProperty(name="Should Extract", default=True) + is_editing_drawings: BoolProperty(name="Is Editing Drawings", default=False) + target_view: EnumProperty( + items=[ + ("PLAN_VIEW", "Plan", ""), + ("ELEVATION_VIEW", "Elevation", ""), + ("SECTION_VIEW", "Section", ""), + ("REFLECTED_PLAN_VIEW", "RCP", ""), + ("MODEL_VIEW", "Model", ""), + ], + name="Target View", + default="PLAN_VIEW", + ) drawings: CollectionProperty(name="Drawings", type=Drawing) - active_drawing_index: IntProperty(name="Active Drawing Index", update=refreshActiveDrawingIndex) + active_drawing_index: IntProperty(name="Active Drawing Index") current_drawing_index: IntProperty(name="Current Drawing Index") schedules: CollectionProperty(name="Schedules", type=Schedule) active_schedule_index: IntProperty(name="Active Schedule Index") diff --git a/src/blenderbim/blenderbim/bim/module/drawing/ui.py b/src/blenderbim/blenderbim/bim/module/drawing/ui.py index 57e1c347a4..33c8af1ddf 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/ui.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/ui.py @@ -20,7 +20,7 @@ import bpy import blenderbim.bim.helper import blenderbim.tool as tool from bpy.types import Panel -from blenderbim.bim.module.drawing.data import TextData, SheetsData +from blenderbim.bim.module.drawing.data import TextData, SheetsData, DrawingsData class BIM_PT_camera(Panel): @@ -156,29 +156,42 @@ class BIM_PT_drawings(Panel): bl_category = "BIM Documentation" def draw(self, context): - layout = self.layout - layout.use_property_split = True - props = context.scene.DocProperties + if not DrawingsData.is_loaded: + DrawingsData.load() - row = layout.row(align=True) - row.operator("bim.add_drawing") - row.operator("bim.refresh_drawing_list", icon="FILE_REFRESH", text="") + self.props = context.scene.DocProperties - if props.drawings: - if props.active_drawing_index < len(props.drawings): + if not self.props.is_editing_drawings: + row = self.layout.row(align=True) + row.label(text=f"{DrawingsData.data['total_drawings']} Drawings Found", icon="IMAGE_DATA") + row.operator("bim.load_drawings", text="", icon="IMPORT") + return + + row = self.layout.row(align=True) + row.prop(self.props, "target_view", text="") + row.operator("bim.add_drawing", text="", icon="ADD") + row.operator("bim.disable_editing_drawings", text="", icon="CANCEL") + + if self.props.drawings: + if self.props.active_drawing_index < len(self.props.drawings): + row = self.layout.row(align=True) + row.alignment = "RIGHT" op = row.operator("bim.open_view", icon="URL", text="") - op.view = props.active_drawing.name - row.operator("bim.remove_drawing", icon="X", text="").index = props.active_drawing_index - layout.template_list("BIM_UL_drawinglist", "", props, "drawings", props, "active_drawing_index") + op.view = self.props.active_drawing.name + op = row.operator("bim.activate_view", icon="OUTLINER_OB_CAMERA", text="") + op.drawing = self.props.active_drawing.ifc_definition_id + row.operator("bim.remove_drawing", icon="X", text="").index = self.props.active_drawing_index + self.layout.template_list("BIM_UL_drawinglist", "", self.props, "drawings", self.props, "active_drawing_index") - row = layout.row() - row.operator("bim.add_ifc_file") + # Commented out until federated drawing generation is rebuilt + # row = self.layout.row() + # 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="").index = index - row.operator("bim.remove_ifc_file", icon="X", text="").index = index + # for index, ifc_file in enumerate(self.props.ifc_files): + # row = self.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="").index = index + # row.operator("bim.remove_ifc_file", icon="X", text="").index = index class BIM_PT_schedules(Panel): @@ -187,7 +200,6 @@ class BIM_PT_schedules(Panel): bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_category = "BIM Documentation" - bl_options = {"DEFAULT_CLOSED"} def draw(self, context): layout = self.layout @@ -365,8 +377,6 @@ class BIM_UL_drawinglist(bpy.types.UIList): if item: row = layout.row(align=True) row.prop(item, "name", text="", emboss=False) - op = row.operator("bim.open_view_camera", icon="OUTLINER_OB_CAMERA", text="") - op.view_name = item.name else: layout.label(text="", translate=False) diff --git a/src/blenderbim/blenderbim/core/drawing.py b/src/blenderbim/blenderbim/core/drawing.py index 83957651a6..4c9789047b 100644 --- a/src/blenderbim/blenderbim/core/drawing.py +++ b/src/blenderbim/blenderbim/core/drawing.py @@ -87,3 +87,12 @@ def open_sheet(drawing, sheet=None): def remove_sheet(ifc, drawing, sheet=None): ifc.run("document.remove_document", document=sheet) drawing.import_sheets() + + +def load_drawings(drawing): + drawing.import_drawings() + drawing.enable_editing_drawings() + + +def disable_editing_drawings(drawing): + drawing.disable_editing_drawings() diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index dd849e9189..71e8e63d8f 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -136,9 +136,11 @@ class Debug: @interface class Drawing: def create_svg_sheet(cls, document, titleblock): pass + def disable_editing_drawings(cls): pass def disable_editing_sheets(cls): pass def disable_editing_text(cls, obj): pass def disable_editing_text_product(cls, obj): pass + def enable_editing_drawings(cls): pass def enable_editing_sheets(cls): pass def enable_editing_text(cls, obj): pass def enable_editing_text_product(cls, obj): pass @@ -148,6 +150,7 @@ class Drawing: def get_sheet_filename(cls, document): pass def get_text_literal(cls, obj): pass def get_text_product(cls, element): pass + def import_drawings(cls): pass def import_sheets(cls): pass def import_text_attributes(cls, obj): pass def import_text_product(cls, obj): pass diff --git a/src/blenderbim/blenderbim/tool/drawing.py b/src/blenderbim/blenderbim/tool/drawing.py index 23f2240536..2ae6c232c6 100644 --- a/src/blenderbim/blenderbim/tool/drawing.py +++ b/src/blenderbim/blenderbim/tool/drawing.py @@ -33,6 +33,10 @@ class Drawing(blenderbim.core.tool.Drawing): sheet_builder.data_dir = bpy.context.scene.BIMProperties.data_dir sheet_builder.create(cls.get_sheet_filename(document), titleblock) + @classmethod + def disable_editing_drawings(cls): + bpy.context.scene.DocProperties.is_editing_drawings = False + @classmethod def disable_editing_sheets(cls): bpy.context.scene.DocProperties.is_editing_sheets = False @@ -45,6 +49,10 @@ class Drawing(blenderbim.core.tool.Drawing): def disable_editing_text_product(cls, obj): obj.BIMTextProperties.is_editing_product = False + @classmethod + def enable_editing_drawings(cls): + bpy.context.scene.DocProperties.is_editing_drawings = True + @classmethod def enable_editing_sheets(cls): bpy.context.scene.DocProperties.is_editing_sheets = True @@ -57,6 +65,13 @@ class Drawing(blenderbim.core.tool.Drawing): def enable_editing_text_product(cls, obj): obj.BIMTextProperties.is_editing_product = True + @classmethod + def ensure_unique_drawing_name(cls, name): + names = [e.Name for e in tool.Ifc.get().by_type("IfcAnnotation") if e.ObjectType == "DRAWING"] + while name in names: + name += "-X" + return name + @classmethod def ensure_unique_identification(cls, identification): if tool.Ifc.get_schema() == "IFC2X3": @@ -105,6 +120,15 @@ class Drawing(blenderbim.core.tool.Drawing): if rel.is_a("IfcRelAssignsToProduct"): return rel.RelatingProduct + @classmethod + def import_drawings(cls): + bpy.context.scene.DocProperties.drawings.clear() + drawings = [e for e in tool.Ifc.get().by_type("IfcAnnotation") if e.ObjectType == "DRAWING"] + for drawing in drawings: + new = bpy.context.scene.DocProperties.drawings.add() + new.ifc_definition_id = drawing.id() + new.name = drawing.Name or "Unnamed" + @classmethod def import_sheets(cls): bpy.context.scene.DocProperties.sheets.clear() diff --git a/src/blenderbim/test/core/test_drawing.py b/src/blenderbim/test/core/test_drawing.py index aec03a03e8..34f7f54a3c 100644 --- a/src/blenderbim/test/core/test_drawing.py +++ b/src/blenderbim/test/core/test_drawing.py @@ -124,3 +124,16 @@ class TestRemoveSheet: ifc.run("document.remove_document", document="sheet").should_be_called() drawing.import_sheets().should_be_called() subject.remove_sheet(ifc, drawing, sheet="sheet") + + +class TestLoadDrawings: + def test_run(self, drawing): + drawing.import_drawings().should_be_called() + drawing.enable_editing_drawings().should_be_called() + subject.load_drawings(drawing) + + +class TestDisableEditingDrawings: + def test_run(self, drawing): + drawing.disable_editing_drawings().should_be_called() + subject.disable_editing_drawings(drawing) diff --git a/src/blenderbim/test/tool/test_drawing.py b/src/blenderbim/test/tool/test_drawing.py index 48829bd850..c54d4ac42d 100644 --- a/src/blenderbim/test/tool/test_drawing.py +++ b/src/blenderbim/test/tool/test_drawing.py @@ -38,6 +38,13 @@ class TestCreateSvgSheet(NewFile): assert os.path.isfile(os.path.join(bpy.context.scene.BIMProperties.data_dir, "sheets", "X - FOOBAR.svg")) +class TestDisableEditingDrawings(NewFile): + def test_run(self): + bpy.context.scene.DocProperties.is_editing_drawings = True + subject.disable_editing_drawings() + assert bpy.context.scene.DocProperties.is_editing_drawings == False + + class TestDisableEditingSheets(NewFile): def test_run(self): bpy.context.scene.DocProperties.is_editing_sheets = True @@ -61,6 +68,13 @@ class TestDisableEditingTextProduct(NewFile): assert obj.BIMTextProperties.is_editing_product == False +class TestEnableEditingDrawings(NewFile): + def test_run(self): + bpy.context.scene.DocProperties.is_editing_drawings = False + subject.enable_editing_drawings() + assert bpy.context.scene.DocProperties.is_editing_drawings == True + + class TestEnableEditingSheets(NewFile): def test_run(self): bpy.context.scene.DocProperties.is_editing_sheets = False @@ -82,6 +96,17 @@ class TestEnableEditingTextProduct(NewFile): assert obj.BIMTextProperties.is_editing_product == True +class TestEnsureUniqueDrawingName(NewFile): + def test_run(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + assert subject.ensure_unique_drawing_name("FOOBAR") == "FOOBAR" + ifc.createIfcAnnotation(Name="FOOBAR", ObjectType="DRAWING") + assert subject.ensure_unique_drawing_name("FOOBAR") == "FOOBAR-X" + ifc.createIfcAnnotation(Name="FOOBAR-X", ObjectType="DRAWING") + assert subject.ensure_unique_drawing_name("FOOBAR") == "FOOBAR-X-X" + + class TestEnsureUniqueIdentification(NewFile): def test_run(self): ifc = ifcopenshell.file() @@ -158,6 +183,17 @@ class TestGetTextProduct(NewFile): assert subject.get_text_product(label) == wall +class TestImportDrawings(NewFile): + def test_run(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + drawing = ifc.createIfcAnnotation(Name="FOOBAR", ObjectType="DRAWING") + subject.import_drawings() + props = bpy.context.scene.DocProperties + assert props.drawings[0].ifc_definition_id == drawing.id() + assert props.drawings[0].name == "FOOBAR" + + class TestImportSheets(NewFile): def test_run(self): ifc = ifcopenshell.file()