Fix #2516. Add/build/remove/view/rename/copy sheet/drawing/schedule now all references and updates user configurable relative paths.

This commit is contained in:
Dion Moult
2023-04-10 22:30:21 +10:00
parent fd989733c7
commit 4e94d0dc10
13 changed files with 350 additions and 150 deletions
@@ -30,7 +30,9 @@ classes = (
operator.AddSchedule,
operator.AddScheduleToSheet,
operator.AddSheet,
operator.AddTextLiteral,
operator.BuildSchedule,
operator.ChangeSheetTitleBlock,
operator.CleanWireframes,
operator.ContractSheet,
operator.CreateDrawing,
@@ -40,8 +42,6 @@ classes = (
operator.DisableEditingSchedules,
operator.DisableEditingSheets,
operator.DisableEditingText,
operator.AddTextLiteral,
operator.RemoveTextLiteral,
operator.DuplicateDrawing,
operator.EditAssignedProduct,
operator.EditText,
@@ -53,20 +53,21 @@ classes = (
operator.LoadDrawings,
operator.LoadSchedules,
operator.LoadSheets,
operator.OpenDrawing,
operator.OpenSchedule,
operator.OpenSheet,
operator.ChangeSheetTitleBlock,
operator.OpenView,
operator.RemoveDrawing,
operator.RemoveDrawingFromSheet,
operator.RemoveDrawingStyle,
operator.RemoveDrawingStyleAttribute,
operator.RemoveSchedule,
operator.RemoveSheet,
operator.RemoveTextLiteral,
operator.RenameSheet,
operator.ResizeText,
operator.SaveDrawingStyle,
operator.SelectDocIfcFile,
operator.SelectAssignedProduct,
operator.SelectDocIfcFile,
prop.Variable,
prop.Drawing,
prop.Schedule,
@@ -61,7 +61,7 @@ class SheetsData:
@classmethod
def total_sheets(cls):
return len([d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "DOCUMENTATION"])
return len([d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "SHEET"])
class DrawingsData:
@@ -245,7 +245,7 @@ class CreateDrawing(bpy.types.Operator):
elif "</svg>" in line:
continue
outfile.write(line)
shutil.copyfile(underlay[0:-4] + ".png", svg_path[0:-4] + "-underlay.png")
shutil.copyfile(os.path.splitext(underlay)[0] + ".png", os.path.splitext(svg_path)[0] + "-underlay.png")
if linework:
with open(linework) as infile:
should_skip = False
@@ -739,29 +739,24 @@ class AddDrawingToSheet(bpy.types.Operator, Operator):
sheet = tool.Ifc.get().by_id(active_sheet.ifc_definition_id)
if not sheet.is_a("IfcDocumentInformation"):
return {"FINISHED"}
return
if tool.Ifc.get_schema() == "IFC2X3":
references = sheet.DocumentReferences or []
else:
references = sheet.HasDocumentReferences or []
references = tool.Drawing.get_document_references(sheet)
has_drawing = False
for reference in references:
if reference.Location == drawing_reference.Location:
has_drawing = True
break
if has_drawing:
return {"FINISHED"}
return
if not tool.Drawing.does_file_exist(tool.Drawing.get_document_uri(drawing_reference)):
self.report({"ERROR"}, "The drawing must be generated before adding to a sheet.")
return
reference = tool.Ifc.run("document.add_reference", information=sheet)
if tool.Ifc.get_schema() == "IFC2X3":
references = sheet.DocumentReferences
id_attr = "ItemReference"
else:
references = sheet.HasDocumentReferences
id_attr = "Identification"
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", "SCHEDULE")]) + 1),
"Location": drawing_reference.Location,
@@ -773,16 +768,15 @@ class AddDrawingToSheet(bpy.types.Operator, Operator):
sheet_builder.add_drawing(reference, drawing, sheet)
tool.Drawing.import_sheets()
return {"FINISHED"}
class RemoveDrawingFromSheet(bpy.types.Operator):
class RemoveDrawingFromSheet(bpy.types.Operator, 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):
def _execute(self, context):
reference = tool.Ifc.get().by_id(self.reference)
sheet = tool.Drawing.get_reference_document(reference)
@@ -790,48 +784,55 @@ class RemoveDrawingFromSheet(bpy.types.Operator):
sheet_builder.data_dir = context.scene.BIMProperties.data_dir
sheet_builder.remove_drawing(reference, sheet)
drawing = tool.Drawing.get_reference_element(reference)
if drawing:
tool.Ifc.run("document.unassign_document", product=drawing, document=reference)
tool.Ifc.run("document.remove_reference", reference=reference)
tool.Drawing.import_sheets()
return {"FINISHED"}
class CreateSheets(bpy.types.Operator):
class CreateSheets(bpy.types.Operator, Operator):
bl_idname = "bim.create_sheets"
bl_label = "Create Sheets"
# TODO: check undo redo
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.scene.DocProperties.sheets and context.scene.BIMProperties.data_dir
def execute(self, context):
def _execute(self, context):
scene = context.scene
props = scene.DocProperties
active_sheet = props.sheets[props.active_sheet_index]
sheet = tool.Ifc.get().by_id(active_sheet.ifc_definition_id)
if not sheet.is_a("IfcDocumentInformation"):
return {"FINISHED"}
return
name = os.path.splitext(os.path.basename(tool.Drawing.get_document_uri(sheet)))[0]
sheet_builder = sheeter.SheetBuilder()
sheet_builder.data_dir = scene.BIMProperties.data_dir
references = sheet_builder.build(sheet)
raster_references = [tool.Ifc.get_relative_uri(r) for r in references["RASTER"]]
# These variables will be made available to the evaluated commands
svg = sheet_builder.build(sheet)
svg = references["SHEET"]
basename = os.path.basename(svg)
path = os.path.dirname(svg)
pdf = os.path.splitext(svg)[0] + ".pdf"
eps = os.path.splitext(svg)[0] + ".eps"
dxf = os.path.splitext(svg)[0] + ".dxf"
references = getattr(sheet, "HasDocumentReferences", getattr(sheet, "DocumentReferences", []))
if not [r for r in references if r.Description == "SHEET"]:
has_sheet_reference = False
for reference in tool.Drawing.get_document_references(sheet):
if reference.Description == "SHEET":
has_sheet_reference = True
elif reference.Description == "RASTER":
if reference.Location in raster_references:
del raster_references[reference.Location]
else:
tool.Ifc.run("document.remove_reference", reference=reference)
if not has_sheet_reference:
reference = tool.Ifc.run("document.add_reference", information=sheet)
tool.Ifc.run(
"document.edit_reference",
@@ -839,6 +840,14 @@ class CreateSheets(bpy.types.Operator):
attributes={"Location": tool.Ifc.get_relative_uri(svg), "Description": "SHEET"},
)
for raster_reference in raster_references:
reference = tool.Ifc.run("document.add_reference", information=sheet)
tool.Ifc.run(
"document.edit_reference",
reference=reference,
attributes={"Location": tool.Ifc.get_relative_uri(raster_reference), "Description": "RASTER"},
)
svg2pdf_command = context.preferences.addons["blenderbim"].preferences.svg2pdf_command
svg2dxf_command = context.preferences.addons["blenderbim"].preferences.svg2dxf_command
@@ -860,37 +869,43 @@ class CreateSheets(bpy.types.Operator):
open_with_user_command(context.preferences.addons["blenderbim"].preferences.pdf_command, pdf)
else:
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg)
return {"FINISHED"}
class ChangeSheetTitleBlock(bpy.types.Operator):
class ChangeSheetTitleBlock(bpy.types.Operator, Operator):
bl_idname = "bim.change_sheet_title_block"
bl_label = "Change Sheet Title Block"
bl_description = "Change the title block of the active sheet"
bl_options = {"REGISTER"}
def execute(self, context):
def _execute(self, context):
scene = context.scene
props = scene.DocProperties
if not len(props.sheets):
return {"CANCELLED"}
titleblock = scene.DocProperties.titleblock
active_sheet = props.sheets[props.active_sheet_index]
sheet = tool.Ifc.get().by_id(active_sheet.ifc_definition_id)
for reference in tool.Drawing.get_document_references(sheet):
description = tool.Drawing.get_reference_description(reference)
if description == "TITLEBLOCK":
tool.Ifc.run(
"document.edit_reference",
reference=reference,
attributes={"Location": tool.Drawing.get_default_titleblock_path(titleblock)},
)
sheet_builder = sheeter.SheetBuilder()
sheet_builder.data_dir = scene.BIMProperties.data_dir
titleblock = scene.DocProperties.titleblock
sheet_builder.change_titleblock(sheet, titleblock)
self.report({"INFO"}, 'Title block changed for sheet "{}" to {}'.format(sheet.Name, titleblock))
return {"FINISHED"}
class OpenView(bpy.types.Operator):
bl_idname = "bim.open_view"
bl_label = "Open View"
class OpenDrawing(bpy.types.Operator):
bl_idname = "bim.open_drawing"
bl_label = "Open Drawing"
view: bpy.props.StringProperty()
bl_description = (
"Opens a .svg drawing based on currently active camera with default system viewer\n"
@@ -1229,27 +1244,22 @@ class AddScheduleToSheet(bpy.types.Operator, Operator):
if not sheet.is_a("IfcDocumentInformation"):
return
if tool.Ifc.get_schema() == "IFC2X3":
references = sheet.DocumentReferences or []
else:
references = sheet.HasDocumentReferences or []
references = tool.Drawing.get_document_references(sheet)
has_schedule = False
for reference in references:
if reference.Location == schedule_location:
has_schedule = True
break
if has_schedule:
return
if not tool.Drawing.does_file_exist(tool.Ifc.resolve_uri(schedule_location)):
self.report({"ERROR"}, "The schedule must be generated before adding to a sheet.")
return
reference = tool.Ifc.run("document.add_reference", information=sheet)
if tool.Ifc.get_schema() == "IFC2X3":
references = sheet.DocumentReferences
id_attr = "ItemReference"
else:
references = sheet.HasDocumentReferences
id_attr = "Identification"
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", "SCHEDULE")]) + 1),
"Location": schedule_location,
@@ -1501,6 +1511,33 @@ class LoadSheets(bpy.types.Operator, Operator):
self.report({"ERROR"}, "Some sheets svg files are missing:\n" + "\n".join(sheets_not_found))
class RenameSheet(bpy.types.Operator, Operator):
bl_idname = "bim.rename_sheet"
bl_label = "Rename Sheet"
bl_options = {"REGISTER", "UNDO"}
identification: bpy.props.StringProperty()
name: bpy.props.StringProperty()
def invoke(self, context, event):
self.props = context.scene.DocProperties
sheet = tool.Ifc.get().by_id(self.props.sheets[self.props.active_sheet_index].ifc_definition_id)
self.identification = sheet.Identification
self.name = sheet.Name
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
row = self.layout.row()
row.prop(self, "identification", text="Identification")
row = self.layout.row()
row.prop(self, "name", text="Name")
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.rename_sheet(tool.Ifc, tool.Drawing, sheet=sheet, identification=self.identification, name=self.name)
tool.Drawing.import_sheets()
class DisableEditingSheets(bpy.types.Operator, Operator):
bl_idname = "bim.disable_editing_sheets"
bl_label = "Disable Editing Sheets"
@@ -248,26 +248,9 @@ class Schedule(PropertyGroup):
class Sheet(PropertyGroup):
def set_name(self, new):
old = self.get("name")
if new == old:
return
sheet = tool.Ifc.get().by_id(self.ifc_definition_id)
old_path = Path(tool.Drawing.get_document_uri(sheet))
core.update_sheet_name(tool.Ifc, tool.Drawing, sheet=sheet, name=new)
self["name"] = new
new_path = Path(tool.Drawing.get_document_uri(sheet))
if old and old_path.is_file():
old_path.rename(new_path)
def get_name(self):
return self.get("name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
identification: StringProperty(name="Identification")
name: StringProperty(name="Name", get=get_name, set=set_name)
name: StringProperty(name="Name")
is_sheet: BoolProperty(name="Is Sheet", default=False)
reference_type: StringProperty(name="Reference Type")
is_expanded: BoolProperty(name="Is Expanded", default=False)
@@ -354,11 +337,15 @@ class DocProperties(PropertyGroup):
decorations_colour: FloatVectorProperty(
name="Decorations Colour", subtype="COLOR", default=(1, 1, 1, 1), min=0.0, max=1.0, size=4
)
docs_dir: StringProperty(default=os.path.join("docs") + os.path.sep, name="Default Docs Directory")
sheets_dir: StringProperty(default=os.path.join("sheets") + os.path.sep, name="Default Sheets Directory")
titleblocks_dir: StringProperty(default=os.path.join("sheets", "titleblocks") + os.path.sep, name="Default Titleblocks Directory")
layouts_dir: StringProperty(default=os.path.join("layouts") + os.path.sep, name="Default Layouts Directory")
titleblocks_dir: StringProperty(
default=os.path.join("layouts", "titleblocks") + os.path.sep, name="Default Titleblocks Directory"
)
drawings_dir: StringProperty(default=os.path.join("drawings") + os.path.sep, name="Default Drawings Directory")
stylesheet_path: StringProperty(default=os.path.join("drawings", "assets", "default.css"), name="Default Stylesheet")
stylesheet_path: StringProperty(
default=os.path.join("drawings", "assets", "default.css"), name="Default Stylesheet"
)
markers_path: StringProperty(default=os.path.join("drawings", "assets", "markers.svg"), name="Default Markers")
symbols_path: StringProperty(default=os.path.join("drawings", "assets", "symbols.svg"), name="Default Symbols")
patterns_path: StringProperty(default=os.path.join("drawings", "assets", "patterns.svg"), name="Default Patterns")
@@ -34,8 +34,8 @@ class SheetBuilder:
self.data_dir = None
self.scale = "NTS"
def create(self, sheet_path, titleblock_name):
sheet_dir = os.path.dirname(sheet_path)
def create(self, layout_path, titleblock_name):
sheet_dir = os.path.dirname(layout_path)
root = ET.Element("svg")
root.attrib["xmlns"] = "http://www.w3.org/2000/svg"
@@ -43,16 +43,16 @@ class SheetBuilder:
root.attrib["id"] = "root"
root.attrib["version"] = "1.1"
titleblock_path = os.path.join(self.data_dir, "templates", "titleblocks", titleblock_name + ".svg")
view_root = ET.parse(titleblock_path).getroot()
ootb_titleblock_path = os.path.join(self.data_dir, "templates", "titleblocks", titleblock_name + ".svg")
titleblock_path = tool.Ifc.resolve_uri(tool.Drawing.get_default_titleblock_path(titleblock_name))
view_root = ET.parse(ootb_titleblock_path).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"] = os.path.relpath(
tool.Drawing.get_default_titleblock_path(titleblock_name), sheet_dir
)
titleblock.attrib["xlink:href"] = os.path.relpath(titleblock_path, sheet_dir)
titleblock.attrib["x"] = "0"
titleblock.attrib["y"] = "0"
titleblock.attrib["width"] = str(view_width)
@@ -63,24 +63,22 @@ class SheetBuilder:
root.attrib["viewBox"] = "0 0 {} {}".format(view_width, view_height)
os.makedirs(sheet_dir, exist_ok=True)
os.makedirs(os.path.join(sheet_dir, "titleblocks"), exist_ok=True)
sheet_titleblock_path = os.path.join(sheet_dir, "titleblocks", titleblock_name + ".svg")
if not os.path.exists(sheet_titleblock_path):
shutil.copy(titleblock_path, sheet_titleblock_path)
with open(sheet_path, "w") as f:
os.makedirs(os.path.dirname(titleblock_path), exist_ok=True)
if not os.path.exists(titleblock_path):
shutil.copy(ootb_titleblock_path, titleblock_path)
with open(layout_path, "w") as f:
f.write(minidom.parseString(ET.tostring(root)).toprettyxml(indent=" "))
def add_drawing(self, reference, drawing, sheet):
filename = drawing.Name
sheet_path = tool.Drawing.get_document_uri(sheet)
sheet_name = os.path.splitext(os.path.basename(sheet_path))[0]
sheet_dir = os.path.dirname(sheet_path)
drawing_path = tool.Drawing.get_document_uri(tool.Drawing.get_drawing_reference(drawing))
drawing_path = tool.Ifc.resolve_uri(drawing_path)
underlay_path = os.path.splitext(drawing_path)[0] + "-underlay.png"
if not os.path.isfile(sheet_path):
if not os.path.exists(sheet_path) or not os.path.exists(drawing_path):
raise FileNotFoundError
ET.register_namespace("", "http://www.w3.org/2000/svg")
@@ -190,18 +188,20 @@ class SheetBuilder:
title.attrib["height"] = str(self.convert_to_mm(title_root.attrib.get("height")))
def build(self, sheet):
sheet_path = tool.Drawing.get_document_uri(sheet)
sheet_name = os.path.splitext(os.path.basename(sheet_path))[0]
self.sheet_dir = os.path.dirname(sheet_path)
self.references = {"SHEET": None, "RASTER": []}
docs_dir = tool.Ifc.resolve_uri(os.path.join(bpy.context.scene.DocProperties.docs_dir, sheet_name))
layout_path = tool.Drawing.get_document_uri(sheet, "LAYOUT")
self.layout_dir = os.path.dirname(layout_path)
os.makedirs(docs_dir, exist_ok=True)
output_filename = tool.Ifc.resolve_uri(tool.Drawing.get_default_sheet_path(sheet[0], sheet.Name))
self.sheets_dir = os.path.dirname(output_filename)
os.makedirs(self.sheets_dir, exist_ok=True)
ET.register_namespace("", "http://www.w3.org/2000/svg")
ET.register_namespace("xlink", "http://www.w3.org/1999/xlink")
tree = ET.parse(sheet_path)
tree = ET.parse(layout_path)
root = tree.getroot()
self.defs = ET.Element("defs")
@@ -211,12 +211,12 @@ class SheetBuilder:
self.build_drawings(root, sheet)
self.build_schedules(root)
output_filename = os.path.join(docs_dir, f"{sheet_name}.svg")
with open(output_filename, "wb") as output:
tree.write(output)
return output_filename
self.references["SHEET"] = output_filename
return self.references
def build_titleblock(self, root, sheet):
titleblock = root.findall('{http://www.w3.org/2000/svg}g[@data-type="titleblock"]')[0]
@@ -232,8 +232,6 @@ class SheetBuilder:
titleblock.remove(image)
def build_drawings(self, root, sheet):
sheet_name = os.path.splitext(os.path.basename(tool.Drawing.get_document_uri(sheet)))[0]
for view in root.findall('{http://www.w3.org/2000/svg}g[@data-type="drawing"]'):
reference = tool.Ifc.get().by_id(int(view.attrib["data-id"]))
drawing = tool.Ifc.get().by_id(view.attrib["data-drawing"])
@@ -256,8 +254,10 @@ class SheetBuilder:
view.append(self.parse_embedded_svg(foreground, {}))
if background is not None:
background_path = os.path.join(self.data_dir, "sheets", self.get_href(background))
shutil.copy(background_path, os.path.join(self.data_dir, "build", sheet_name))
background_path = os.path.join(self.layout_dir, self.get_href(background))
raster_path = os.path.join(self.sheets_dir, os.path.basename(background_path))
shutil.copy(background_path, raster_path)
self.references["RASTER"].append(raster_path)
if view_title is not None:
foreground_path = self.get_href(foreground)
@@ -322,7 +322,7 @@ class SheetBuilder:
self.defs.append(clip_path)
svg_path = self.get_href(image)
with open(os.path.join(self.sheet_dir, svg_path), "r") as template:
with open(os.path.join(self.layout_dir, svg_path), "r") as template:
embedded = ET.fromstring(pystache.render(template.read(), data))
# viewBox should not be nested
embedded.attrib["viewBox"] = ""
@@ -339,23 +339,29 @@ class SheetBuilder:
return group
def change_titleblock(self, sheet, titleblock_name):
ootb_titleblock_path = os.path.join(self.data_dir, "templates", "titleblocks", titleblock_name + ".svg")
titleblock_path = tool.Drawing.get_default_titleblock_path(titleblock_name)
sheet_path = tool.Drawing.get_document_uri(sheet, "LAYOUT")
sheet_dir = os.path.dirname(sheet_path)
os.makedirs(sheet_dir, exist_ok=True)
os.makedirs(os.path.join(sheet_dir, "titleblocks"), exist_ok=True)
if not os.path.exists(titleblock_path):
shutil.copy(ootb_titleblock_path, titleblock_path)
ET.register_namespace("", "http://www.w3.org/2000/svg")
ET.register_namespace("xlink", "http://www.w3.org/1999/xlink")
titleblock_svg_path = os.path.join(self.data_dir, "templates", "titleblocks", f"{titleblock_name}.svg")
view_root = ET.parse(titleblock_svg_path).getroot()
view_root = ET.parse(ootb_titleblock_path).getroot()
view_width = self.convert_to_mm(view_root.attrib.get("width"))
view_height = self.convert_to_mm(view_root.attrib.get("height"))
sheet_dir = os.path.join(self.data_dir, "sheets")
sheet_name = os.path.splitext(os.path.basename(tool.Drawing.get_document_uri(sheet)))[0]
sheet_path = os.path.join(sheet_dir, sheet_name + ".svg")
sheet_tree = ET.parse(sheet_path)
root = sheet_tree.getroot()
titleblock = sheet_tree.findall('{http://www.w3.org/2000/svg}g[@data-type="titleblock"]')[0]
image = titleblock.findall("{http://www.w3.org/2000/svg}image[@{http://www.w3.org/1999/xlink}href]")[0]
image.attrib["{http://www.w3.org/1999/xlink}href"] = f"../templates/titleblocks/{titleblock_name}.svg"
image.attrib["{http://www.w3.org/1999/xlink}href"] = os.path.relpath(titleblock_path, sheet_dir)
image.attrib["width"] = str(view_width)
image.attrib["height"] = str(view_height)
@@ -85,13 +85,7 @@ class SvgWriter:
self.svg.save(pretty=True)
def draw_underlay(self, image):
self.svg.add(
self.svg.image(
os.path.join("..", "diagrams", os.path.basename(image)),
width=self.width,
height=self.height,
)
)
self.svg.add(self.svg.image(os.path.basename(image), width=self.width, height=self.height))
return self
def setup_drawing_resource_paths(self, element):
@@ -84,7 +84,7 @@ class BIM_PT_camera(Panel):
row = layout.row(align=True)
row.operator("bim.create_drawing", text="Create Drawing", icon="OUTPUT")
op = row.operator("bim.open_view", icon="URL", text="")
op = row.operator("bim.open_drawing", icon="URL", text="")
op.view = context.active_object.name.split("/")[1]
@@ -185,7 +185,7 @@ class BIM_PT_drawings(Panel):
).drawing = active_drawing.ifc_definition_id
col = row.column()
col.alignment = "RIGHT"
op = row.operator("bim.open_view", icon="URL", text="")
op = row.operator("bim.open_drawing", icon="URL", text="")
op.view = active_drawing.name
op = row.operator("bim.activate_view", icon="OUTLINER_OB_CAMERA", text="")
op.drawing = active_drawing.ifc_definition_id
@@ -281,6 +281,7 @@ class BIM_PT_sheets(Panel):
active_sheet = self.props.sheets[self.props.active_sheet_index]
row = self.layout.row(align=True)
row.alignment = "RIGHT"
row.operator("bim.rename_sheet", icon="GREASEPENCIL", text="")
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="")
@@ -510,7 +511,7 @@ class BIM_UL_sheets(bpy.types.UIList):
layout.label(text="", translate=False)
return
row = layout.row()
row = layout.row(align=True)
if item.is_sheet:
if item.is_expanded:
row.operator(
@@ -520,8 +521,8 @@ class BIM_UL_sheets(bpy.types.UIList):
row.operator(
"bim.expand_sheet", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT"
).sheet = item.ifc_definition_id
name = "{} - {}".format(item.identification or "X", item.name or "Unnamed")
row.prop(item, "name", text=item.identification or "X", emboss=False)
row.label(text=f"{item.identification} - {item.name}")
else:
row.label(text="", icon="BLANK1")
if item.reference_type == "DRAWING":
@@ -530,6 +531,9 @@ class BIM_UL_sheets(bpy.types.UIList):
row.label(text="", icon="LONGDISPLAY")
elif item.reference_type == "TITLEBLOCK":
row.label(text="", icon="MENU_PANEL")
elif item.reference_type == "REVISION":
row.label(text="", icon="RECOVER_LAST")
if item.identification:
name = f"{item.identification} - {item.name or 'Unnamed'}"
else:
+2 -2
View File
@@ -182,11 +182,11 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
row.prop(context.scene.BIMProperties, "data_dir")
row.operator("bim.select_data_dir", icon="FILE_FOLDER", text="")
row = self.layout.row(align=True)
row.prop(context.scene.DocProperties, "docs_dir")
row = self.layout.row(align=True)
row.prop(context.scene.DocProperties, "sheets_dir")
row = self.layout.row(align=True)
row.prop(context.scene.DocProperties, "layouts_dir")
row = self.layout.row(align=True)
row.prop(context.scene.DocProperties, "titleblocks_dir")
row = self.layout.row(align=True)
row.prop(context.scene.DocProperties, "drawings_dir")
+59 -5
View File
@@ -78,7 +78,7 @@ def add_sheet(ifc, drawing, titleblock=None):
ifc.run(
"document.edit_reference",
reference=layout,
attributes={"Location": drawing.get_default_sheet_path(identification, "UNTITLED"), "Description": "LAYOUT"},
attributes={"Location": drawing.get_default_layout_path(identification, "UNTITLED"), "Description": "LAYOUT"},
)
ifc.run(
"document.edit_reference",
@@ -94,13 +94,37 @@ def open_sheet(drawing, sheet=None):
def remove_sheet(ifc, drawing, sheet=None):
for reference in drawing.get_document_references(sheet):
if drawing.get_reference_description(reference) in ("LAYOUT", "SHEET", "REVISION", "RASTER"):
uri = ifc.resolve_uri(drawing.get_document_uri(reference))
if drawing.does_file_exist(uri):
drawing.delete_file(uri)
ifc.run("document.remove_information", information=sheet)
drawing.import_sheets()
def update_sheet_name(ifc, drawing, sheet=None, name=None):
if drawing.get_name(sheet) != name:
ifc.run("document.edit_information", information=sheet, attributes={"Name": name})
def rename_sheet(ifc, drawing, sheet=None, identification=None, name=None):
ifc.run(
"document.edit_information", information=sheet, attributes={"Identification": identification, "Name": name}
)
for reference in drawing.get_document_references(sheet):
description = drawing.get_reference_description(reference)
if description == "SHEET":
old_location = drawing.get_reference_location(reference)
new_location = drawing.get_default_sheet_path(identification, name)
if old_location != new_location:
ifc.run("document.edit_reference", reference=reference, attributes={"Location": new_location})
old_location = ifc.resolve_uri(old_location)
if drawing.does_file_exist(old_location):
drawing.move_file(old_location, ifc.resolve_uri(new_location))
elif description == "LAYOUT":
old_location = drawing.get_reference_location(reference)
new_location = drawing.get_default_layout_path(identification, name)
if old_location != new_location:
ifc.run("document.edit_reference", reference=reference, attributes={"Location": new_location})
old_location = ifc.resolve_uri(old_location)
if drawing.does_file_exist(old_location):
drawing.move_file(old_location, ifc.resolve_uri(new_location))
def load_schedules(drawing):
@@ -213,6 +237,21 @@ def duplicate_drawing(ifc, drawing_tool, drawing=None, should_duplicate_annotati
drawing_tool.copy_representation(annotation, new_annotation)
ifc.run("group.unassign_group", group=group, product=new_annotation)
ifc.run("group.assign_group", group=new_group, products=[new_annotation])
old_reference = drawing_tool.get_drawing_document(new_drawing)
ifc.run("document.unassign_document", product=new_drawing, document=old_reference)
information = ifc.run("document.add_information")
uri = drawing_tool.get_default_drawing_path(drawing_name)
reference = ifc.run("document.add_reference", information=information)
if ifc.get_schema() == "IFC2X3":
attributes = {"DocumentId": "X", "Name": drawing_name, "Scope": "DRAWING"}
else:
attributes = {"Identification": "X", "Name": drawing_name, "Scope": "DRAWING"}
ifc.run("document.edit_information", information=information, attributes=attributes)
ifc.run("document.edit_reference", reference=reference, attributes={"Location": uri})
ifc.run("document.assign_document", product=new_drawing, document=reference)
drawing_tool.import_drawings()
return new_drawing
@@ -230,7 +269,11 @@ def remove_drawing(ifc, drawing_tool, drawing=None):
if reference_obj:
drawing_tool.delete_object(reference_obj)
ifc.run("root.remove_product", product=reference)
ifc.run("document.remove_information", information=drawing_tool.get_drawing_document(drawing))
information = drawing_tool.get_reference_document(drawing_tool.get_drawing_document(drawing))
uri = ifc.resolve_uri(drawing_tool.get_document_uri(information))
if drawing_tool.does_file_exist(uri):
drawing_tool.delete_file(uri)
ifc.run("document.remove_information", information=information)
ifc.run("root.remove_product", product=drawing)
drawing_tool.import_drawings()
@@ -245,6 +288,17 @@ def update_drawing_name(ifc, drawing_tool, drawing=None, name=None):
if collection:
drawing_tool.set_drawing_collection_name(group, collection)
reference = drawing_tool.get_drawing_document(drawing)
information = drawing_tool.get_reference_document(reference)
ifc.run("document.edit_information", information=information, attributes={"Name": name})
old_location = drawing_tool.get_reference_location(reference)
new_location = drawing_tool.get_default_drawing_path(name)
if old_location != new_location:
ifc.run("document.edit_reference", reference=reference, attributes={"Location": new_location})
old_location = ifc.resolve_uri(old_location)
if drawing_tool.does_file_exist(old_location):
drawing_tool.move_file(old_location, ifc.resolve_uri(new_location))
def add_annotation(ifc, collector, drawing_tool, drawing=None, object_type=None):
context = drawing_tool.get_annotation_context(drawing_tool.get_drawing_target_view(drawing))
+17 -7
View File
@@ -226,20 +226,22 @@ class Document:
@interface
class Drawing:
def activate_view(cls, camera): pass
def add_literal_to_annotation(cls, obj, Literal='Literal', Path='RIGHT', BoxAlignment='bottom-left'): pass
def copy_representation(cls, source, dest): pass
def create_annotation_object(cls, drawing, object_type): pass
def setup_annotation_object(cls, obj, object_type): pass
def create_camera(cls, name, matrix): pass
def create_svg_schedule(cls, schedule): pass
def create_svg_sheet(cls, document, titleblock): pass
def delete_collection(cls, collection): pass
def delete_drawing_elements(cls, elements): pass
def delete_file(cls, uri): pass
def delete_object(cls, obj): pass
def disable_editing_assigned_product(cls, obj): pass
def disable_editing_drawings(cls): pass
def disable_editing_schedules(cls): pass
def disable_editing_sheets(cls): pass
def disable_editing_text(cls, obj): pass
def does_file_exist(cls, uri): pass
def enable_editing(cls, obj): pass
def enable_editing_assigned_product(cls, obj): pass
def enable_editing_drawings(cls): pass
@@ -257,21 +259,25 @@ class Drawing:
def get_body_context(cls): pass
def get_default_drawing_path(cls, name): pass
def get_default_drawing_resource_path(cls, resource): pass
def get_default_layout_path(cls, identification, name): pass
def get_default_sheet_path(cls, identification, name): pass
def get_document_uri(cls, document): 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
def get_drawing_collection(cls, drawing): pass
def get_drawing_document(cls, drawing): pass
def get_drawing_group(cls, drawing): pass
def get_path_filename(cls, uri): pass
def get_drawing_references(cls, drawing): pass
def get_drawing_target_view(cls, drawing): pass
def get_group_elements(cls, group): pass
def get_ifc_representation_class(cls, object_type): pass
def get_name(cls, element): pass
def get_path_filename(cls, uri): pass
def get_reference_description(cls, reference): pass
def get_reference_document(cls, reference): pass
def get_reference_location(cls, reference): pass
def get_text_literal(cls, obj): pass
def remove_literal_from_annotation(cls, obj, literal): pass
def synchronise_ifc_and_text_attributes(cls, obj): pass
def add_literal_to_annotation(cls, obj, Literal='Literal', Path='RIGHT', BoxAlignment='bottom-left'): pass
def import_assigned_product(cls, obj): pass
def import_drawings(cls): pass
def import_schedules(cls): pass
@@ -279,16 +285,20 @@ class Drawing:
def import_text_attributes(cls, obj): pass
def is_camera_orthographic(cls): pass
def is_drawing_active(cls): pass
def move_file(cls, src, dest): pass
def open_spreadsheet(cls, uri): pass
def open_svg(cls, filepath): pass
def remove_literal_from_annotation(cls, obj, literal): pass
def run_root_assign_class(cls, obj=None, ifc_class=None, predefined_type=None, should_add_representation=True, context=None, ifc_representation_class=None): pass
def select_assigned_product(cls, drawing): pass
def set_drawing_collection_name(cls, group, collection): pass
def set_name(cls, element, name): pass
def setup_annotation_object(cls, obj, object_type): pass
def show_decorations(cls): pass
def sync_object_placement(cls, obj): pass
def update_text_value(cls, obj): pass
def synchronise_ifc_and_text_attributes(cls, obj): pass
def update_text_size_pset(cls, obj): pass
def update_text_value(cls, obj): pass
@interface
+31 -3
View File
@@ -21,6 +21,7 @@ import re
import bpy
import math
import bmesh
import shutil
import logging
import mathutils
import webbrowser
@@ -136,7 +137,7 @@ class Drawing(blenderbim.core.tool.Drawing):
def create_svg_sheet(cls, document, titleblock):
sheet_builder = sheeter.SheetBuilder()
sheet_builder.data_dir = bpy.context.scene.BIMProperties.data_dir
uri = cls.get_document_uri(document)
uri = cls.get_document_uri(document, "LAYOUT")
sheet_builder.create(uri, titleblock)
return uri
@@ -583,8 +584,9 @@ class Drawing(blenderbim.core.tool.Drawing):
continue
for reference in cls.get_document_references(sheet):
if reference.Description == "LAYOUT":
continue # The layout itself is an internal detail and should not be visible to users
if reference.Description in ("SHEET", "LAYOUT", "RASTER"):
# These references are an internal detail and should not be visible to users
continue
new = props.sheets.add()
new.ifc_definition_id = reference.id()
new.is_sheet = False
@@ -726,6 +728,18 @@ class Drawing(blenderbim.core.tool.Drawing):
# TODO below this point is highly experimental prototype code with no tests
@classmethod
def does_file_exist(cls, uri):
return os.path.exists(uri)
@classmethod
def delete_file(cls, uri):
os.remove(uri)
@classmethod
def move_file(cls, src, dest):
shutil.move(src, dest)
@classmethod
def generate_drawing_name(cls, target_view, location_hint):
if target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW") and location_hint:
@@ -737,6 +751,12 @@ class Drawing(blenderbim.core.tool.Drawing):
return location_hint + " " + target_view.split("_")[0]
return target_view
@classmethod
def get_default_layout_path(cls, identification, name):
return os.path.join(
bpy.context.scene.DocProperties.layouts_dir, cls.sanitise_filename(f"{identification} - {name}.svg")
)
@classmethod
def get_default_sheet_path(cls, identification, name):
return os.path.join(
@@ -1176,6 +1196,14 @@ class Drawing(blenderbim.core.tool.Drawing):
return document.DocumentReferences or []
return document.HasDocumentReferences or []
@classmethod
def get_reference_description(cls, reference):
return reference.Description
@classmethod
def get_reference_location(cls, reference):
return reference.Location
@classmethod
def get_reference_element(cls, reference):
if tool.Ifc.get_schema() == "IFC2X3":
+85 -6
View File
@@ -84,14 +84,26 @@ class TestDisableEditingSheets:
class TestAddSheet:
def test_run(self, ifc, drawing):
ifc.run("document.add_information").should_be_called().will_return("sheet")
ifc.run("document.add_reference", information="sheet").should_be_called().will_return("reference")
drawing.generate_sheet_identification().should_be_called().will_return("identification")
drawing.ensure_unique_identification("identification").should_be_called().will_return("u_identification")
ifc.get_schema().should_be_called().will_return("IFC4")
drawing.get_default_sheet_path("u_identification", "UNTITLED").should_be_called().will_return("uri")
drawing.get_default_layout_path("u_identification", "UNTITLED").should_be_called().will_return("layout_path")
drawing.get_default_titleblock_path("titleblock").should_be_called().will_return("titleblock_path")
ifc.run(
"document.edit_information",
information="sheet",
attributes={"Identification": "u_identification", "Name": "UNTITLED", "Scope": "DOCUMENTATION", "Location": "uri"},
attributes={"Identification": "u_identification", "Name": "UNTITLED", "Scope": "SHEET"},
).should_be_called()
ifc.run(
"document.edit_reference",
reference="reference",
attributes={"Location": "layout_path", "Description": "LAYOUT"},
).should_be_called()
ifc.run(
"document.edit_reference",
reference="reference",
attributes={"Location": "titleblock_path", "Description": "TITLEBLOCK"},
).should_be_called()
drawing.create_svg_sheet("sheet", "titleblock").should_be_called()
drawing.import_sheets().should_be_called()
@@ -99,13 +111,26 @@ class TestAddSheet:
def test_using_a_document_id_in_ifc2x3(self, ifc, drawing):
ifc.run("document.add_information").should_be_called().will_return("sheet")
ifc.run("document.add_reference", information="sheet").should_be_called().will_return("reference")
drawing.generate_sheet_identification().should_be_called().will_return("identification")
drawing.ensure_unique_identification("identification").should_be_called().will_return("u_identification")
ifc.get_schema().should_be_called().will_return("IFC2X3")
drawing.get_default_layout_path("u_identification", "UNTITLED").should_be_called().will_return("layout_path")
drawing.get_default_titleblock_path("titleblock").should_be_called().will_return("titleblock_path")
ifc.run(
"document.edit_information",
information="sheet",
attributes={"DocumentId": "u_identification", "Name": "UNTITLED", "Scope": "DOCUMENTATION"},
attributes={"DocumentId": "u_identification", "Name": "UNTITLED", "Scope": "SHEET"},
).should_be_called()
ifc.run(
"document.edit_reference",
reference="reference",
attributes={"Location": "layout_path", "Description": "LAYOUT"},
).should_be_called()
ifc.run(
"document.edit_reference",
reference="reference",
attributes={"Location": "titleblock_path", "Description": "TITLEBLOCK"},
).should_be_called()
drawing.create_svg_sheet("sheet", "titleblock").should_be_called()
drawing.import_sheets().should_be_called()
@@ -114,13 +139,19 @@ class TestAddSheet:
class TestOpenSheet:
def test_run(self, drawing):
drawing.get_document_uri("sheet").should_be_called().will_return("uri")
drawing.get_document_uri("sheet", "LAYOUT").should_be_called().will_return("uri")
drawing.open_svg("uri").should_be_called()
subject.open_sheet(drawing, sheet="sheet")
class TestRemoveSheet:
def test_run(self, ifc, drawing):
drawing.get_document_references("sheet").should_be_called().will_return(["reference"])
drawing.get_reference_description("reference").should_be_called().will_return("LAYOUT")
drawing.get_document_uri("reference").should_be_called().will_return("relative_uri")
ifc.resolve_uri("relative_uri").should_be_called().will_return("absolute_uri")
drawing.does_file_exist("absolute_uri").should_be_called().will_return(True)
drawing.delete_file("absolute_uri").should_be_called()
ifc.run("document.remove_information", information="sheet").should_be_called()
drawing.import_sheets().should_be_called()
subject.remove_sheet(ifc, drawing, sheet="sheet")
@@ -254,7 +285,11 @@ class TestAddDrawing:
ifc.run("document.add_information").should_be_called().will_return("information")
ifc.run("document.add_reference", information="information").should_be_called().will_return("reference")
ifc.get_schema().should_be_called().will_return("IFC4")
ifc.run("document.edit_information", information="information", attributes={"Identification": "X", "Name": "name", "Scope": "DRAWING"}).should_be_called()
ifc.run(
"document.edit_information",
information="information",
attributes={"Identification": "X", "Name": "name", "Scope": "DRAWING"},
).should_be_called()
ifc.run("document.edit_reference", reference="reference", attributes={"Location": "uri"}).should_be_called()
ifc.run("document.assign_document", product="element", document="reference").should_be_called()
drawing.import_drawings().should_be_called()
@@ -280,6 +315,24 @@ class TestDuplicateDrawing:
drawing.copy_representation("annotation", "new_annotation").should_be_called()
ifc.run("group.unassign_group", group="group", product="new_annotation").should_be_called()
ifc.run("group.assign_group", group="new_group", products=["new_annotation"]).should_be_called()
drawing.get_drawing_document("new_drawing").should_be_called().will_return("old_reference")
ifc.run("document.unassign_document", product="new_drawing", document="old_reference").should_be_called()
ifc.run("document.add_information").should_be_called().will_return("information")
ifc.run("document.add_reference", information="information").should_be_called().will_return("reference")
ifc.get_schema().should_be_called().will_return("IFC4")
drawing.get_default_drawing_path("unique_name").should_be_called().will_return("drawing_path")
ifc.run(
"document.edit_information",
information="information",
attributes={"Identification": "X", "Name": "unique_name", "Scope": "DRAWING"},
).should_be_called()
ifc.run(
"document.edit_reference", reference="reference", attributes={"Location": "drawing_path"}
).should_be_called()
ifc.run("document.assign_document", product="new_drawing", document="reference").should_be_called()
drawing.import_drawings().should_be_called()
subject.duplicate_drawing(ifc, drawing, drawing="drawing", should_duplicate_annotations=True)
@@ -296,7 +349,12 @@ class TestRemoveDrawing:
ifc.get_object("reference").should_be_called().will_return("reference_obj")
drawing.delete_object("reference_obj").should_be_called()
ifc.run("root.remove_product", product="reference").should_be_called()
drawing.get_drawing_document("drawing").should_be_called().will_return("information")
drawing.get_drawing_document("drawing").should_be_called().will_return("reference")
drawing.get_reference_document("reference").should_be_called().will_return("information")
drawing.get_document_uri("information").should_be_called().will_return("relative_uri")
ifc.resolve_uri("relative_uri").should_be_called().will_return("absolute_uri")
drawing.does_file_exist("absolute_uri").should_be_called().will_return(True)
drawing.delete_file("absolute_uri").should_be_called()
ifc.run("document.remove_information", information="information").should_be_called()
ifc.run("root.remove_product", product="drawing").should_be_called()
drawing.import_drawings().should_be_called()
@@ -310,6 +368,13 @@ class TestUpdateDrawingName:
drawing.get_name("group").should_be_called().will_return("name")
drawing.get_drawing_collection("drawing").should_be_called().will_return("collection")
drawing.set_drawing_collection_name("group", "collection").should_be_called()
drawing.get_drawing_document("drawing").should_be_called().will_return("reference")
drawing.get_reference_document("reference").should_be_called().will_return("information")
ifc.run("document.edit_information", information="information", attributes={"Name": "name"}).should_be_called()
drawing.get_reference_location("reference").should_be_called().will_return("location")
drawing.get_default_drawing_path("name").should_be_called().will_return("location")
subject.update_drawing_name(ifc, drawing, drawing="drawing", name="name")
def test_run(self, ifc, drawing):
@@ -320,6 +385,20 @@ class TestUpdateDrawingName:
ifc.run("attribute.edit_attributes", product="group", attributes={"Name": "name"}).should_be_called()
drawing.get_drawing_collection("drawing").should_be_called().will_return("collection")
drawing.set_drawing_collection_name("group", "collection").should_be_called()
drawing.get_drawing_document("drawing").should_be_called().will_return("reference")
drawing.get_reference_document("reference").should_be_called().will_return("information")
ifc.run("document.edit_information", information="information", attributes={"Name": "name"}).should_be_called()
drawing.get_reference_location("reference").should_be_called().will_return("old_location")
drawing.get_default_drawing_path("name").should_be_called().will_return("new_location")
ifc.run(
"document.edit_reference", reference="reference", attributes={"Location": "new_location"}
).should_be_called()
ifc.resolve_uri("old_location").should_be_called().will_return("old_uri")
drawing.does_file_exist("old_uri").should_be_called().will_return(True)
ifc.resolve_uri("new_location").should_be_called().will_return("new_uri")
drawing.move_file("old_uri", "new_uri").should_be_called()
subject.update_drawing_name(ifc, drawing, drawing="drawing", name="name")
+3 -3
View File
@@ -83,7 +83,7 @@ class TestDeleteDrawingElements(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
obj = bpy.data.objects.new("Object", None)
obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh"))
collection = bpy.data.collections.new("Collection")
bpy.context.scene.collection.children.link(collection)
collection.objects.link(obj)
@@ -488,7 +488,7 @@ class TestImportSheets(NewFile):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
ifc.createIfcDocumentInformation(Identification="Y", Name="FOOBAZ")
document = ifc.createIfcDocumentInformation(Identification="X", Name="FOOBAR", Scope="DOCUMENTATION")
document = ifc.createIfcDocumentInformation(Identification="X", Name="FOOBAR", Scope="SHEET")
subject.import_sheets()
props = bpy.context.scene.DocProperties
assert props.sheets[0].ifc_definition_id == document.id()
@@ -499,7 +499,7 @@ class TestImportSheets(NewFile):
ifc = ifcopenshell.file(schema="IFC2X3")
tool.Ifc.set(ifc)
ifc.createIfcDocumentInformation(DocumentId="Y", Name="FOOBAZ")
document = ifc.createIfcDocumentInformation(DocumentId="X", Name="FOOBAR", Scope="DOCUMENTATION")
document = ifc.createIfcDocumentInformation(DocumentId="X", Name="FOOBAR", Scope="SHEET")
subject.import_sheets()
props = bpy.context.scene.DocProperties
assert props.sheets[0].ifc_definition_id == document.id()