References for adding external svgs to the sheets

It works exactly the same as schedules - you add some .svg file, path to it stored in ifc and you can attach it to the sheet.

UI - https://i.imgur.com/tGbCGBb.png

Still need to add some tests for references
This commit is contained in:
Andrej730
2023-06-01 21:41:06 +05:00
parent 3c9f09f246
commit 0fd94d291b
11 changed files with 275 additions and 60 deletions
@@ -30,6 +30,7 @@ classes = (
operator.AddDrawingToSheet,
operator.AddSchedule,
operator.AddScheduleToSheet,
operator.AddReferenceToSheet,
operator.AddSheet,
operator.AddTextLiteral,
operator.BuildSchedule,
@@ -70,9 +71,14 @@ classes = (
operator.SaveDrawingStylesData,
operator.SelectAssignedProduct,
operator.SelectDocIfcFile,
operator.LoadReferences,
operator.DisableEditingReferences,
operator.AddReference,
operator.RemoveReference,
operator.OpenReference,
prop.Variable,
prop.Drawing,
prop.Schedule,
prop.Document,
prop.DrawingStyle,
prop.Sheet,
prop.DocProperties,
@@ -87,6 +93,7 @@ classes = (
ui.BIM_PT_sheets,
ui.BIM_PT_drawings,
ui.BIM_PT_schedules,
ui.BIM_PT_references,
ui.BIM_PT_product_assignments,
ui.BIM_PT_text,
ui.BIM_UL_drawinglist,
@@ -27,7 +27,7 @@ from pathlib import Path
def refresh():
ProductAssignmentsData.is_loaded = False
SheetsData.is_loaded = False
SchedulesData.is_loaded = False
DocumentsData.is_loaded = False
DrawingsData.is_loaded = False
AnnotationData.is_loaded = False
DecoratorData.data = {}
@@ -135,13 +135,18 @@ class DrawingsData:
return ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing")
class SchedulesData:
class DocumentsData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {"has_saved_ifc": cls.has_saved_ifc(), "total_schedules": cls.total_schedules()}
documents = cls.count_documents()
cls.data = {
"has_saved_ifc": cls.has_saved_ifc(),
"total_schedules": documents["SCHEDULE"],
"total_references": documents["REFERENCE"],
}
cls.is_loaded = True
@classmethod
@@ -149,8 +154,16 @@ class SchedulesData:
return os.path.isfile(tool.Ifc.get_path())
@classmethod
def total_schedules(cls):
return len([d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "SCHEDULE"])
def count_documents(cls):
documents = {
"SCHEDULE": 0,
"REFERENCE": 0,
}
for d in tool.Ifc.get().by_type("IfcDocumentInformation"):
scope = d.Scope
documents[scope] = documents.get(scope, 0) + 1
return documents
FONT_SIZES = {
@@ -1708,9 +1708,10 @@ class AddSchedule(bpy.types.Operator, Operator):
# taking into account different drives on windows
if Path(filepath).drive == Path(ifc_path).drive:
filepath = os.path.relpath(filepath, ifc_path)
core.add_schedule(
core.add_document(
tool.Ifc,
tool.Drawing,
"SCHEDULE",
uri=filepath,
)
@@ -1726,7 +1727,7 @@ class RemoveSchedule(bpy.types.Operator, Operator):
schedule: bpy.props.IntProperty()
def _execute(self, context):
core.remove_schedule(tool.Ifc, tool.Drawing, schedule=tool.Ifc.get().by_id(self.schedule))
core.remove_document(tool.Ifc, tool.Drawing, "SCHEDULE", schedule=tool.Ifc.get().by_id(self.schedule))
class OpenSchedule(bpy.types.Operator, Operator):
@@ -1797,11 +1798,115 @@ class AddScheduleToSheet(bpy.types.Operator, Operator):
sheet_builder = sheeter.SheetBuilder()
sheet_builder.data_dir = context.scene.BIMProperties.data_dir
sheet_builder.add_schedule(reference, schedule, sheet)
sheet_builder.add_document(reference, schedule, sheet)
tool.Drawing.import_sheets()
class AddReferenceToSheet(bpy.types.Operator, Operator):
bl_idname = "bim.add_reference_to_sheet"
bl_label = "Add Reference To Sheet"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
props = context.scene.DocProperties
return props.references and props.sheets and context.scene.BIMProperties.data_dir
def _execute(self, context):
props = context.scene.DocProperties
active_reference = props.references[props.active_reference_index]
active_sheet = tool.Drawing.get_active_sheet(context)
reference = tool.Ifc.get().by_id(active_reference.ifc_definition_id)
if tool.Ifc.get_schema() == "IFC2X3":
reference_location = tool.Drawing.get_path_with_ext(reference.DocumentReferences[0].Location, "svg")
else:
reference_location = tool.Drawing.get_path_with_ext(reference.HasDocumentReferences[0].Location, "svg")
sheet = tool.Ifc.get().by_id(active_sheet.ifc_definition_id)
if not sheet.is_a("IfcDocumentInformation"):
return
references = tool.Drawing.get_document_references(sheet)
has_reference = False
for reference in references:
if reference.Location == reference_location:
has_reference = True
break
if has_reference:
return
if not tool.Drawing.does_file_exist(tool.Ifc.resolve_uri(reference_location)):
self.report({"ERROR"}, f"Cannot find reference svg by path {reference_location}.")
return
reference = tool.Ifc.run("document.add_reference", information=sheet)
id_attr = "ItemReference" if tool.Ifc.get_schema() == "IFC2X3" else "Identification"
attributes = {
id_attr: str(len([r for r in references if r.Description in ("DRAWING", "REFERENCE")]) + 1),
"Location": reference_location,
"Description": "REFERENCE",
}
tool.Ifc.run("document.edit_reference", reference=reference, attributes=attributes)
sheet_builder = sheeter.SheetBuilder()
sheet_builder.data_dir = context.scene.BIMProperties.data_dir
sheet_builder.add_document(reference, reference, sheet)
tool.Drawing.import_sheets()
class AddReference(bpy.types.Operator, Operator):
bl_idname = "bim.add_reference"
bl_label = "Add Reference"
bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
filter_glob: bpy.props.StringProperty(default="*.svg", options={"HIDDEN"})
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=True)
def _execute(self, context):
filepath = self.filepath
if self.use_relative_path:
ifc_path = tool.Ifc.get_path()
if os.path.isfile(ifc_path):
ifc_path = os.path.dirname(ifc_path)
# taking into account different drives on windows
if Path(filepath).drive == Path(ifc_path).drive:
filepath = os.path.relpath(filepath, ifc_path)
core.add_document(
tool.Ifc,
tool.Drawing,
"REFERENCE",
uri=filepath,
)
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class RemoveReference(bpy.types.Operator, Operator):
bl_idname = "bim.remove_reference"
bl_label = "Remove Reference"
bl_options = {"REGISTER", "UNDO"}
reference: bpy.props.IntProperty()
def _execute(self, context):
core.remove_document(tool.Ifc, tool.Drawing, "REFERENCE", document=tool.Ifc.get().by_id(self.reference))
class OpenReference(bpy.types.Operator, Operator):
bl_idname = "bim.open_reference"
bl_label = "Open Reference"
bl_options = {"REGISTER", "UNDO"}
reference: bpy.props.IntProperty()
def _execute(self, context):
core.open_reference(tool.Drawing, reference=tool.Ifc.get().by_id(self.reference))
# TODO: dead code - drawing style attributes are never used?
class AddDrawingStyleAttribute(bpy.types.Operator):
bl_idname = "bim.add_drawing_style_attribute"
@@ -2127,6 +2232,24 @@ class DisableEditingSchedules(bpy.types.Operator, Operator):
core.disable_editing_schedules(tool.Drawing)
class LoadReferences(bpy.types.Operator, Operator):
bl_idname = "bim.load_references"
bl_label = "Load References"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.load_references(tool.Drawing)
class DisableEditingReferences(bpy.types.Operator, Operator):
bl_idname = "bim.disable_editing_references"
bl_label = "Disable Editing References"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.disable_editing_references(tool.Drawing)
class LoadDrawings(bpy.types.Operator, Operator):
bl_idname = "bim.load_drawings"
bl_label = "Load Drawings"
@@ -255,7 +255,7 @@ class Drawing(PropertyGroup):
is_selected: BoolProperty(name="Is Selected", default=True)
class Schedule(PropertyGroup):
class Document(PropertyGroup):
ifc_definition_id: IntProperty(name="IFC Definition ID")
name: StringProperty(name="Name", update=update_schedule_name)
identification: StringProperty(name="Identification")
@@ -311,6 +311,7 @@ class DocProperties(PropertyGroup):
should_extract: BoolProperty(name="Should Extract", default=True)
is_editing_drawings: BoolProperty(name="Is Editing Drawings", default=False)
is_editing_schedules: BoolProperty(name="Is Editing Schedules", default=False)
is_editing_references: BoolProperty(name="Is Editing References", default=False)
target_view: EnumProperty(
items=[
("PLAN_VIEW", "Plan", ""),
@@ -328,8 +329,10 @@ class DocProperties(PropertyGroup):
active_drawing_id: IntProperty(name="Active Drawing Id")
active_drawing_index: IntProperty(name="Active Drawing Index")
current_drawing_index: IntProperty(name="Current Drawing Index")
schedules: CollectionProperty(name="Schedules", type=Schedule)
schedules: CollectionProperty(name="Schedules", type=Document)
active_schedule_index: IntProperty(name="Active Schedule Index")
references: CollectionProperty(name="References", type=Document)
active_reference_index: IntProperty(name="Active Reference Index")
titleblock: EnumProperty(items=get_titleblocks, name="Titleblock", update=update_titleblocks)
is_editing_sheets: BoolProperty(name="Is Editing Sheets", default=False)
sheets: CollectionProperty(name="Sheets", type=Sheet)
@@ -521,9 +524,11 @@ def update_annotation_object_type(self, context):
# changing enum doesn't trigger refresh by itself
AnnotationData.is_loaded = False
def update_sheet_data(self, context):
SheetsData.is_loaded = False
class BIMAnnotationProperties(PropertyGroup):
object_type: bpy.props.EnumProperty(
name="Annotation Object Type", items=annotation_classes, default="TEXT", update=update_annotation_object_type
@@ -182,7 +182,7 @@ class SheetBuilder:
layout_tree.write(layout_path)
def add_schedule(self, reference, schedule, sheet):
def add_document(self, reference, schedule, sheet):
view_path = tool.Drawing.get_path_with_ext(tool.Drawing.get_document_uri(schedule), "svg")
if not os.path.exists(view_path):
tool.Drawing.create_svg_schedule(schedule)
@@ -23,7 +23,7 @@ from bpy.types import Panel
from blenderbim.bim.module.drawing.data import (
ProductAssignmentsData,
SheetsData,
SchedulesData,
DocumentsData,
DrawingsData,
DecoratorData,
)
@@ -174,11 +174,7 @@ class BIM_PT_drawings(Panel):
DrawingsData.load()
if not DrawingsData.data["has_saved_ifc"]:
row = self.layout.row()
row.label(text="Project Not Yet Saved", icon="ERROR")
row = self.layout.row()
op = row.operator("export_ifc.bim", icon="EXPORT", text="Save Project")
op.should_save_as = False
draw_project_not_saved_ui()
return
self.props = context.scene.DocProperties
@@ -242,22 +238,18 @@ class BIM_PT_schedules(Panel):
return tool.Ifc.get()
def draw(self, context):
if not SchedulesData.is_loaded:
SchedulesData.load()
if not DocumentsData.is_loaded:
DocumentsData.load()
if not SchedulesData.data["has_saved_ifc"]:
row = self.layout.row()
row.label(text="Project Not Yet Saved", icon="ERROR")
row = self.layout.row()
op = row.operator("export_ifc.bim", icon="EXPORT", text="Save Project")
op.should_save_as = False
if not DocumentsData.data["has_saved_ifc"]:
draw_project_not_saved_ui()
return
self.props = context.scene.DocProperties
if not self.props.is_editing_schedules:
row = self.layout.row(align=True)
row.label(text=f"{SchedulesData.data['total_schedules']} Schedules Found", icon="LONGDISPLAY")
row.label(text=f"{DocumentsData.data['total_schedules']} Schedules Found", icon="LONGDISPLAY")
row.operator("bim.load_schedules", text="", icon="IMPORT")
return
@@ -281,6 +273,58 @@ class BIM_PT_schedules(Panel):
)
def draw_project_not_saved_ui(self):
row = self.layout.row()
row.label(text="Project Not Yet Saved", icon="ERROR")
row = self.layout.row()
op = row.operator("export_ifc.bim", icon="EXPORT", text="Save Project")
op.should_save_as = False
class BIM_PT_references(Panel):
bl_label = "References"
bl_idname = "BIM_PT_references"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "BIM Documentation"
@classmethod
def poll(cls, context):
return tool.Ifc.get()
def draw(self, context):
if not DocumentsData.is_loaded:
DocumentsData.load()
if not DocumentsData.data["has_saved_ifc"]:
draw_project_not_saved_ui(self)
return
self.props = context.scene.DocProperties
if not self.props.is_editing_references:
row = self.layout.row(align=True)
row.label(text=f"{DocumentsData.data['total_references']} References Found", icon="LONGDISPLAY")
row.operator("bim.load_references", text="", icon="IMPORT")
return
row = self.layout.row(align=True)
row.operator("bim.add_reference", icon="ADD")
row.operator("bim.disable_editing_references", text="", icon="CANCEL")
if self.props.references:
if self.props.active_reference_index < len(self.props.references):
active_reference = self.props.references[self.props.active_reference_index]
row = self.layout.row(align=True)
row.alignment = "RIGHT"
row.operator("bim.open_reference", icon="URL", text="").reference = active_reference.ifc_definition_id
row.operator("bim.remove_reference", icon="X", text="").reference = active_reference.ifc_definition_id
self.layout.template_list(
"BIM_UL_generic", "", self.props, "references", self.props, "active_reference_index"
)
class BIM_PT_sheets(Panel):
bl_label = "Sheets"
bl_idname = "BIM_PT_sheets"
@@ -297,11 +341,7 @@ class BIM_PT_sheets(Panel):
SheetsData.load()
if not SheetsData.data["has_saved_ifc"]:
row = self.layout.row()
row.label(text="Project Not Yet Saved", icon="ERROR")
row = self.layout.row()
op = row.operator("export_ifc.bim", icon="EXPORT", text="Save Project")
op.should_save_as = False
draw_project_not_saved_ui()
return
self.props = context.scene.DocProperties
@@ -325,6 +365,7 @@ class BIM_PT_sheets(Panel):
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.add_reference_to_sheet", icon="IMAGE_REFERENCE", text="")
row.operator("bim.create_sheets", icon="FILE_REFRESH", text="")
if active_sheet.is_sheet:
row.operator("bim.remove_sheet", icon="X", text="").sheet = active_sheet.ifc_definition_id
@@ -577,6 +618,8 @@ class BIM_UL_sheets(bpy.types.UIList):
row.label(text="", icon="MENU_PANEL")
elif item.reference_type == "REVISION":
row.label(text="", icon="RECOVER_LAST")
elif item.reference_type == "REFERENCE":
row.label(text="", icon="IMAGE_REFERENCE")
if item.identification:
name = f"{item.identification} - {item.name or 'Unnamed'}"
+24 -11
View File
@@ -130,36 +130,49 @@ def rename_reference(ifc, reference=None, identification=None):
def load_schedules(drawing):
drawing.import_schedules()
drawing.import_documents("SCHEDULE")
drawing.enable_editing_schedules()
def load_references(drawing):
drawing.import_documents("REFERENCE")
drawing.enable_editing_references()
def disable_editing_schedules(drawing):
drawing.disable_editing_schedules()
def add_schedule(ifc, drawing, uri=None):
schedule = ifc.run("document.add_information")
reference = ifc.run("document.add_reference", information=schedule)
def disable_editing_references(drawing):
drawing.disable_editing_references()
def add_document(ifc, drawing, document_type, uri=None):
document = ifc.run("document.add_information")
reference = ifc.run("document.add_reference", information=document)
name = drawing.get_path_filename(uri)
if ifc.get_schema() == "IFC2X3":
attributes = {"DocumentId": "X", "Name": name, "Scope": "SCHEDULE"}
attributes = {"DocumentId": "X", "Name": name, "Scope": document_type}
else:
attributes = {"Identification": "X", "Name": name, "Scope": "SCHEDULE"}
ifc.run("document.edit_information", information=schedule, attributes=attributes)
attributes = {"Identification": "X", "Name": name, "Scope": document_type}
ifc.run("document.edit_information", information=document, attributes=attributes)
ifc.run("document.edit_reference", reference=reference, attributes={"Location": uri})
drawing.import_schedules()
drawing.import_documents(document_type)
def remove_schedule(ifc, drawing, schedule=None):
ifc.run("document.remove_information", information=schedule)
drawing.import_schedules()
def remove_document(ifc, drawing, document_type, document=None):
ifc.run("document.remove_information", information=document)
drawing.import_documents(document_type)
def open_schedule(drawing, schedule=None):
drawing.open_spreadsheet(drawing.get_document_uri(schedule))
def open_reference(drawing, reference=None):
drawing.open_svg(drawing.get_document_uri(reference))
def update_schedule_name(ifc, drawing, schedule=None, name=None):
if drawing.get_name(schedule) != name:
ifc.run("document.edit_information", information=schedule, attributes={"Name": name})
+1 -2
View File
@@ -276,7 +276,6 @@ class Drawing:
def get_default_layout_path(cls, identification, name): pass
def get_default_shading_style(cls): pass
def get_default_sheet_path(cls, identification, name): pass
def get_default_sheet_path(cls, identification, name): pass
def get_default_titleblock_path(cls, name): pass
def get_document_references(cls, document): pass
def get_document_uri(cls, document, description=None): pass
@@ -297,7 +296,7 @@ class Drawing:
def get_unit_system(cls): pass
def import_assigned_product(cls, obj): pass
def import_drawings(cls): pass
def import_schedules(cls): pass
def import_documents(cls, document_type): pass
def import_sheets(cls): pass
def import_text_attributes(cls, obj): pass
def is_active_drawing(cls, drawing): pass
+18 -4
View File
@@ -237,6 +237,10 @@ class Drawing(blenderbim.core.tool.Drawing):
def disable_editing_schedules(cls):
bpy.context.scene.DocProperties.is_editing_schedules = False
@classmethod
def disable_editing_references(cls):
bpy.context.scene.DocProperties.is_editing_references = False
@classmethod
def disable_editing_sheets(cls):
bpy.context.scene.DocProperties.is_editing_sheets = False
@@ -265,6 +269,10 @@ class Drawing(blenderbim.core.tool.Drawing):
def enable_editing_schedules(cls):
bpy.context.scene.DocProperties.is_editing_schedules = True
@classmethod
def enable_editing_references(cls):
bpy.context.scene.DocProperties.is_editing_references = True
@classmethod
def enable_editing_sheets(cls):
bpy.context.scene.DocProperties.is_editing_sheets = True
@@ -625,11 +633,17 @@ class Drawing(blenderbim.core.tool.Drawing):
new.is_selected = current_drawings_selection.get(drawing.id(), True)
@classmethod
def import_schedules(cls):
bpy.context.scene.DocProperties.schedules.clear()
schedules = [d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "SCHEDULE"]
def import_documents(cls, document_type):
dprops = bpy.context.scene.DocProperties
if document_type == "SCHEDULE":
documents_collection = dprops.schedules
elif document_type == "REFERENCE":
documents_collection = dprops.references
documents_collection.clear()
schedules = [d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == document_type]
for schedule in schedules:
new = bpy.context.scene.DocProperties.schedules.add()
new = documents_collection.add()
new.ifc_definition_id = schedule.id()
new.name = schedule.Name or "Unnamed"
if tool.Ifc.get_schema() == "IFC2X3":
+7 -7
View File
@@ -159,7 +159,7 @@ class TestRemoveSheet:
class TestLoadSchedules:
def test_run(self, drawing):
drawing.import_schedules().should_be_called()
drawing.import_documents("SCHEDULE").should_be_called()
drawing.enable_editing_schedules().should_be_called()
subject.load_schedules(drawing)
@@ -182,8 +182,8 @@ class TestAddSchedule:
attributes={"Identification": "X", "Name": "UNTITLED", "Scope": "SCHEDULE"},
).should_be_called()
ifc.run("document.edit_reference", reference="reference", attributes={"Location": "uri"}).should_be_called()
drawing.import_schedules().should_be_called()
subject.add_schedule(ifc, drawing, uri="uri")
drawing.import_documents("SCHEDULE").should_be_called()
subject.add_document(ifc, drawing, "SCHEDULE", uri="uri")
def test_using_a_document_id_in_ifc2x3(self, ifc, drawing):
ifc.run("document.add_information").should_be_called().will_return("schedule")
@@ -196,15 +196,15 @@ class TestAddSchedule:
attributes={"DocumentId": "X", "Name": "UNTITLED", "Scope": "SCHEDULE"},
).should_be_called()
ifc.run("document.edit_reference", reference="reference", attributes={"Location": "uri"}).should_be_called()
drawing.import_schedules().should_be_called()
subject.add_schedule(ifc, drawing, uri="uri")
drawing.import_documents("SCHEDULE").should_be_called()
subject.add_document(ifc, drawing, "SCHEDULE", uri="uri")
class TestRemoveSchedule:
def test_run(self, ifc, drawing):
ifc.run("document.remove_information", information="schedule").should_be_called()
drawing.import_schedules().should_be_called()
subject.remove_schedule(ifc, drawing, schedule="schedule")
drawing.import_documents("SCHEDULE").should_be_called()
subject.remove_document(ifc, drawing, "SCHEDULE", document="schedule")
class TestOpenSchedule:
+2 -4
View File
@@ -479,7 +479,7 @@ class TestImportSchedules(NewFile):
tool.Ifc.set(ifc)
ifc.createIfcDocumentInformation(Identification="Y", Name="FOOBAZ")
document = ifc.createIfcDocumentInformation(Identification="X", Name="FOOBAR", Scope="SCHEDULE")
subject.import_schedules()
subject.import_documents("SCHEDULE")
props = bpy.context.scene.DocProperties
assert props.schedules[0].ifc_definition_id == document.id()
assert props.schedules[0].identification == "X"
@@ -490,7 +490,7 @@ class TestImportSchedules(NewFile):
tool.Ifc.set(ifc)
ifc.createIfcDocumentInformation(DocumentId="Y", Name="FOOBAZ")
document = ifc.createIfcDocumentInformation(DocumentId="X", Name="FOOBAR", Scope="SCHEDULE")
subject.import_schedules()
subject.import_documents("SCHEDULE")
props = bpy.context.scene.DocProperties
assert props.schedules[0].ifc_definition_id == document.id()
assert props.schedules[0].identification == "X"
@@ -778,5 +778,3 @@ class TestDrawingStyles(NewFile):
self.setup_project_with_drawing()
bpy.ops.bim.reload_drawing_styles()
assert len(self.drawing_styles) == 3