mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
See #2516. References to titleblocks, layouts, and published sheets are now explicitly stored. Titleblock paths are now configurable. Generated filenames are sanitised.
This commit is contained in:
@@ -732,7 +732,7 @@ class AddDrawingToSheet(bpy.types.Operator, Operator):
|
||||
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)
|
||||
drawing_uri = tool.Drawing.get_document_uri(tool.Drawing.get_drawing_document(drawing))
|
||||
drawing_reference = tool.Drawing.get_drawing_document(drawing)
|
||||
|
||||
sheet = tool.Ifc.get().by_id(active_sheet.ifc_definition_id)
|
||||
if not sheet.is_a("IfcDocumentInformation"):
|
||||
@@ -745,7 +745,7 @@ class AddDrawingToSheet(bpy.types.Operator, Operator):
|
||||
|
||||
has_drawing = False
|
||||
for reference in references:
|
||||
if reference.Location == drawing_uri:
|
||||
if reference.Location == drawing_reference.Location:
|
||||
has_drawing = True
|
||||
break
|
||||
|
||||
@@ -754,9 +754,16 @@ class AddDrawingToSheet(bpy.types.Operator, Operator):
|
||||
|
||||
reference = tool.Ifc.run("document.add_reference", information=sheet)
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
attributes = {"ItemReference": str(len(sheet.DocumentReferences or []))}
|
||||
references = sheet.DocumentReferences
|
||||
id_attr = "ItemReference"
|
||||
else:
|
||||
attributes = {"Identification": str(len(sheet.HasDocumentReferences or []))}
|
||||
references = sheet.HasDocumentReferences
|
||||
id_attr = "Identification"
|
||||
attributes = {
|
||||
id_attr: str(len([r for r in references if r.Description in ("DRAWING", "SCHEDULE")]) + 1),
|
||||
"Location": drawing_reference.Location,
|
||||
"Description": "DRAWING",
|
||||
}
|
||||
tool.Ifc.run("document.edit_reference", reference=reference, attributes=attributes)
|
||||
sheet_builder = sheeter.SheetBuilder()
|
||||
sheet_builder.data_dir = context.scene.BIMProperties.data_dir
|
||||
@@ -820,6 +827,15 @@ class CreateSheets(bpy.types.Operator):
|
||||
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"]:
|
||||
reference = tool.Ifc.run("document.add_reference", information=sheet)
|
||||
tool.Ifc.run(
|
||||
"document.edit_reference",
|
||||
reference=reference,
|
||||
attributes={"Location": tool.Ifc.get_relative_uri(svg), "Description": "SHEET"},
|
||||
)
|
||||
|
||||
svg2pdf_command = context.preferences.addons["blenderbim"].preferences.svg2pdf_command
|
||||
svg2dxf_command = context.preferences.addons["blenderbim"].preferences.svg2dxf_command
|
||||
|
||||
@@ -1186,25 +1202,29 @@ class BuildSchedule(bpy.types.Operator, Operator):
|
||||
core.build_schedule(tool.Drawing, schedule=tool.Ifc.get().by_id(self.schedule))
|
||||
|
||||
|
||||
class AddScheduleToSheet(bpy.types.Operator):
|
||||
class AddScheduleToSheet(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_schedule_to_sheet"
|
||||
bl_label = "Add Schedule To Sheet"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
# TODO: check undo redo
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = context.scene.DocProperties
|
||||
return props.schedules and props.sheets and context.scene.BIMProperties.data_dir
|
||||
|
||||
def execute(self, context):
|
||||
def _execute(self, context):
|
||||
props = context.scene.DocProperties
|
||||
active_schedule = props.schedules[props.active_schedule_index]
|
||||
active_sheet = props.sheets[props.active_sheet_index]
|
||||
schedule = tool.Ifc.get().by_id(active_schedule.ifc_definition_id)
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
schedule_location = tool.Drawing.get_path_with_ext(schedule.DocumentReferences[0].Location, "svg")
|
||||
else:
|
||||
schedule_location = tool.Drawing.get_path_with_ext(schedule.HasDocumentReferences[0].Location, "svg")
|
||||
|
||||
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 []
|
||||
@@ -1213,19 +1233,25 @@ class AddScheduleToSheet(bpy.types.Operator):
|
||||
|
||||
has_schedule = False
|
||||
for reference in references:
|
||||
if reference.Location == tool.Drawing.get_path_with_ext(tool.Drawing.get_document_uri(schedule), "svg"):
|
||||
if reference.Location == schedule_location:
|
||||
has_schedule = True
|
||||
break
|
||||
|
||||
if has_schedule:
|
||||
return {"FINISHED"}
|
||||
return
|
||||
|
||||
reference = tool.Ifc.run("document.add_reference", information=sheet)
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
attributes = {"ItemReference": str(len(sheet.DocumentReferences or []))}
|
||||
references = sheet.DocumentReferences
|
||||
id_attr = "ItemReference"
|
||||
else:
|
||||
attributes = {"Identification": str(len(sheet.HasDocumentReferences or []))}
|
||||
attributes["Location"] = tool.Drawing.get_schedule_location(schedule)
|
||||
references = sheet.HasDocumentReferences
|
||||
id_attr = "Identification"
|
||||
attributes = {
|
||||
id_attr: str(len([r for r in references if r.Description in ("DRAWING", "SCHEDULE")]) + 1),
|
||||
"Location": schedule_location,
|
||||
"Description": "SCHEDULE",
|
||||
}
|
||||
tool.Ifc.run("document.edit_reference", reference=reference, attributes=attributes)
|
||||
|
||||
sheet_builder = sheeter.SheetBuilder()
|
||||
@@ -1233,7 +1259,6 @@ class AddScheduleToSheet(bpy.types.Operator):
|
||||
sheet_builder.add_schedule(reference, schedule, sheet)
|
||||
|
||||
tool.Drawing.import_sheets()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddDrawingStyleAttribute(bpy.types.Operator):
|
||||
|
||||
@@ -354,13 +354,14 @@ 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")
|
||||
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")
|
||||
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")
|
||||
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")
|
||||
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")
|
||||
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")
|
||||
|
||||
|
||||
class BIMCameraProperties(PropertyGroup):
|
||||
|
||||
@@ -35,6 +35,8 @@ class SheetBuilder:
|
||||
self.scale = "NTS"
|
||||
|
||||
def create(self, sheet_path, titleblock_name):
|
||||
sheet_dir = os.path.dirname(sheet_path)
|
||||
|
||||
root = ET.Element("svg")
|
||||
root.attrib["xmlns"] = "http://www.w3.org/2000/svg"
|
||||
root.attrib["xmlns:xlink"] = "http://www.w3.org/1999/xlink"
|
||||
@@ -48,7 +50,9 @@ class SheetBuilder:
|
||||
view = ET.SubElement(root, "g")
|
||||
view.attrib["data-type"] = "titleblock"
|
||||
titleblock = ET.SubElement(view, "image")
|
||||
titleblock.attrib["xlink:href"] = f"./titleblocks/{titleblock_name}.svg"
|
||||
titleblock.attrib["xlink:href"] = os.path.relpath(
|
||||
tool.Drawing.get_default_titleblock_path(titleblock_name), sheet_dir
|
||||
)
|
||||
titleblock.attrib["x"] = "0"
|
||||
titleblock.attrib["y"] = "0"
|
||||
titleblock.attrib["width"] = str(view_width)
|
||||
@@ -58,7 +62,6 @@ class SheetBuilder:
|
||||
root.attrib["height"] = "{}mm".format(view_height)
|
||||
root.attrib["viewBox"] = "0 0 {} {}".format(view_width, view_height)
|
||||
|
||||
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)
|
||||
sheet_titleblock_path = os.path.join(sheet_dir, "titleblocks", titleblock_name + ".svg")
|
||||
|
||||
@@ -99,6 +99,7 @@ class SvgWriter:
|
||||
for resource in ("Stylesheet", "Markers", "Symbols", "Patterns"):
|
||||
resource_path = pset.get(resource)
|
||||
if not resource_path:
|
||||
self.resource_paths[resource] = None
|
||||
continue
|
||||
os.makedirs(os.path.dirname(resource_path), exist_ok=True)
|
||||
if not os.path.exists(resource_path):
|
||||
|
||||
@@ -528,5 +528,10 @@ class BIM_UL_sheets(bpy.types.UIList):
|
||||
row.label(text="", icon="IMAGE_DATA")
|
||||
elif item.reference_type == "SCHEDULE":
|
||||
row.label(text="", icon="LONGDISPLAY")
|
||||
name = "{} - {}".format(item.identification or "X", item.name or "Unnamed")
|
||||
elif item.reference_type == "TITLEBLOCK":
|
||||
row.label(text="", icon="MENU_PANEL")
|
||||
if item.identification:
|
||||
name = f"{item.identification} - {item.name or 'Unnamed'}"
|
||||
else:
|
||||
name = item.name or "Unnamed"
|
||||
row.label(text=name)
|
||||
|
||||
@@ -187,6 +187,8 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(context.scene.DocProperties, "sheets_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")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(context.scene.DocProperties, "stylesheet_path")
|
||||
|
||||
@@ -66,22 +66,31 @@ def disable_editing_sheets(drawing):
|
||||
|
||||
def add_sheet(ifc, drawing, titleblock=None):
|
||||
sheet = ifc.run("document.add_information")
|
||||
layout = ifc.run("document.add_reference", information=sheet)
|
||||
titleblock_reference = ifc.run("document.add_reference", information=sheet)
|
||||
identification = drawing.generate_sheet_identification()
|
||||
identification = drawing.ensure_unique_identification(identification)
|
||||
attributes = {"Identification": identification, "Name": "UNTITLED", "Scope": "DOCUMENTATION"}
|
||||
if ifc.get_schema() == "IFC2X3":
|
||||
attributes["DocumentId"] = attributes["Identification"]
|
||||
del attributes["Identification"]
|
||||
# TODO: How does IFC2X3 store the location?
|
||||
attributes = {"DocumentId": identification, "Name": "UNTITLED", "Scope": "SHEET"}
|
||||
else:
|
||||
attributes["Location"] = drawing.get_default_sheet_path(identification, "UNTITLED")
|
||||
attributes = {"Identification": identification, "Name": "UNTITLED", "Scope": "SHEET"}
|
||||
ifc.run("document.edit_information", information=sheet, attributes=attributes)
|
||||
ifc.run(
|
||||
"document.edit_reference",
|
||||
reference=layout,
|
||||
attributes={"Location": drawing.get_default_sheet_path(identification, "UNTITLED"), "Description": "LAYOUT"},
|
||||
)
|
||||
ifc.run(
|
||||
"document.edit_reference",
|
||||
reference=titleblock_reference,
|
||||
attributes={"Location": drawing.get_default_titleblock_path(titleblock), "Description": "TITLEBLOCK"},
|
||||
)
|
||||
drawing.create_svg_sheet(sheet, titleblock)
|
||||
drawing.import_sheets()
|
||||
|
||||
|
||||
def open_sheet(drawing, sheet=None):
|
||||
drawing.open_svg(drawing.get_document_uri(sheet))
|
||||
drawing.open_svg(drawing.get_document_uri(sheet, "LAYOUT"))
|
||||
|
||||
|
||||
def remove_sheet(ifc, drawing, sheet=None):
|
||||
|
||||
@@ -255,8 +255,10 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
|
||||
|
||||
@classmethod
|
||||
def get_document_uri(cls, document):
|
||||
def get_document_uri(cls, document, description=None):
|
||||
if getattr(document, "Location", None):
|
||||
if os.path.isabs(document.Location):
|
||||
return document.Location
|
||||
ifc_path = tool.Ifc.get_path()
|
||||
if os.path.isfile(ifc_path):
|
||||
ifc_path = os.path.dirname(ifc_path)
|
||||
@@ -267,6 +269,8 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
else:
|
||||
references = document.HasDocumentReferences
|
||||
for reference in references:
|
||||
if description and reference.Description != description:
|
||||
continue
|
||||
location = cls.get_document_uri(reference)
|
||||
if location:
|
||||
return location
|
||||
@@ -563,7 +567,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
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"]
|
||||
sheets = [d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "SHEET"]
|
||||
for sheet in sheets:
|
||||
new = props.sheets.add()
|
||||
new.ifc_definition_id = sheet.id()
|
||||
@@ -579,22 +583,19 @@ 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
|
||||
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"
|
||||
new.identification = reference.ItemReference or ""
|
||||
else:
|
||||
new.identification = reference.Identification or "X"
|
||||
new.identification = reference.Identification or ""
|
||||
|
||||
element = cls.get_reference_element(reference)
|
||||
if element:
|
||||
new.name = element.Name
|
||||
new.reference_type = "DRAWING"
|
||||
else:
|
||||
new.name = cls.get_reference_document(reference).Name or "Unnamed"
|
||||
new.reference_type = "SCHEDULE"
|
||||
new.name = os.path.basename(reference.Location)
|
||||
new.reference_type = reference.Description
|
||||
|
||||
@classmethod
|
||||
def import_text_attributes(cls, obj):
|
||||
@@ -738,11 +739,21 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
|
||||
@classmethod
|
||||
def get_default_sheet_path(cls, identification, name):
|
||||
return os.path.join(bpy.context.scene.DocProperties.sheets_dir, f"{identification} - {name}.svg")
|
||||
return os.path.join(
|
||||
bpy.context.scene.DocProperties.sheets_dir, cls.sanitise_filename(f"{identification} - {name}.svg")
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_default_titleblock_path(cls, name):
|
||||
return os.path.join(bpy.context.scene.DocProperties.titleblocks_dir, cls.sanitise_filename(f"{name}.svg"))
|
||||
|
||||
@classmethod
|
||||
def get_default_drawing_path(cls, name):
|
||||
return os.path.join(bpy.context.scene.DocProperties.drawings_dir, f"{name}.svg")
|
||||
return os.path.join(bpy.context.scene.DocProperties.drawings_dir, cls.sanitise_filename(f"{name}.svg"))
|
||||
|
||||
@classmethod
|
||||
def sanitise_filename(cls, name):
|
||||
return "".join(x for x in name if (x.isalnum() or x in "_- "))
|
||||
|
||||
@classmethod
|
||||
def get_default_drawing_resource_path(cls, resource):
|
||||
|
||||
@@ -106,11 +106,22 @@ class Ifc(blenderbim.core.tool.Ifc):
|
||||
|
||||
@classmethod
|
||||
def resolve_uri(cls, uri):
|
||||
if os.path.isabs(uri):
|
||||
return uri
|
||||
ifc_path = cls.get_path()
|
||||
if os.path.isfile(ifc_path):
|
||||
ifc_path = os.path.dirname(ifc_path)
|
||||
return uri if not uri or os.path.isabs(uri) else os.path.join(ifc_path, uri)
|
||||
|
||||
@classmethod
|
||||
def get_relative_uri(cls, uri):
|
||||
if not os.path.isabs(uri):
|
||||
return uri
|
||||
ifc_path = cls.get_path()
|
||||
if os.path.isfile(ifc_path):
|
||||
ifc_path = os.path.dirname(ifc_path)
|
||||
return os.path.relpath(uri, ifc_path)
|
||||
|
||||
@classmethod
|
||||
def unlink(cls, element=None, obj=None):
|
||||
IfcStore.unlink_element(element, obj)
|
||||
|
||||
@@ -247,13 +247,13 @@ class TestGetDocumentUri(NewFile):
|
||||
def test_run(self):
|
||||
ifc = ifcopenshell.file()
|
||||
document = ifc.createIfcDocumentInformation(
|
||||
Identification="X", Name="FOOBAR", Scope="DOCUMENTATION", Location="Location"
|
||||
Identification="X", Name="FOOBAR", Scope="SHEET", Location="Location"
|
||||
)
|
||||
assert subject.get_document_uri(document) == os.path.abspath(os.path.join(tool.Ifc.get_path(), "Location"))
|
||||
|
||||
def test_get_indirect_locations(self):
|
||||
ifc = ifcopenshell.file()
|
||||
document = ifc.createIfcDocumentInformation(Identification="X", Name="FOOBAR", Scope="DOCUMENTATION")
|
||||
document = ifc.createIfcDocumentInformation(Identification="X", Name="FOOBAR", Scope="SHEET")
|
||||
reference = ifc.createIfcDocumentReference(Location="Location", ReferencedDocument=document)
|
||||
assert subject.get_document_uri(document) == os.path.abspath(os.path.join(tool.Ifc.get_path(), "Location"))
|
||||
assert subject.get_document_uri(reference) == os.path.abspath(os.path.join(tool.Ifc.get_path(), "Location"))
|
||||
@@ -263,7 +263,7 @@ class TestGetDocumentUri(NewFile):
|
||||
tool.Ifc.set(ifc)
|
||||
reference = ifc.createIfcDocumentReference(Location="Location")
|
||||
document = ifc.createIfcDocumentInformation(
|
||||
DocumentId="X", Name="FOOBAR", Scope="DOCUMENTATION", DocumentReferences=[reference]
|
||||
DocumentId="X", Name="FOOBAR", Scope="SHEET", DocumentReferences=[reference]
|
||||
)
|
||||
assert subject.get_document_uri(document) == os.path.abspath(os.path.join(tool.Ifc.get_path(), "Location"))
|
||||
assert subject.get_document_uri(reference) == os.path.abspath(os.path.join(tool.Ifc.get_path(), "Location"))
|
||||
|
||||
Reference in New Issue
Block a user