See #2516. WARNING: breaking changes! Restructure drawing system to use relative paths based on IfcDocumentReference Locations.

This commit is contained in:
Dion Moult
2023-04-08 21:59:11 +10:00
parent 5864873945
commit 2e466ead37
18 changed files with 285 additions and 173 deletions

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

+2 -1
View File
@@ -24,6 +24,7 @@ import zipfile
import tempfile
import ifcopenshell
import blenderbim.bim.handler
import blenderbim.tool as tool
from pathlib import Path
@@ -371,7 +372,7 @@ class IfcStore:
def unlink_element(element=None, obj=None):
if element is None:
try:
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
element = tool.Ifc.get_entity(obj)
except:
pass
@@ -179,6 +179,7 @@ class CreateDrawing(bpy.types.Operator):
self.camera = context.scene.camera
self.camera_element = tool.Ifc.get_entity(self.camera)
self.camera_document = tool.Drawing.get_drawing_document(self.camera_element)
self.file = IfcStore.get_file()
with profile("Drawing generation process"):
@@ -198,14 +199,8 @@ class CreateDrawing(bpy.types.Operator):
self.svg_writer.camera_projection = tuple(
self.camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
)
pset = ifcopenshell.util.element.get_psets(self.camera_element)["EPset_Drawing"]
related_paths = {
"Stylesheet": pset.get("Stylesheet"),
"Markers": pset.get("Markers"),
"Symbols": pset.get("Symbols"),
"Patterns": pset.get("Patterns"),
}
self.svg_writer.define_related_paths(**related_paths)
self.svg_writer.setup_drawing_resource_paths(self.camera_element)
with profile("Generate underlay"):
underlay_svg = self.generate_underlay(context)
@@ -237,7 +232,7 @@ class CreateDrawing(bpy.types.Operator):
def combine_svgs(self, context, underlay, linework, annotation):
# Hacky :)
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "diagrams", self.drawing_name + ".svg")
svg_path = self.get_svg_path()
with open(svg_path, "w") as outfile:
self.svg_writer.create_blank_svg(svg_path).define_boilerplate()
boilerplate = self.svg_writer.svg.tostring()
@@ -282,7 +277,7 @@ class CreateDrawing(bpy.types.Operator):
def generate_underlay(self, context):
if not self.cprops.has_underlay:
return
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-underlay.svg")
svg_path = self.get_svg_path(cache_type="underlay")
context.scene.render.filepath = svg_path[0:-4] + ".png"
drawing_style = context.scene.DocProperties.drawing_styles[self.cprops.active_drawing_style_index]
@@ -322,7 +317,7 @@ class CreateDrawing(bpy.types.Operator):
def generate_linework(self, context):
if not self.cprops.has_linework:
return
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-linework.svg")
svg_path = self.get_svg_path(cache_type="linework")
if os.path.isfile(svg_path) and self.props.should_use_linework_cache:
return svg_path
@@ -605,7 +600,7 @@ class CreateDrawing(bpy.types.Operator):
def generate_annotation(self, context):
if not self.cprops.has_annotation:
return
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-annotation.svg")
svg_path = self.get_svg_path(cache_type="annotation")
if os.path.isfile(svg_path) and self.props.should_use_annotation_cache:
return svg_path
@@ -662,6 +657,17 @@ class CreateDrawing(bpy.types.Operator):
return element.LayerSetName
return "mat-" + str(element.id())
def get_svg_path(self, cache_type=None):
drawing_path = tool.Drawing.get_document_uri(self.camera_document)
drawings_dir = os.path.dirname(drawing_path)
if cache_type:
drawings_dir = os.path.join(drawings_dir, "cache")
os.makedirs(drawings_dir, exist_ok=True)
return os.path.join(drawings_dir, f"{self.drawing_name}-{cache_type}.svg")
os.makedirs(drawings_dir, exist_ok=True)
return drawing_path
class AddAnnotation(bpy.types.Operator, Operator):
bl_idname = "bim.add_annotation"
@@ -711,7 +717,7 @@ class OpenSheet(bpy.types.Operator, Operator):
core.open_sheet(tool.Drawing, sheet=sheet)
class AddDrawingToSheet(bpy.types.Operator):
class AddDrawingToSheet(bpy.types.Operator, Operator):
bl_idname = "bim.add_drawing_to_sheet"
bl_label = "Add Drawing To Sheet"
bl_options = {"REGISTER", "UNDO"}
@@ -721,11 +727,13 @@ class AddDrawingToSheet(bpy.types.Operator):
props = context.scene.DocProperties
return props.drawings and props.sheets and context.scene.BIMProperties.data_dir
def execute(self, context):
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)
drawing_uri = tool.Drawing.get_document_uri(tool.Drawing.get_drawing_document(drawing))
sheet = tool.Ifc.get().by_id(active_sheet.ifc_definition_id)
if not sheet.is_a("IfcDocumentInformation"):
return {"FINISHED"}
@@ -737,13 +745,7 @@ class AddDrawingToSheet(bpy.types.Operator):
has_drawing = False
for reference in references:
if tool.Ifc.get_schema() == "IFC2X3":
element = [r for r in tool.Ifc.by_type("IfcRelAssociatesDocument") if r.RelatingDocument == reference][
0
].RelatedObjects[0]
else:
element = reference.DocumentRefForObjects[0].RelatedObjects[0]
if element == drawing:
if reference.Location == drawing_uri:
has_drawing = True
break
@@ -756,7 +758,6 @@ class AddDrawingToSheet(bpy.types.Operator):
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(reference, drawing, sheet)
@@ -810,15 +811,19 @@ class CreateSheets(bpy.types.Operator):
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
sheet_builder.build(sheet)
# These variables will be made available to the evaluated commands
svg = sheet_builder.build(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"
svg2pdf_command = context.preferences.addons["blenderbim"].preferences.svg2pdf_command
svg2dxf_command = context.preferences.addons["blenderbim"].preferences.svg2dxf_command
if svg2pdf_command:
path = os.path.join(scene.BIMProperties.data_dir, "build", name)
svg = os.path.join(path, name + ".svg")
pdf = os.path.join(path, name + ".pdf")
# With great power comes great responsibility. Example:
# [['inkscape', svg, '-o', pdf]]
commands = eval(svg2pdf_command)
@@ -826,11 +831,6 @@ class CreateSheets(bpy.types.Operator):
subprocess.run(command)
if svg2dxf_command:
path = os.path.join(scene.BIMProperties.data_dir, "build", name)
svg = os.path.join(path, name + ".svg")
eps = os.path.join(path, name + ".eps")
dxf = os.path.join(path, name + ".dxf")
base = os.path.join(path, name)
# With great power comes great responsibility. Example:
# [['inkscape', svg, '-o', eps], ['pstoedit', '-dt', '-f', 'dxf:-polyaslines -mm', eps, dxf, '-psarg', '-dNOSAFER']]
commands = eval(svg2dxf_command)
@@ -840,10 +840,7 @@ class CreateSheets(bpy.types.Operator):
if svg2pdf_command:
open_with_user_command(context.preferences.addons["blenderbim"].preferences.pdf_command, pdf)
else:
open_with_user_command(
context.preferences.addons["blenderbim"].preferences.svg_command,
os.path.join(scene.BIMProperties.data_dir, "build", name, name + ".svg"),
)
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg)
return {"FINISHED"}
@@ -894,19 +891,19 @@ class OpenView(bpy.types.Operator):
return self.execute(context)
def execute(self, context):
if not self.open_all:
view_list = [self.view]
if self.open_all:
drawings = [tool.Ifc.get().by_id(d.ifc_definition_id) for d in context.scene.DocProperties.drawings]
else:
view_list = [d.name for d in context.scene.DocProperties.drawings]
drawings = [tool.Ifc.get().by_id(context.scene.DocProperties.drawings.get(self.view).ifc_definition_id)]
data_dir = Path(context.scene.BIMProperties.data_dir)
diagrams = []
drawing_uris = []
drawings_not_found = []
for view_name in view_list.copy():
path = data_dir / "diagrams" / (view_name + ".svg")
if not path.is_file():
drawings_not_found.append(view_name)
diagrams.append(path)
for drawing in drawings:
drawing_uri = tool.Drawing.get_document_uri(tool.Drawing.get_drawing_document(drawing))
drawing_uris.append(drawing_uri)
if not os.path.exists(drawing_uri):
drawings_not_found.append(drawing.Name)
if drawings_not_found:
msg = "Some drawings .svg files were not found, need to print them first: \n{}.".format(
@@ -915,11 +912,8 @@ class OpenView(bpy.types.Operator):
self.report({"ERROR"}, msg)
return {"CANCELLED"}
for diagram_path in diagrams:
open_with_user_command(
context.preferences.addons["blenderbim"].preferences.svg_command,
str(diagram_path),
)
for drawing_uri in drawing_uris:
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, drawing_uri)
return {"FINISHED"}
@@ -1143,12 +1137,15 @@ class AddSchedule(bpy.types.Operator, Operator):
bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
filter_glob: bpy.props.StringProperty(default="*.ods;*.xls;*.xlsx", options={"HIDDEN"})
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=True)
def _execute(self, context):
filepath = self.filepath
if self.use_relative_path:
filepath = os.path.relpath(filepath, bpy.path.abspath("//"))
ifc_path = tool.Ifc.get_path()
if os.path.isfile(ifc_path):
ifc_path = os.path.dirname(ifc_path)
filepath = os.path.relpath(filepath, ifc_path)
core.add_schedule(
tool.Ifc,
tool.Drawing,
@@ -1216,7 +1213,7 @@ class AddScheduleToSheet(bpy.types.Operator):
has_schedule = False
for reference in references:
if reference.Location == tool.Drawing.get_schedule_location(schedule):
if reference.Location == tool.Drawing.get_path_with_ext(tool.Drawing.get_document_uri(schedule), "svg"):
has_schedule = True
break
@@ -354,6 +354,13 @@ 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")
class BIMCameraProperties(PropertyGroup):
@@ -17,14 +17,15 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import os
import bpy
import uuid
import shutil
import ntpath
import pystache
import urllib.parse
import xml.etree.ElementTree as ET
import blenderbim.tool as tool
import ifcopenshell.util.geolocation
from shutil import copy
from xml.dom import minidom
@@ -40,15 +41,14 @@ class SheetBuilder:
root.attrib["id"] = "root"
root.attrib["version"] = "1.1"
view_root = ET.parse(
os.path.join(self.data_dir, "templates", "titleblocks", titleblock_name + ".svg")
).getroot()
titleblock_path = os.path.join(self.data_dir, "templates", "titleblocks", titleblock_name + ".svg")
view_root = ET.parse(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"] = f"../templates/titleblocks/{titleblock_name}.svg"
titleblock.attrib["xlink:href"] = f"./titleblocks/{titleblock_name}.svg"
titleblock.attrib["x"] = "0"
titleblock.attrib["y"] = "0"
titleblock.attrib["width"] = str(view_width)
@@ -58,17 +58,24 @@ 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")
if not os.path.exists(sheet_titleblock_path):
shutil.copy(titleblock_path, sheet_titleblock_path)
with open(sheet_path, "w") as f:
f.write(minidom.parseString(ET.tostring(root)).toprettyxml(indent=" "))
def add_drawing(self, reference, drawing, sheet):
filename = drawing.Name
sheet_name = os.path.splitext(os.path.basename(tool.Drawing.get_document_uri(sheet)))[0]
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 + "-underlay.png")
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):
raise FileNotFoundError
@@ -110,7 +117,7 @@ class SheetBuilder:
foreground.attrib["width"] = str(view_width)
foreground.attrib["height"] = str(view_height)
self.add_view_title(30, view_height + 35, view)
self.add_view_title(30, view_height + 35, view, sheet_dir)
sheet_tree.write(sheet_path)
def remove_drawing(self, reference, sheet):
@@ -128,11 +135,12 @@ class SheetBuilder:
sheet_tree.write(sheet_path)
def add_schedule(self, reference, schedule, sheet):
view_path = tool.Drawing.get_document_uri(schedule)
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)
schedule_name = os.path.splitext(os.path.basename(view_path))[0]
sheet_path = tool.Drawing.get_document_uri(sheet)
sheet_dir = os.path.dirname(sheet_path)
ET.register_namespace("", "http://www.w3.org/2000/svg")
ET.register_namespace("xlink", "http://www.w3.org/1999/xlink")
@@ -152,31 +160,40 @@ class SheetBuilder:
foreground = ET.SubElement(view, "image")
foreground.attrib["data-type"] = "table"
foreground.attrib["xlink:href"] = "../schedules/{}.svg".format(schedule_name)
foreground.attrib["xlink:href"] = os.path.relpath(view_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)
self.add_view_title(30, view_height + 35, view, sheet_dir)
sheet_tree.write(sheet_path)
def add_view_title(self, x, y, parent):
title_tree = ET.parse(os.path.join(self.data_dir, "templates", "view-title.svg"))
def add_view_title(self, x, y, parent, sheet_dir):
title_path = os.path.join(sheet_dir, "assets", "view-title.svg")
os.makedirs(os.path.dirname(title_path), exist_ok=True)
if not os.path.exists(title_path):
ootb_title = os.path.join(bpy.context.scene.BIMProperties.data_dir, "assets", "view-title.svg")
shutil.copy(ootb_title, title_path)
title_tree = ET.parse(title_path)
title_root = title_tree.getroot()
title = ET.SubElement(parent, "image")
title.attrib["data-type"] = "view-title"
title.attrib["xlink:href"] = "../templates/view-title.svg"
title.attrib["xlink:href"] = os.path.relpath(title_path, sheet_dir)
title.attrib["x"] = str(x)
title.attrib["y"] = str(y)
title.attrib["width"] = str(self.convert_to_mm(title_root.attrib.get("width")))
title.attrib["height"] = str(self.convert_to_mm(title_root.attrib.get("height")))
def build(self, sheet):
sheet_name = os.path.splitext(os.path.basename(tool.Drawing.get_document_uri(sheet)))[0]
os.makedirs(os.path.join(self.data_dir, "build", sheet_name), exist_ok=True)
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)
sheet_path = os.path.join(self.data_dir, "sheets", f"{sheet_name}.svg")
docs_dir = tool.Ifc.resolve_uri(os.path.join(bpy.context.scene.DocProperties.docs_dir, sheet_name))
os.makedirs(docs_dir, exist_ok=True)
ET.register_namespace("", "http://www.w3.org/2000/svg")
ET.register_namespace("xlink", "http://www.w3.org/1999/xlink")
@@ -191,9 +208,13 @@ class SheetBuilder:
self.build_drawings(root, sheet)
self.build_schedules(root)
with open(os.path.join(self.data_dir, "build", sheet_name, f"{sheet_name}.svg"), "wb") as output:
output_filename = os.path.join(docs_dir, f"{sheet_name}.svg")
with open(output_filename, "wb") as output:
tree.write(output)
return output_filename
def build_titleblock(self, root, sheet):
titleblock = root.findall('{http://www.w3.org/2000/svg}g[@data-type="titleblock"]')[0]
image = titleblock.findall("{http://www.w3.org/2000/svg}image")[0]
@@ -233,7 +254,7 @@ class SheetBuilder:
if background is not None:
background_path = os.path.join(self.data_dir, "sheets", self.get_href(background))
copy(background_path, os.path.join(self.data_dir, "build", sheet_name))
shutil.copy(background_path, os.path.join(self.data_dir, "build", sheet_name))
if view_title is not None:
foreground_path = self.get_href(foreground)
@@ -298,7 +319,7 @@ class SheetBuilder:
self.defs.append(clip_path)
svg_path = self.get_href(image)
with open(os.path.join(self.data_dir, "sheets", svg_path), "r") as template:
with open(os.path.join(self.sheet_dir, svg_path), "r") as template:
embedded = ET.fromstring(pystache.render(template.read(), data))
# viewBox should not be nested
embedded.attrib["viewBox"] = ""
@@ -21,6 +21,7 @@ import re
import bpy
import math
import bmesh
import shutil
import pystache
import mathutils
import xml.etree.ElementTree as ET
@@ -65,10 +66,9 @@ class SvgWriter:
self.scale = 1 / 100 # 1:100
self.camera_width = None
self.camera_height = None
self.related_paths = None
self.resource_paths = {}
def create_blank_svg(self, output_path):
self.define_related_paths() # making sure all paths are defined
self.calculate_scale()
self.svg = svgwrite.Drawing(
output_path,
@@ -94,26 +94,19 @@ class SvgWriter:
)
return self
def define_related_paths(self, **related_paths):
if not self.related_paths:
self.related_paths = {}
if not related_paths:
related_paths = {
"Stylesheet": os.path.join(self.data_dir, "styles", f"default.css"),
"Markers": os.path.join(self.data_dir, "templates", "markers.svg"),
"Symbols": os.path.join(self.data_dir, "templates", "symbols.svg"),
"Patterns": os.path.join(self.data_dir, "templates", "patterns.svg"),
}
for path_name in list(related_paths.keys()):
if path_name in self.related_paths:
del related_paths[path_name]
for path_name in related_paths:
uri = related_paths[path_name]
custom_path = tool.Ifc.resolve_uri(uri)
if custom_path:
self.related_paths[path_name] = custom_path
def setup_drawing_resource_paths(self, element):
pset = ifcopenshell.util.element.get_pset(element, "EPset_Drawing")
for resource in ("Stylesheet", "Markers", "Symbols", "Patterns"):
resource_path = pset.get(resource)
if not resource_path:
continue
os.makedirs(os.path.dirname(resource_path), exist_ok=True)
if not os.path.exists(resource_path):
resource_basename = os.path.basename(resource_path)
ootb_resource = os.path.join(bpy.context.scene.BIMProperties.data_dir, "assets", resource_basename)
if os.path.exists(ootb_resource):
shutil.copy(ootb_resource, resource_path)
self.resource_paths[resource] = tool.Ifc.resolve_uri(resource_path)
def define_boilerplate(self):
self.add_stylesheet()
@@ -132,28 +125,36 @@ class SvgWriter:
self.height = self.raw_height * self.svg_scale
def add_stylesheet(self):
with open(self.related_paths["Stylesheet"], "r") as stylesheet:
if not self.resource_paths["Stylesheet"] or not os.path.exists(self.resource_paths["Stylesheet"]):
return
with open(self.resource_paths["Stylesheet"], "r") as stylesheet:
self.svg.defs.add(self.svg.style(stylesheet.read()))
def add_markers(self):
tree = ET.parse(self.related_paths["Markers"])
if not self.resource_paths["Markers"] or not os.path.exists(self.resource_paths["Markers"]):
return
tree = ET.parse(self.resource_paths["Markers"])
root = tree.getroot()
for child in root:
self.svg.defs.add(External(child))
def add_symbols(self):
tree = ET.parse(self.related_paths["Symbols"])
if not self.resource_paths["Symbols"] or not os.path.exists(self.resource_paths["Symbols"]):
return
tree = ET.parse(self.resource_paths["Symbols"])
root = tree.getroot()
for child in root:
self.svg.defs.add(External(child))
def find_xml_symbol_by_id(self, id):
tree = ET.parse(self.related_paths["Symbols"])
tree = ET.parse(self.resource_paths["Symbols"])
xml_symbol = tree.find(f'.//*[@id="{id}"]')
return External(xml_symbol) if xml_symbol else None
def add_patterns(self):
tree = ET.parse(self.related_paths["Patterns"])
if not self.resource_paths["Patterns"] or not os.path.exists(self.resource_paths["Patterns"]):
return
tree = ET.parse(self.resource_paths["Patterns"])
root = tree.getroot()
for child in root:
self.svg.defs.add(External(child))
+15
View File
@@ -182,6 +182,21 @@ 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, "drawings_dir")
row = self.layout.row(align=True)
row.prop(context.scene.DocProperties, "stylesheet_path")
row = self.layout.row(align=True)
row.prop(context.scene.DocProperties, "markers_path")
row = self.layout.row(align=True)
row.prop(context.scene.DocProperties, "symbols_path")
row = self.layout.row(align=True)
row.prop(context.scene.DocProperties, "patterns_path")
row = layout.row()
row.operator("bim.configure_visibility")
+25 -7
View File
@@ -72,6 +72,9 @@ def add_sheet(ifc, drawing, titleblock=None):
if ifc.get_schema() == "IFC2X3":
attributes["DocumentId"] = attributes["Identification"]
del attributes["Identification"]
# TODO: How does IFC2X3 store the location?
else:
attributes["Location"] = drawing.get_default_sheet_path(identification, "UNTITLED")
ifc.run("document.edit_information", information=sheet, attributes=attributes)
drawing.create_svg_sheet(sheet, titleblock)
drawing.import_sheets()
@@ -103,13 +106,13 @@ def disable_editing_schedules(drawing):
def add_schedule(ifc, drawing, uri=None):
schedule = ifc.run("document.add_information")
reference = ifc.run("document.add_reference", information=schedule)
name = drawing.get_path_filename(uri)
if ifc.get_schema() == "IFC2X3":
attributes = {"DocumentId": "X", "Name": "UNTITLED", "Scope": "SCHEDULE"}
ifc.run("document.edit_information", information=schedule, attributes=attributes)
ifc.run("document.edit_reference", reference=reference, attributes={"Location": uri})
attributes = {"DocumentId": "X", "Name": name, "Scope": "SCHEDULE"}
else:
attributes = {"Identification": "X", "Name": "UNTITLED", "Scope": "SCHEDULE", "Location": uri}
ifc.run("document.edit_information", information=schedule, attributes=attributes)
attributes = {"Identification": "X", "Name": name, "Scope": "SCHEDULE"}
ifc.run("document.edit_information", information=schedule, attributes=attributes)
ifc.run("document.edit_reference", reference=reference, attributes={"Location": uri})
drawing.import_schedules()
@@ -119,7 +122,7 @@ def remove_schedule(ifc, drawing, schedule=None):
def open_schedule(drawing, schedule=None):
drawing.open_spreadsheet(drawing.get_schedule_location(schedule))
drawing.open_spreadsheet(drawing.get_document_uri(schedule))
def update_schedule_name(ifc, drawing, schedule=None, name=None):
@@ -164,8 +167,22 @@ def add_drawing(ifc, collector, drawing, target_view=None, location_hint=None):
"HasLinework": True,
"HasAnnotation": True,
"GlobalReferencing": True,
"Stylesheet": drawing.get_default_drawing_resource_path("Stylesheet"),
"Markers": drawing.get_default_drawing_resource_path("Markers"),
"Symbols": drawing.get_default_drawing_resource_path("Symbols"),
"Patterns": drawing.get_default_drawing_resource_path("Patterns"),
},
)
information = ifc.run("document.add_information")
uri = drawing.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=element, document=reference)
drawing.import_drawings()
@@ -204,6 +221,7 @@ 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))
ifc.run("root.remove_product", product=drawing)
drawing_tool.import_drawings()
@@ -243,7 +261,7 @@ def add_annotation(ifc, collector, drawing_tool, drawing=None, object_type=None)
def build_schedule(drawing, schedule=None):
drawing.create_svg_schedule(schedule)
drawing.open_svg(drawing.get_document_uri(schedule))
drawing.open_svg(drawing.get_path_with_ext(drawing.get_document_uri(schedule), "svg"))
def sync_references(ifc, collector, drawing_tool, drawing=None):
+5 -1
View File
@@ -255,15 +255,19 @@ class Drawing:
def get_annotation_context(cls, target_view): pass
def get_assigned_product(cls, element): pass
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_sheet_path(cls, identification, name): pass
def get_document_uri(cls, document): 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_schedule_location(cls, schedule): 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
+46 -19
View File
@@ -128,13 +128,17 @@ class Drawing(blenderbim.core.tool.Drawing):
@classmethod
def create_svg_schedule(cls, schedule):
schedule_creator = scheduler.Scheduler()
schedule_creator.schedule(cls.get_schedule_location(schedule), cls.get_document_uri(schedule))
schedule_creator.schedule(
cls.get_document_uri(schedule), cls.get_path_with_ext(cls.get_document_uri(schedule), "svg")
)
@classmethod
def create_svg_sheet(cls, document, titleblock):
sheet_builder = sheeter.SheetBuilder()
sheet_builder.data_dir = bpy.context.scene.BIMProperties.data_dir
sheet_builder.create(cls.get_document_uri(document), titleblock)
uri = cls.get_document_uri(document)
sheet_builder.create(uri, titleblock)
return uri
@classmethod
def delete_collection(cls, collection):
@@ -252,17 +256,28 @@ class Drawing(blenderbim.core.tool.Drawing):
@classmethod
def get_document_uri(cls, document):
if hasattr(document, "Identification"):
name = document.Identification or "X"
else:
name = document.DocumentId or "X"
name += " - " + (document.Name or "Unnamed")
if not hasattr(document, "Scope"):
return
if document.Scope == "DOCUMENTATION":
return os.path.join(bpy.context.scene.BIMProperties.data_dir, "sheets", name + ".svg")
elif document.Scope == "SCHEDULE":
return os.path.join(bpy.context.scene.BIMProperties.data_dir, "schedules", name + ".svg")
if getattr(document, "Location", None):
ifc_path = tool.Ifc.get_path()
if os.path.isfile(ifc_path):
ifc_path = os.path.dirname(ifc_path)
return os.path.abspath(os.path.join(ifc_path, document.Location))
if document.is_a("IfcDocumentInformation"):
if tool.Ifc.get_schema() == "IFC2X3":
references = document.DocumentReferences
else:
references = document.HasDocumentReferences
for reference in references:
location = cls.get_document_uri(reference)
if location:
return location
@classmethod
def get_path_filename(cls, path):
return os.path.splitext(os.path.basename(path))[0]
@classmethod
def get_path_with_ext(cls, path, ext):
return os.path.splitext(path)[0] + f".{ext}"
@classmethod
def get_drawing_collection(cls, drawing):
@@ -276,6 +291,12 @@ class Drawing(blenderbim.core.tool.Drawing):
if rel.is_a("IfcRelAssignsToGroup"):
return rel.RelatingGroup
@classmethod
def get_drawing_document(cls, drawing):
for rel in drawing.HasAssociations:
if rel.is_a("IfcRelAssociatesDocument"):
return rel.RelatingDocument
@classmethod
def get_drawing_references(cls, drawing):
results = set()
@@ -305,12 +326,6 @@ class Drawing(blenderbim.core.tool.Drawing):
def get_name(cls, element):
return element.Name
@classmethod
def get_schedule_location(cls, schedule):
if tool.Ifc.get_schema() == "IFC2X3":
return schedule.DocumentReferences[0].Location
return schedule.Location
@classmethod
def generate_drawing_matrix(cls, target_view, location_hint):
x = 0 if location_hint == 0 else bpy.context.scene.cursor.matrix[0][3]
@@ -721,6 +736,18 @@ class Drawing(blenderbim.core.tool.Drawing):
return location_hint + " " + target_view.split("_")[0]
return target_view
@classmethod
def get_default_sheet_path(cls, identification, name):
return os.path.join(bpy.context.scene.DocProperties.sheets_dir, f"{identification} - {name}.svg")
@classmethod
def get_default_drawing_path(cls, name):
return os.path.join(bpy.context.scene.DocProperties.drawings_dir, f"{name}.svg")
@classmethod
def get_default_drawing_resource_path(cls, resource):
return getattr(bpy.context.scene.DocProperties, f"{resource.lower()}_path") or None
@classmethod
def get_potential_reference_elements(cls, drawing):
elements = []
+4 -1
View File
@@ -106,7 +106,10 @@ class Ifc(blenderbim.core.tool.Ifc):
@classmethod
def resolve_uri(cls, uri):
return uri if not uri or os.path.isabs(uri) else os.path.join(os.path.dirname(cls.get_path()), 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 unlink(cls, element=None, obj=None):
+24 -3
View File
@@ -87,10 +87,11 @@ class TestAddSheet:
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")
ifc.run(
"document.edit_information",
information="sheet",
attributes={"Identification": "u_identification", "Name": "UNTITLED", "Scope": "DOCUMENTATION"},
attributes={"Identification": "u_identification", "Name": "UNTITLED", "Scope": "DOCUMENTATION", "Location": "uri"},
).should_be_called()
drawing.create_svg_sheet("sheet", "titleblock").should_be_called()
drawing.import_sheets().should_be_called()
@@ -141,18 +142,21 @@ class TestDisableEditingSchedules:
class TestAddSchedule:
def test_run(self, ifc, drawing):
ifc.run("document.add_information").should_be_called().will_return("schedule")
drawing.get_path_filename("uri").should_be_called().will_return("UNTITLED")
ifc.run("document.add_reference", information="schedule").should_be_called().will_return("reference")
ifc.get_schema().should_be_called().will_return("IFC4")
ifc.run(
"document.edit_information",
information="schedule",
attributes={"Identification": "X", "Name": "UNTITLED", "Scope": "SCHEDULE", "Location": "uri"},
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")
def test_using_a_document_id_in_ifc2x3(self, ifc, drawing):
ifc.run("document.add_information").should_be_called().will_return("schedule")
drawing.get_path_filename("uri").should_be_called().will_return("UNTITLED")
ifc.run("document.add_reference", information="schedule").should_be_called().will_return("reference")
ifc.get_schema().should_be_called().will_return("IFC2X3")
ifc.run(
@@ -174,7 +178,7 @@ class TestRemoveSchedule:
class TestOpenSchedule:
def test_run(self, drawing):
drawing.get_schedule_location("schedule").should_be_called().will_return("uri")
drawing.get_document_uri("schedule").should_be_called().will_return("uri")
drawing.open_spreadsheet("uri").should_be_called()
subject.open_schedule(drawing, schedule="schedule")
@@ -225,6 +229,10 @@ class TestAddDrawing:
ifc.run("group.assign_group", group="group", products=["element"]).should_be_called()
collector.assign("obj").should_be_called()
ifc.run("pset.add_pset", product="element", name="EPset_Drawing").should_be_called().will_return("pset")
drawing.get_default_drawing_resource_path("Stylesheet").should_be_called().will_return("stylesheet.css")
drawing.get_default_drawing_resource_path("Markers").should_be_called().will_return("markers.svg")
drawing.get_default_drawing_resource_path("Symbols").should_be_called().will_return("symbols.svg")
drawing.get_default_drawing_resource_path("Patterns").should_be_called().will_return("patterns.svg")
ifc.run(
"pset.edit_pset",
pset="pset",
@@ -236,8 +244,19 @@ class TestAddDrawing:
"HasLinework": True,
"HasAnnotation": True,
"GlobalReferencing": True,
"Stylesheet": "stylesheet.css",
"Markers": "markers.svg",
"Symbols": "symbols.svg",
"Patterns": "patterns.svg",
},
).should_be_called()
drawing.get_default_drawing_path("name").should_be_called().will_return("uri")
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_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()
subject.add_drawing(ifc, collector, drawing, target_view="target_view", location_hint="location_hint")
@@ -277,6 +296,8 @@ 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")
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()
subject.remove_drawing(ifc, drawing, drawing="drawing")
+34 -37
View File
@@ -61,9 +61,15 @@ class TestCreateCamera(NewFile):
class TestCreateSvgSheet(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
document = ifc.createIfcDocumentInformation(Identification="X", Name="FOOBAR", Scope="DOCUMENTATION")
subject.create_svg_sheet(document, "A1")
assert os.path.isfile(os.path.join(bpy.context.scene.BIMProperties.data_dir, "sheets", "X - FOOBAR.svg"))
document = ifc.createIfcDocumentInformation(
Identification="X",
Name="FOOBAR",
Scope="DOCUMENTATION",
Location=os.path.join(bpy.context.scene.BIMProperties.data_dir, "cache", "X - FOOBAR.svg"),
)
uri = subject.create_svg_sheet(document, "A1")
assert uri.endswith(".svg")
assert os.path.isfile(uri)
class TestDeleteCollection(NewFile):
@@ -204,11 +210,13 @@ class TestEnsureUniqueIdentification(NewFile):
class TestExportTextLiteralAttributes(NewFile):
def test_run(self):
TestImportTextAttributes().test_run()
assert subject.export_text_literal_attributes(bpy.data.objects.get("Object")) == [{
"Literal": "Literal",
"Path": "RIGHT",
"BoxAlignment": "bottom-left",
}]
assert subject.export_text_literal_attributes(bpy.data.objects.get("Object")) == [
{
"Literal": "Literal",
"Path": "RIGHT",
"BoxAlignment": "bottom-left",
}
]
class TestGetAnnotationContext(NewFile):
@@ -236,23 +244,29 @@ class TestGetBodyContext(NewFile):
class TestGetDocumentUri(NewFile):
def test_get_sheet_uri(self):
def test_run(self):
ifc = ifcopenshell.file()
document = ifc.createIfcDocumentInformation(
Identification="X", Name="FOOBAR", Scope="DOCUMENTATION", 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")
result = subject.get_document_uri(document)
assert result == os.path.join(bpy.context.scene.BIMProperties.data_dir, "sheets", "X - FOOBAR.svg")
def test_get_schedule_uri(self):
ifc = ifcopenshell.file()
document = ifc.createIfcDocumentInformation(Identification="X", Name="FOOBAR", Scope="SCHEDULE")
result = subject.get_document_uri(document)
assert result == os.path.join(bpy.context.scene.BIMProperties.data_dir, "schedules", "X - FOOBAR.svg")
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"))
def test_run_ifc2x3(self):
ifc = ifcopenshell.file(schema="IFC2X3")
document = ifc.createIfcDocumentInformation(DocumentId="X", Name="FOOBAR", Scope="DOCUMENTATION")
result = subject.get_document_uri(document)
assert result == os.path.join(bpy.context.scene.BIMProperties.data_dir, "sheets", "X - FOOBAR.svg")
tool.Ifc.set(ifc)
reference = ifc.createIfcDocumentReference(Location="Location")
document = ifc.createIfcDocumentInformation(
DocumentId="X", Name="FOOBAR", Scope="DOCUMENTATION", 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"))
class TestGetDrawingCollection(NewFile):
@@ -311,23 +325,6 @@ class TestGetName(NewFile):
assert subject.get_name(ifc.createIfcWall(Name="Foobar")) == "Foobar"
class TestGetScheduleLocation(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
schedule = ifcopenshell.api.run("document.add_information", ifc)
schedule.Location = "uri"
reference = ifcopenshell.api.run("document.add_reference", ifc, information=schedule)
subject.get_schedule_location(schedule) == "uri"
def test_run_ifc2x3(self):
ifc = ifcopenshell.file(schema="IFC2X3")
tool.Ifc.set(ifc)
reference = ifc.createIfcDocumentReference(Location="uri")
schedule = ifc.createIfcDocumentInformation(DocumentReferences=[reference])
subject.get_schedule_location(schedule) == "uri"
class TestGenerateDrawingMatrix(NewFile):
def test_returning_the_origin_as_a_fallback(self):
assert subject.generate_drawing_matrix("PLAN_VIEW", None) == mathutils.Matrix()