diff --git a/src/blenderbim/blenderbim/bim/module/drawing/__init__.py b/src/blenderbim/blenderbim/bim/module/drawing/__init__.py index a2ac21cffc..795a33d173 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/__init__.py @@ -33,6 +33,7 @@ classes = ( operator.AddSheet, operator.BuildSchedule, operator.CleanWireframes, + operator.ContractSheet, operator.CopyGrid, operator.CreateDrawing, operator.CreateSheets, @@ -45,12 +46,14 @@ classes = ( operator.EditVectorStyle, operator.EnableEditingText, operator.EnableEditingTextProduct, + operator.ExpandSheet, operator.GenerateReferences, operator.LoadDrawings, operator.LoadSheets, operator.OpenSheet, operator.OpenView, operator.RemoveDrawing, + operator.RemoveDrawingFromSheet, operator.RemoveDrawingStyle, operator.RemoveDrawingStyleAttribute, operator.RemoveSchedule, diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py index c7d083fa13..34b8cdfcd2 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py @@ -571,7 +571,6 @@ class AddDrawingToSheet(bpy.types.Operator): bl_idname = "bim.add_drawing_to_sheet" bl_label = "Add Drawing To Sheet" bl_options = {"REGISTER", "UNDO"} - # TODO: check undo redo @classmethod def poll(cls, context): @@ -580,9 +579,78 @@ class AddDrawingToSheet(bpy.types.Operator): def execute(self, context): props = context.scene.DocProperties + active_drawing = props.drawings[props.active_drawing_index] + active_sheet = props.sheets[props.active_sheet_index] + drawing = tool.Ifc.get().by_id(active_drawing.ifc_definition_id) + sheet = tool.Ifc.get().by_id(active_sheet.ifc_definition_id) + if not sheet.is_a("IfcDocumentInformation"): + return {"FINISHED"} + + if tool.Ifc.get_schema() == "IFC2X3": + references = sheet.DocumentReferences or [] + else: + references = sheet.HasDocumentReferences or [] + + has_drawing = False + for reference in references: + new = props.sheets.add() + new.ifc_definition_id = reference.id() + new.is_sheet = False + + if tool.Ifc.get_schema() == "IFC2X3": + new.identification = reference.ItemReference or "X" + element = [r for r in tool.Ifc.by_type("IfcRelAssociatesDocument") if r.RelatingDocument == reference][ + 0 + ].RelatedObjects[0] + else: + new.identification = reference.Identification or "X" + element = reference.DocumentRefForObjects[0].RelatedObjects[0] + if element == drawing: + has_drawing = True + break + + if has_drawing: + return {"FINISHED"} + + reference = tool.Ifc.run("document.add_reference", information=sheet) + if tool.Ifc.get_schema() == "IFC2X3": + attributes = {"ItemReference": str(len(sheet.DocumentReferences or []))} + else: + attributes = {"Identification": str(len(sheet.HasDocumentReferences or []))} + tool.Ifc.run("document.edit_reference", reference=reference, attributes=attributes) + tool.Ifc.run("document.assign_document", product=drawing, document=reference) sheet_builder = sheeter.SheetBuilder() sheet_builder.data_dir = context.scene.BIMProperties.data_dir - sheet_builder.add_drawing(props.active_drawing.name, props.active_sheet.name) + sheet_builder.add_drawing(drawing, sheet) + + tool.Drawing.import_sheets() + return {"FINISHED"} + + +class RemoveDrawingFromSheet(bpy.types.Operator): + bl_idname = "bim.remove_drawing_from_sheet" + bl_label = "Remove Drawing From Sheet" + bl_options = {"REGISTER", "UNDO"} + reference: bpy.props.IntProperty() + + def execute(self, context): + reference = tool.Ifc.get().by_id(self.reference) + if tool.Ifc.get_schema() == "IFC2X3": + sheet = reference.ReferenceToDocument[0] + drawing = [r for r in tool.Ifc.by_type("IfcRelAssociatesDocument") if r.RelatingDocument == reference][ + 0 + ].RelatedObjects[0] + else: + sheet = reference.ReferencedDocument + drawing = reference.DocumentRefForObjects[0].RelatedObjects[0] + + tool.Ifc.run("document.unassign_document", product=drawing, document=reference) + tool.Ifc.run("document.remove_reference", reference=reference) + + sheet_builder = sheeter.SheetBuilder() + sheet_builder.data_dir = context.scene.BIMProperties.data_dir + sheet_builder.remove_drawing(drawing, sheet) + tool.Drawing.import_sheets() return {"FINISHED"} @@ -598,7 +666,8 @@ class CreateSheets(bpy.types.Operator): def execute(self, context): scene = context.scene props = scene.DocProperties - name = props.active_sheet.name + active_sheet = props.sheets[props.active_sheet_index] + name = active_sheet.name sheet_builder = sheeter.SheetBuilder() sheet_builder.data_dir = scene.BIMProperties.data_dir sheet_builder.build(name) @@ -943,12 +1012,10 @@ class RemoveSheet(bpy.types.Operator, Operator): bl_idname = "bim.remove_sheet" bl_label = "Remove Sheet" bl_options = {"REGISTER", "UNDO"} - index: bpy.props.IntProperty() + sheet: bpy.props.IntProperty() def _execute(self, context): - self.props = context.scene.DocProperties - sheet = tool.Ifc.get().by_id(self.props.sheets[self.props.active_sheet_index].ifc_definition_id) - core.remove_sheet(tool.Ifc, tool.Drawing, sheet=sheet) + core.remove_sheet(tool.Ifc, tool.Drawing, sheet=tool.Ifc.get().by_id(self.sheet)) class AddSchedule(bpy.types.Operator): @@ -1323,3 +1390,31 @@ class DisableEditingDrawings(bpy.types.Operator, Operator): def _execute(self, context): core.disable_editing_drawings(tool.Drawing) + + +class ExpandSheet(bpy.types.Operator): + bl_idname = "bim.expand_sheet" + bl_label = "Expand Sheet" + bl_options = {"REGISTER", "UNDO"} + sheet: bpy.props.IntProperty() + + def execute(self, context): + props = context.scene.DocProperties + for sheet in [s for s in props.sheets if s.ifc_definition_id == self.sheet]: + sheet.is_expanded = True + core.load_sheets(tool.Drawing) + return {"FINISHED"} + + +class ContractSheet(bpy.types.Operator): + bl_idname = "bim.contract_sheet" + bl_label = "Contract Sheet" + bl_options = {"REGISTER", "UNDO"} + sheet: bpy.props.IntProperty() + + def execute(self, context): + props = context.scene.DocProperties + for sheet in [s for s in props.sheets if s.ifc_definition_id == self.sheet]: + sheet.is_expanded = False + core.load_sheets(tool.Drawing) + return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/drawing/prop.py b/src/blenderbim/blenderbim/bim/module/drawing/prop.py index 95e801b8b7..07e5b9b626 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/prop.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/prop.py @@ -221,8 +221,9 @@ class Sheet(PropertyGroup): ifc_definition_id: IntProperty(name="IFC Definition ID") identification: StringProperty(name="Identification") name: StringProperty(name="Name", get=get_name, set=set_name) - drawings: CollectionProperty(name="Drawings", type=Drawing) - active_drawing_index: IntProperty(name="Active Drawing Index") + is_sheet: BoolProperty(name="Is Sheet", default=False) + reference_type: StringProperty(name="Reference Type") + is_expanded: BoolProperty(name="Is Expanded", default=False) class DrawingStyle(PropertyGroup): @@ -313,10 +314,6 @@ class DocProperties(PropertyGroup): def active_schedule(self): return self.schedules[self.active_schedule_index] - @property - def active_sheet(self): - return self.sheets[self.active_sheet_index] - class BIMCameraProperties(PropertyGroup): representation: StringProperty(name="Representation") diff --git a/src/blenderbim/blenderbim/bim/module/drawing/sheeter.py b/src/blenderbim/blenderbim/bim/module/drawing/sheeter.py index db77c70e78..317a76e83a 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/sheeter.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/sheeter.py @@ -16,11 +16,12 @@ # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . -import xml.etree.ElementTree as ET -import urllib.parse -import pystache -import ntpath import os +import ntpath +import pystache +import urllib.parse +import xml.etree.ElementTree as ET +import blenderbim.tool as tool from shutil import copy from xml.dom import minidom @@ -59,11 +60,16 @@ class SheetBuilder: 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") + def add_drawing(self, drawing, sheet): + filename = drawing.Name + sheet_name = tool.Drawing.get_sheet_filename(sheet) + sheet_dir = os.path.join(self.data_dir, "sheets") + drawing_dir = os.path.join(self.data_dir, "diagrams") + sheet_path = os.path.join(sheet_dir, sheet_name + ".svg") + drawing_path = os.path.join(drawing_dir, filename + ".svg") + underlay_path = os.path.join(drawing_dir, filename + ".png") - if not os.path.isfile(view_path): + if not os.path.isfile(sheet_path): raise FileNotFoundError ET.register_namespace("", "http://www.w3.org/2000/svg") @@ -72,7 +78,7 @@ class SheetBuilder: sheet_tree = ET.parse(sheet_path) sheet_root = sheet_tree.getroot() - view_tree = ET.parse(view_path) + view_tree = ET.parse(drawing_path) view_root = view_tree.getroot() # The view is placed into a group with a background image element. @@ -80,26 +86,48 @@ class SheetBuilder: # here to accommodate browsers which do not nest images. view = ET.SubElement(sheet_root, "g") view.attrib["data-type"] = "drawing" + view.attrib["data-guid"] = drawing.GlobalId 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) + if os.path.isfile(underlay_path): + background = ET.SubElement(view, "image") + background.attrib["xlink:href"] = os.path.relpath(underlay_path, sheet_dir) + 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) + if os.path.isfile(drawing_path): + foreground = ET.SubElement(view, "image") + foreground.attrib["xlink:href"] = os.path.relpath(drawing_path, sheet_dir) + 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) sheet_tree.write(sheet_path) + def remove_drawing(self, drawing, sheet): + sheet_name = tool.Drawing.get_sheet_filename(sheet) + sheet_dir = os.path.join(self.data_dir, "sheets") + sheet_path = os.path.join(sheet_dir, sheet_name + ".svg") + + ET.register_namespace("", "http://www.w3.org/2000/svg") + + sheet_tree = ET.parse(sheet_path) + sheet_root = sheet_tree.getroot() + + print('removing drawing', sheet_root.findall("{http://www.w3.org/2000/svg}g")) + for g in sheet_root.findall("{http://www.w3.org/2000/svg}g"): + print('checking g', g) + if g.attrib["data-type"] == "drawing" and g.attrib["data-guid"] == drawing.GlobalId: + sheet_root.remove(g) + break + + 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") diff --git a/src/blenderbim/blenderbim/bim/module/drawing/ui.py b/src/blenderbim/blenderbim/bim/module/drawing/ui.py index 2df0fb6c62..55cc14b092 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/ui.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/ui.py @@ -246,14 +246,19 @@ class BIM_PT_sheets(Panel): row.operator("bim.add_sheet", text="", icon="ADD") row.operator("bim.disable_editing_sheets", text="", icon="CANCEL") - if self.props.sheets: + if self.props.sheets and self.props.active_sheet_index < len(self.props.sheets): + active_sheet = self.props.sheets[self.props.active_sheet_index] row = self.layout.row(align=True) row.alignment = "RIGHT" row.operator("bim.open_sheet", icon="URL", text="") row.operator("bim.add_drawing_to_sheet", icon="IMAGE_PLANE", text="") row.operator("bim.add_schedule_to_sheet", icon="PRESET_NEW", text="") row.operator("bim.create_sheets", icon="FILE_REFRESH", text="") - row.operator("bim.remove_sheet", icon="X", text="").index = self.props.active_sheet_index + if active_sheet.is_sheet: + row.operator("bim.remove_sheet", icon="X", text="").sheet = active_sheet.ifc_definition_id + else: + op = row.operator("bim.remove_drawing_from_sheet", icon="X", text="") + op.reference = active_sheet.ifc_definition_id self.layout.template_list("BIM_UL_sheets", "", self.props, "sheets", self.props, "active_sheet_index") @@ -388,9 +393,22 @@ class BIM_UL_sheets(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): if item: row = layout.row(align=True) - split1 = row.split(factor=0.2) - split1.label(text=item.identification or "X") - split2 = split1.split(factor=0.8) - split2.label(text=item.name or "Unnamed") + + if item.is_sheet: + if item.is_expanded: + row.operator( + "bim.contract_sheet", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN" + ).sheet = item.ifc_definition_id + else: + row.operator( + "bim.expand_sheet", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT" + ).sheet = item.ifc_definition_id + else: + row.label(text="", icon="BLANK1") + if item.reference_type == "DRAWING": + row.label(text="", icon="IMAGE_DATA") + + name = "{} - {}".format(item.identification or "X", item.name or "Unnamed") + row.label(text=name) else: layout.label(text="", translate=False) diff --git a/src/blenderbim/blenderbim/core/drawing.py b/src/blenderbim/blenderbim/core/drawing.py index 30e25c5da1..ac43fd3323 100644 --- a/src/blenderbim/blenderbim/core/drawing.py +++ b/src/blenderbim/blenderbim/core/drawing.py @@ -85,7 +85,7 @@ def open_sheet(drawing, sheet=None): def remove_sheet(ifc, drawing, sheet=None): - ifc.run("document.remove_document", document=sheet) + ifc.run("document.remove_information", information=sheet) drawing.import_sheets() diff --git a/src/blenderbim/blenderbim/tool/drawing.py b/src/blenderbim/blenderbim/tool/drawing.py index adf153c05f..5dd7d8022a 100644 --- a/src/blenderbim/blenderbim/tool/drawing.py +++ b/src/blenderbim/blenderbim/tool/drawing.py @@ -273,16 +273,45 @@ class Drawing(blenderbim.core.tool.Drawing): @classmethod def import_sheets(cls): - bpy.context.scene.DocProperties.sheets.clear() + props = bpy.context.scene.DocProperties + expanded_sheets = {s.ifc_definition_id for s in props.sheets if s.is_expanded} + props.sheets.clear() sheets = [d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "DOCUMENTATION"] for sheet in sheets: - new = bpy.context.scene.DocProperties.sheets.add() + new = props.sheets.add() new.ifc_definition_id = sheet.id() if tool.Ifc.get_schema() == "IFC2X3": new.identification = sheet.DocumentId else: new.identification = sheet.Identification new.name = sheet.Name + new.is_sheet = True + new.is_expanded = sheet.id() in expanded_sheets + + if not new.is_expanded: + continue + + if tool.Ifc.get_schema() == "IFC2X3": + references = sheet.DocumentReferences or [] + else: + references = sheet.HasDocumentReferences or [] + + for reference in references: + new = props.sheets.add() + new.ifc_definition_id = reference.id() + new.is_sheet = False + + if tool.Ifc.get_schema() == "IFC2X3": + new.identification = reference.ItemReference or "X" + element = [ + r for r in tool.Ifc.by_type("IfcRelAssociatesDocument") if r.RelatingDocument == reference + ][0].RelatedObjects[0] + else: + new.identification = reference.Identification or "X" + element = reference.DocumentRefForObjects[0].RelatedObjects[0] + + new.name = element.Name + new.reference_type = "DRAWING" @classmethod def import_text_attributes(cls, obj): @@ -456,9 +485,6 @@ class Drawing(blenderbim.core.tool.Drawing): # 1. Select the 4 +Z vertices local to the reference element. This is the cutting plane. verts_local_to_reference = [reference_obj.matrix_world.inverted() @ v for v in reference_mesh["verts"]] cutting_plane_verts = sorted(verts_local_to_reference, key=lambda x: x.z)[-4:] - # Filter verts with the same XY coords - #set([v.xy for v in cutting_plane_verts]) - global_cutting_plane_verts = [reference_obj.matrix_world @ v for v in cutting_plane_verts] # 2. Project the cutting plane onto our viewing camera. verts_local_to_camera = [camera.matrix_world.inverted() @ v for v in global_cutting_plane_verts] @@ -640,14 +666,14 @@ class Drawing(blenderbim.core.tool.Drawing): depth = obj.data.clip_end verts = ( - obj.matrix_world @ mathutils.Vector((-width / 2, -height /2, -depth)), - obj.matrix_world @ mathutils.Vector((-width / 2, -height /2, 0)), - obj.matrix_world @ mathutils.Vector((-width / 2, height /2, -depth)), - obj.matrix_world @ mathutils.Vector((-width / 2, height /2, 0)), - obj.matrix_world @ mathutils.Vector((width / 2, -height /2, -depth)), - obj.matrix_world @ mathutils.Vector((width / 2, -height /2, 0)), - obj.matrix_world @ mathutils.Vector((width / 2, height /2, -depth)), - obj.matrix_world @ mathutils.Vector((width / 2, height /2, 0)) + obj.matrix_world @ mathutils.Vector((-width / 2, -height / 2, -depth)), + obj.matrix_world @ mathutils.Vector((-width / 2, -height / 2, 0)), + obj.matrix_world @ mathutils.Vector((-width / 2, height / 2, -depth)), + obj.matrix_world @ mathutils.Vector((-width / 2, height / 2, 0)), + obj.matrix_world @ mathutils.Vector((width / 2, -height / 2, -depth)), + obj.matrix_world @ mathutils.Vector((width / 2, -height / 2, 0)), + obj.matrix_world @ mathutils.Vector((width / 2, height / 2, -depth)), + obj.matrix_world @ mathutils.Vector((width / 2, height / 2, 0)), ) faces = [ [0, 1, 3, 2],