Merge branch 'v0.7.0' of https://github.com/IfcOpenShell/IfcOpenShell into pr-daily-builds

This commit is contained in:
krande
2022-04-16 17:02:10 +02:00
9 changed files with 177 additions and 61 deletions
@@ -0,0 +1,16 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION((),'2;1');
FILE_NAME('EPset_Drawing.ifc','2020-01-01T00:00:00',(),(),'EPset_Drawing','EPset_Drawing',$);
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4,#5,#6,#7));
#2=IFCSIMPLEPROPERTYTEMPLATE('23JavTMk98ZxXhrUEnjAcf',$,'TargetView','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#3=IFCSIMPLEPROPERTYTEMPLATE('1yVWUt5H9DAOuu0OaMMLpe',$,'Scale','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#4=IFCSIMPLEPROPERTYTEMPLATE('3gsuPBtU93b8f0gg1pjkq6',$,'HumanScale','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#5=IFCSIMPLEPROPERTYTEMPLATE('0AK5C2UpL4$eaac2LszAx$',$,'HasUnderlay','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#6=IFCSIMPLEPROPERTYTEMPLATE('2j2ZEZR8X5tONm7kli5hM6',$,'HasLinework','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#7=IFCSIMPLEPROPERTYTEMPLATE('1ttChRysH9UuEX2FeMj5Hu',$,'HasAnnotation','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
ENDSEC;
END-ISO-10303-21;
@@ -23,10 +23,19 @@ text { fill: black; stroke: none; }
.cut { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; fill-rule: evenodd; } .cut { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; fill-rule: evenodd; }
.projection { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; } .projection { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
.annotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; } .annotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
.hidden { stroke-dasharray: 3, 2; }
.solid {} .solid {}
.leader { marker-end: url(#leader-marker); } .IfcAnnotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
.stair { marker-start: url(#stair-marker-start); marker-end: url(#stair-marker-end); } .PredefinedType-MISC { fill: red; stroke: blue; stroke-width: 1; }
.PredefinedType-GRID { marker-start: url(#grid-marker); marker-end: url(#grid-marker); }
.PredefinedType-SECTIONLEVEL { marker-start: url(#section-level-marker); stroke-dasharray: 12.5, 3, 3, 3; }
.PredefinedType-PLANLEVEL { marker-end: url(#plan-level-marker); }
.PredefinedType-DIMENSION { marker-start: url(#dimension-marker-start); marker-end: url(#dimension-marker-end); }
.PredefinedType-HIDDENLINE { stroke-dasharray: 3, 2; }
.PredefinedType-STAIR { marker-start: url(#stair-marker-start); marker-end: url(#stair-marker-end); }
.PredefinedType-BOUNDARY { fill: none; stroke: red; stroke-width: 1; stroke-dasharray: 12, 4, 3, 4, 3, 4; }
.PredefinedType-TEXT { fill: black; stroke: none; }
path.PredefinedType-TEXTLEADER { marker-end: url(#leader-marker); }
text.PredefinedType-TEXTLEADER { fill: black; stroke: none; }
.break { fill: white; } .break { fill: white; }
.breakline { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; marker-mid: url(#breakline-marker); } .breakline { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; marker-mid: url(#breakline-marker); }
.material-blank { fill: white; } .material-blank { fill: white; }
@@ -44,5 +53,4 @@ text { fill: black; stroke: none; }
.material-honeycomb { fill: url(#honeycomb); } .material-honeycomb { fill: url(#honeycomb); }
.material-sand { fill: url(#sand); } .material-sand { fill: url(#sand); }
.material-concrete { fill: url(#concrete); stroke-width: 0.5; } .material-concrete { fill: url(#concrete); stroke-width: 0.5; }
.material-boundary { fill: none; stroke: red; stroke-width: 1; stroke-dasharray: 12,4,3,4,3,4; }
.IfcSpace { fill: none; stroke: none; } .IfcSpace { fill: none; stroke: none; }
+7 -7
View File
@@ -157,12 +157,12 @@ def loadIfcStore(scene):
if not IfcStore.get_file(): if not IfcStore.get_file():
return return
IfcStore.get_schema() IfcStore.get_schema()
IfcStore.reload_linked_elements() IfcStore.relink_all_objects()
@persistent @persistent
def undo_pre(scene): def undo_pre(scene):
IfcStore.update_undo_redo_stack_objects() IfcStore.track_undo_redo_stack_object_map()
@persistent @persistent
@@ -171,13 +171,13 @@ def undo_post(scene):
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
IfcStore.undo() IfcStore.undo()
purge_module_data() purge_module_data()
IfcStore.update_undo_redo_stack_objects() IfcStore.track_undo_redo_stack_selected_objects()
IfcStore.reload_linked_elements(objects=[bpy.data.objects.get(o) for o in IfcStore.undo_redo_stack_objects]) IfcStore.reload_undo_redo_stack_objects()
@persistent @persistent
def redo_pre(scene): def redo_pre(scene):
IfcStore.update_undo_redo_stack_objects() IfcStore.track_undo_redo_stack_object_map()
@persistent @persistent
@@ -186,8 +186,8 @@ def redo_post(scene):
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
IfcStore.redo() IfcStore.redo()
purge_module_data() purge_module_data()
IfcStore.update_undo_redo_stack_objects() IfcStore.track_undo_redo_stack_selected_objects()
IfcStore.reload_linked_elements(objects=[bpy.data.objects.get(o) for o in IfcStore.undo_redo_stack_objects]) IfcStore.reload_undo_redo_stack_objects()
@persistent @persistent
+68 -7
View File
@@ -43,6 +43,7 @@ class IfcStore:
library_file = None library_file = None
element_listeners = set() element_listeners = set()
undo_redo_stack_objects = set() undo_redo_stack_objects = set()
undo_redo_stack_object_names = {}
current_transaction = "" current_transaction = ""
last_transaction = "" last_transaction = ""
history = [] history = []
@@ -147,14 +148,32 @@ class IfcStore:
IfcStore.element_listeners.add(callback) IfcStore.element_listeners.add(callback)
@staticmethod @staticmethod
def update_undo_redo_stack_objects(): def track_undo_redo_stack_object_map():
"""Keeps track of currently mapped object names, typically during undo and redo
When any Blender object is stored outside a Blender PointerProperty, such as
in a regular Python list, there is the likely probability that the object
will be invalidated when undo or redo occurs. Object invalidation seems to
occur whenever an object is affected during an operation.
For example, if an operator deletes a modifier on o1, then o1 will be invalidated.
"""
for key, value in IfcStore.id_map.items():
try:
IfcStore.undo_redo_stack_object_names[key] = value.name
except:
continue
@staticmethod
def track_undo_redo_stack_selected_objects():
"""Keeps track of selected object names, typically during undo and redo """Keeps track of selected object names, typically during undo and redo
When any Blender object is stored outside a Blender PointerProperty, such as When any Blender object is stored outside a Blender PointerProperty, such as
in a regular Python list, there is the likely probability that the object in a regular Python list, there is the likely probability that the object
will be invalidated when undo or redo occurs. Object invalidation seems to will be invalidated when undo or redo occurs. Object invalidation seems to
only occur for selected objects either pre/post undo/redo event, including occur for selected objects either pre/post undo/redo event, including
selected objects for consecutive undo/redos, and all children. selected objects for consecutive undo/redos, and all children. This is
important because selected objects are often deleted from the scene.
So if I first select o1, then o2, then o3, then press undo, o3 will be So if I first select o1, then o2, then o3, then press undo, o3 will be
invalidated. If instead I press undo twice, o3 and o2 will be invalidated. invalidated. If instead I press undo twice, o3 and o2 will be invalidated.
@@ -169,14 +188,23 @@ class IfcStore:
IfcStore.undo_redo_stack_objects |= objects IfcStore.undo_redo_stack_objects |= objects
@staticmethod @staticmethod
def reload_linked_elements(objects=None): def reload_undo_redo_stack_objects():
"""Reloads any invalidated objects after undo or redo
After an undo or redo operation, objects may have been invalidated in
our id_map and guid_map. Invalidated objects are typically those that
have been manipulated or deleted. This checks the cache of mapped and
selected objects prior to the operation and ensures that if the object
is invalidated, they are reloaded based on the object name that was
tracked prior to the undo / redo.
"""
file = IfcStore.get_file() file = IfcStore.get_file()
if not file: if not file:
return return
if objects is None:
objects = bpy.data.objects
for obj in objects: # First, reload objects that were selected or active
for name in IfcStore.undo_redo_stack_objects:
obj = bpy.data.objects.get(name)
if not obj: if not obj:
continue continue
if not obj.BIMObjectProperties.ifc_definition_id: if not obj.BIMObjectProperties.ifc_definition_id:
@@ -187,6 +215,38 @@ class IfcStore:
data["guid"] = element.GlobalId data["guid"] = element.GlobalId
IfcStore.commit_link_element(data) IfcStore.commit_link_element(data)
# Scan for any straggling invalidated objects which were indirectly affected and reload them too.
for key, value in IfcStore.id_map.items():
try:
value.name
except:
obj = bpy.data.objects.get(IfcStore.undo_redo_stack_object_names[key])
if not obj or not obj.BIMObjectProperties.ifc_definition_id:
continue
element = file.by_id(obj.BIMObjectProperties.ifc_definition_id)
data = {"id": element.id(), "obj": obj.name}
if hasattr(element, "GlobalId"):
data["guid"] = element.GlobalId
IfcStore.commit_link_element(data)
@staticmethod
def relink_all_objects():
file = IfcStore.get_file()
if not file:
return
for obj in bpy.data.objects:
if not obj:
continue
if not obj.BIMObjectProperties.ifc_definition_id:
continue
element = file.by_id(obj.BIMObjectProperties.ifc_definition_id)
data = {"id": element.id(), "obj": obj.name}
if hasattr(element, "GlobalId"):
data["guid"] = element.GlobalId
IfcStore.commit_link_element(data)
@staticmethod @staticmethod
def delete_element(element): def delete_element(element):
IfcStore.deleted_ids.add(element.id()) IfcStore.deleted_ids.add(element.id())
@@ -312,6 +372,7 @@ class IfcStore:
@staticmethod @staticmethod
def begin_transaction(operator): def begin_transaction(operator):
IfcStore.undo_redo_stack_objects = set() IfcStore.undo_redo_stack_objects = set()
IfcStore.undo_redo_stack_object_names = {}
IfcStore.current_transaction = str(uuid.uuid4()) IfcStore.current_transaction = str(uuid.uuid4())
operator.transaction_key = IfcStore.current_transaction operator.transaction_key = IfcStore.current_transaction
@@ -1682,6 +1682,12 @@ class IfcImporter:
else: else:
camera.BIMCameraProperties.diagram_scale = "CUSTOM" camera.BIMCameraProperties.diagram_scale = "CUSTOM"
camera.BIMCameraProperties.custom_diagram_scale = pset["Scale"] camera.BIMCameraProperties.custom_diagram_scale = pset["Scale"]
if "HasUnderlay" in pset:
camera.BIMCameraProperties.has_underlay = pset["HasUnderlay"]
if "HasLinework" in pset:
camera.BIMCameraProperties.has_linework = pset["HasLinework"]
if "HasAnnotation" in pset:
camera.BIMCameraProperties.has_annotation = pset["HasAnnotation"]
return camera return camera
def create_curve(self, element, shape): def create_curve(self, element, shape):
@@ -119,9 +119,10 @@ class CreateDrawing(bpy.types.Operator):
start = time.time() start = time.time()
self.profile_code("Start drawing generation process") self.profile_code("Start drawing generation process")
self.props = context.scene.DocProperties self.props = context.scene.DocProperties
self.cprops = self.camera.data.BIMCameraProperties
self.drawing_name = self.file.by_id(self.camera.BIMObjectProperties.ifc_definition_id).Name self.drawing_name = self.file.by_id(self.camera.BIMObjectProperties.ifc_definition_id).Name
self.get_scale() self.get_scale()
if self.camera.data.BIMCameraProperties.update_representation(self.camera): if self.cprops.update_representation(self.camera):
bpy.ops.bim.update_representation(obj=self.camera.name, ifc_representation_class="") bpy.ops.bim.update_representation(obj=self.camera.name, ifc_representation_class="")
underlay_svg = self.generate_underlay(context) underlay_svg = self.generate_underlay(context)
self.profile_code("Generate underlay") self.profile_code("Generate underlay")
@@ -186,12 +187,12 @@ class CreateDrawing(bpy.types.Operator):
return svg_path return svg_path
def generate_underlay(self, context): def generate_underlay(self, context):
if not self.props.has_underlay: if not self.cprops.has_underlay:
return return
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-underlay.svg") svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-underlay.svg")
context.scene.render.filepath = svg_path[0:-4] + ".png" context.scene.render.filepath = svg_path[0:-4] + ".png"
drawing_style = context.scene.DocProperties.drawing_styles[ drawing_style = context.scene.DocProperties.drawing_styles[
self.camera.data.BIMCameraProperties.active_drawing_style_index self.cprops.active_drawing_style_index
] ]
if drawing_style.render_type == "DEFAULT": if drawing_style.render_type == "DEFAULT":
@@ -246,7 +247,7 @@ class CreateDrawing(bpy.types.Operator):
return svg_path return svg_path
def generate_linework(self, context): def generate_linework(self, context):
if not self.props.has_linework: if not self.cprops.has_linework:
return return
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-linework.svg") svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-linework.svg")
if os.path.isfile(svg_path) and self.props.should_use_linework_cache: if os.path.isfile(svg_path) and self.props.should_use_linework_cache:
@@ -410,7 +411,7 @@ class CreateDrawing(bpy.types.Operator):
return re.sub("[^0-9a-zA-Z]+", "", name) return re.sub("[^0-9a-zA-Z]+", "", name)
def generate_annotation(self, context): def generate_annotation(self, context):
if not self.props.has_annotation: if not self.cprops.has_annotation:
return return
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-annotation.svg") svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-annotation.svg")
if os.path.isfile(svg_path) and self.props.should_use_annotation_cache: if os.path.isfile(svg_path) and self.props.should_use_annotation_cache:
@@ -420,7 +421,7 @@ class CreateDrawing(bpy.types.Operator):
svg_writer = svgwriter.SvgWriter() svg_writer = svgwriter.SvgWriter()
drawing_style = context.scene.DocProperties.drawing_styles[ drawing_style = context.scene.DocProperties.drawing_styles[
camera.data.BIMCameraProperties.active_drawing_style_index self.cprops.active_drawing_style_index
] ]
render = context.scene.render render = context.scene.render
@@ -473,7 +474,7 @@ class CreateDrawing(bpy.types.Operator):
svg_writer.annotations["section_level_obj"] = obj svg_writer.annotations["section_level_obj"] = obj
elif element.ObjectType == "TEXT": elif element.ObjectType == "TEXT":
svg_writer.annotations.setdefault("text_objs", []).append(obj) svg_writer.annotations.setdefault("text_objs", []).append(obj)
elif element.ObjectType == "MISC": else:
svg_writer.annotations.setdefault("misc_objs", []).append(obj) svg_writer.annotations.setdefault("misc_objs", []).append(obj)
svg_writer.annotations["attributes"] = [a.name for a in drawing_style.attributes] svg_writer.annotations["attributes"] = [a.name for a in drawing_style.attributes]
@@ -153,6 +153,30 @@ def update_drawing_name(self, context):
core.update_drawing_name(tool.Ifc, tool.Drawing, drawing=drawing, name=self.name) core.update_drawing_name(tool.Ifc, tool.Drawing, drawing=drawing, name=self.name)
def update_has_underlay(self, context):
update_layer(self, context, "HasUnderlay", self.has_underlay)
def update_has_linework(self, context):
update_layer(self, context, "HasLinework", self.has_linework)
def update_has_annotation(self, context):
update_layer(self, context, "HasAnnotation", self.has_annotation)
def update_layer(self, context, name, value):
element = tool.Ifc.get_entity(context.active_object)
if not element:
return
pset = ifcopenshell.util.element.get_psets(element).get("EPset_Drawing")
if pset:
pset = tool.Ifc.get().by_id(pset["id"])
else:
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="EPset_Drawing")
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={name: value})
def getTitleblocks(self, context): def getTitleblocks(self, context):
global titleblocks_enum global titleblocks_enum
if len(titleblocks_enum) < 1: if len(titleblocks_enum) < 1:
@@ -276,9 +300,6 @@ class RasterStyleProperty(enum.Enum):
class DocProperties(PropertyGroup): class DocProperties(PropertyGroup):
has_underlay: BoolProperty(name="Underlay", default=False)
has_linework: BoolProperty(name="Linework", default=True)
has_annotation: BoolProperty(name="Annotation", default=True)
should_use_underlay_cache: BoolProperty(name="Use Underlay Cache", default=False) should_use_underlay_cache: BoolProperty(name="Use Underlay Cache", default=False)
should_use_linework_cache: BoolProperty(name="Use Linework Cache", default=False) should_use_linework_cache: BoolProperty(name="Use Linework Cache", default=False)
should_use_annotation_cache: BoolProperty(name="Use Annotation Cache", default=False) should_use_annotation_cache: BoolProperty(name="Use Annotation Cache", default=False)
@@ -320,6 +341,9 @@ class DocProperties(PropertyGroup):
class BIMCameraProperties(PropertyGroup): class BIMCameraProperties(PropertyGroup):
has_underlay: BoolProperty(name="Underlay", default=False, update=update_has_underlay)
has_linework: BoolProperty(name="Linework", default=True, update=update_has_linework)
has_annotation: BoolProperty(name="Annotation", default=True, update=update_has_annotation)
representation: StringProperty(name="Representation") representation: StringProperty(name="Representation")
view_name: StringProperty(name="View Name") view_name: StringProperty(name="View Name")
diagram_scale: EnumProperty(items=get_diagram_scales, name="Drawing Scale", update=update_diagram_scale) diagram_scale: EnumProperty(items=get_diagram_scales, name="Drawing Scale", update=update_diagram_scale)
@@ -24,6 +24,7 @@ import pystache
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
import svgwrite import svgwrite
import ifcopenshell import ifcopenshell
import ifcopenshell.util.element
import ifcopenshell.util.representation import ifcopenshell.util.representation
import blenderbim.tool as tool import blenderbim.tool as tool
import blenderbim.bim.module.drawing.helper as helper import blenderbim.bim.module.drawing.helper as helper
@@ -153,8 +154,8 @@ class SvgWriter:
for grid_obj in self.annotations.get("grid_objs", []): for grid_obj in self.annotations.get("grid_objs", []):
matrix_world = grid_obj.matrix_world matrix_world = grid_obj.matrix_world
classes = self.get_attribute_classes(grid_obj)
for edge in grid_obj.data.edges: for edge in grid_obj.data.edges:
classes = ["annotation", "grid"]
v0_global = matrix_world @ grid_obj.data.vertices[edge.vertices[0]].co.xyz v0_global = matrix_world @ grid_obj.data.vertices[edge.vertices[0]].co.xyz
v1_global = matrix_world @ grid_obj.data.vertices[edge.vertices[1]].co.xyz v1_global = matrix_world @ grid_obj.data.vertices[edge.vertices[1]].co.xyz
v0 = self.project_point_onto_camera(v0_global) v0 = self.project_point_onto_camera(v0_global)
@@ -167,8 +168,6 @@ class SvgWriter:
start=tuple(start * self.scale), end=tuple(end * self.scale), class_=" ".join(classes) start=tuple(start * self.scale), end=tuple(end * self.scale), class_=" ".join(classes)
) )
) )
line["marker-start"] = "url(#grid-marker)"
line["marker-end"] = "url(#grid-marker)"
line["stroke-dasharray"] = "12.5, 3, 3, 3" line["stroke-dasharray"] = "12.5, 3, 3, 3"
axis_tag = tool.Ifc.get_entity(grid_obj).Name axis_tag = tool.Ifc.get_entity(grid_obj).Name
self.svg.add( self.svg.add(
@@ -201,13 +200,13 @@ class SvgWriter:
self.draw_ifc_annotation() self.draw_ifc_annotation()
for obj in self.annotations.get("misc_objs", []): for obj in self.annotations.get("misc_objs", []):
self.draw_misc_annotation(obj, ["IfcAnnotation"]) self.draw_misc_annotation(obj)
for obj_data in self.annotations.get("hidden_objs", []): for obj_data in self.annotations.get("hidden_objs", []):
self.draw_line_annotation(obj_data, ["hidden"]) self.draw_line_annotation(obj_data)
for obj_data in self.annotations.get("solid_objs", []): for obj_data in self.annotations.get("solid_objs", []):
self.draw_line_annotation(obj_data, ["solid"]) self.draw_line_annotation(obj_data)
self.draw_leader_annotations() self.draw_leader_annotations()
@@ -216,8 +215,8 @@ class SvgWriter:
if self.annotations.get("section_level_obj"): if self.annotations.get("section_level_obj"):
matrix_world = self.annotations["section_level_obj"].matrix_world matrix_world = self.annotations["section_level_obj"].matrix_world
classes = self.get_attribute_classes(self.annotations["section_level_obj"])
for spline in self.annotations["section_level_obj"].data.splines: for spline in self.annotations["section_level_obj"].data.splines:
classes = ["annotation", "section-level"]
points = self.get_spline_points(spline) points = self.get_spline_points(spline)
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points] projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = " ".join( d = " ".join(
@@ -228,8 +227,6 @@ class SvgWriter:
) )
d = "M{}".format(d[1:]) d = "M{}".format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes))) path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
path["marker-start"] = "url(#section-level-marker)"
path["stroke-dasharray"] = "12.5, 3, 3, 3"
text_position = Vector( text_position = Vector(
( (
(x_offset + projected_points[0].x) * self.scale, (x_offset + projected_points[0].x) * self.scale,
@@ -258,8 +255,8 @@ class SvgWriter:
if self.annotations.get("stair_obj"): if self.annotations.get("stair_obj"):
matrix_world = self.annotations["stair_obj"].matrix_world matrix_world = self.annotations["stair_obj"].matrix_world
classes = self.get_attribute_classes(self.annotations["stair_obj"])
for spline in self.annotations["stair_obj"].data.splines: for spline in self.annotations["stair_obj"].data.splines:
classes = ["annotation", "stair"]
points = self.get_spline_points(spline) points = self.get_spline_points(spline)
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points] projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = " ".join( d = " ".join(
@@ -316,13 +313,13 @@ class SvgWriter:
) )
) )
def draw_misc_annotation(self, obj, classes): def draw_misc_annotation(self, obj):
# We have to decide whether this should come from Blender or from IFC. # We have to decide whether this should come from Blender or from IFC.
# For the moment, for convenience of experimenting with ideas, it comes # For the moment, for convenience of experimenting with ideas, it comes
# from Blender. In the future, it should probably come from IFC. # from Blender. In the future, it should probably come from IFC.
if not isinstance(obj.data, bpy.types.Mesh): if not isinstance(obj.data, bpy.types.Mesh):
return return
classes.extend(self.get_attribute_classes(obj)) classes = self.get_attribute_classes(obj)
if len(obj.data.polygons) == 0: if len(obj.data.polygons) == 0:
self.draw_edge_annotation(obj, classes) self.draw_edge_annotation(obj, classes)
return return
@@ -332,6 +329,7 @@ class SvgWriter:
for polygon in obj.data.polygons: for polygon in obj.data.polygons:
points = [obj.data.vertices[v] for v in polygon.vertices] points = [obj.data.vertices[v] for v in polygon.vertices]
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points] projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
projected_points.append(projected_points[0])
d = " ".join( d = " ".join(
[ [
"L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale) "L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
@@ -342,12 +340,12 @@ class SvgWriter:
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes))) path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
def get_attribute_classes(self, obj): def get_attribute_classes(self, obj):
classes = [obj.name.split("/")[0]] element = tool.Ifc.get_entity(obj)
for slot in obj.material_slots: global_id = "GlobalId-{}".format(element.GlobalId)
if slot.material: predefined_type = "PredefinedType-" + self.canonicalise_class_name(
classes.append("material-{}".format(re.sub("[^0-9a-zA-Z]+", "", slot.material.name))) str(ifcopenshell.util.element.get_predefined_type(element))
global_id = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId )
classes.append("globalid-{}".format(global_id)) classes = [global_id, element.is_a(), predefined_type]
for attribute in self.annotations.get("attributes", []): for attribute in self.annotations.get("attributes", []):
result = self.get_obj_value(obj, attribute) result = self.get_obj_value(obj, attribute)
if result: if result:
@@ -356,6 +354,9 @@ class SvgWriter:
) )
return classes return classes
def canonicalise_class_name(self, name):
return re.sub("[^0-9a-zA-Z]+", "", name)
def get_obj_value(self, obj, key): def get_obj_value(self, obj, key):
# This is a duplicate implementation of the IFC selector key in Blender # This is a duplicate implementation of the IFC selector key in Blender
# In the future if all this becomes purely IFC based this can be deleted # In the future if all this becomes purely IFC based this can be deleted
@@ -381,14 +382,14 @@ class SvgWriter:
if result: if result:
return result.string_value return result.string_value
def draw_line_annotation(self, obj_data, classes): def draw_line_annotation(self, obj_data):
# TODO: properly scope these offsets # TODO: properly scope these offsets
x_offset = self.raw_width / 2 x_offset = self.raw_width / 2
y_offset = self.raw_height / 2 y_offset = self.raw_height / 2
obj, data = obj_data obj, data = obj_data
classes.extend(["annotation"]) classes = self.get_attribute_classes(obj)
matrix_world = obj.matrix_world matrix_world = obj.matrix_world
if isinstance(data, bpy.types.Curve): if isinstance(data, bpy.types.Curve):
@@ -424,7 +425,7 @@ class SvgWriter:
def draw_leader_annotations(self): def draw_leader_annotations(self):
for obj in self.annotations.get("leader_objs", []): for obj in self.annotations.get("leader_objs", []):
self.draw_line_annotation((obj, obj.data), ["leader"]) self.draw_line_annotation((obj, obj.data))
spline = obj.data.splines[0] spline = obj.data.splines[0]
spline_points = spline.bezier_points if spline.bezier_points else spline.points spline_points = spline.bezier_points if spline.bezier_points else spline.points
if spline_points: if spline_points:
@@ -437,7 +438,7 @@ class SvgWriter:
x_offset = self.raw_width / 2 x_offset = self.raw_width / 2
y_offset = self.raw_height / 2 y_offset = self.raw_height / 2
self.draw_line_annotation((obj, obj.data), ["solid"]) self.draw_line_annotation((obj, obj.data))
v1 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, 0))) v1 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, 0)))
v2 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, -1))) v2 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, -1)))
@@ -632,8 +633,8 @@ class SvgWriter:
x_offset = self.raw_width / 2 x_offset = self.raw_width / 2
y_offset = self.raw_height / 2 y_offset = self.raw_height / 2
matrix_world = obj.matrix_world matrix_world = obj.matrix_world
classes = self.get_attribute_classes(obj)
for spline in obj.data.splines: for spline in obj.data.splines:
classes = ["annotation", "plan-level"]
points = self.get_spline_points(spline) points = self.get_spline_points(spline)
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points] projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = " ".join( d = " ".join(
@@ -644,7 +645,6 @@ class SvgWriter:
) )
d = "M{}".format(d[1:]) d = "M{}".format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes))) path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
path["marker-end"] = "url(#plan-level-marker)"
text_position = Vector( text_position = Vector(
( (
(x_offset + projected_points[0].x) * self.scale, (x_offset + projected_points[0].x) * self.scale,
@@ -676,6 +676,7 @@ class SvgWriter:
) )
def draw_dimension_annotations(self, dimension_obj, text_override=None): def draw_dimension_annotations(self, dimension_obj, text_override=None):
classes = self.get_attribute_classes(dimension_obj)
matrix_world = dimension_obj.matrix_world matrix_world = dimension_obj.matrix_world
for spline in dimension_obj.data.splines: for spline in dimension_obj.data.splines:
points = self.get_spline_points(spline) points = self.get_spline_points(spline)
@@ -684,7 +685,7 @@ class SvgWriter:
continue continue
v0_global = matrix_world @ points[i].co.xyz v0_global = matrix_world @ points[i].co.xyz
v1_global = matrix_world @ points[i + 1].co.xyz v1_global = matrix_world @ points[i + 1].co.xyz
self.draw_dimension_annotation(v0_global, v1_global, text_override) self.draw_dimension_annotation(v0_global, v1_global, classes, text_override)
def draw_measureit_arch_dimension_annotations(self): def draw_measureit_arch_dimension_annotations(self):
try: try:
@@ -694,10 +695,11 @@ class SvgWriter:
except: except:
return return
for coord in coords: for coord in coords:
self.draw_dimension_annotation(Vector(coord[0]), Vector(coord[1])) self.draw_dimension_annotation(
Vector(coord[0]), Vector(coord[1]), ["IfcAnnotation", "PredefinedType-DIMENSION"]
)
def draw_dimension_annotation(self, v0_global, v1_global, text_override=None): def draw_dimension_annotation(self, v0_global, v1_global, classes, text_override=None):
classes = ["annotation", "dimension"]
x_offset = self.raw_width / 2 x_offset = self.raw_width / 2
y_offset = self.raw_height / 2 y_offset = self.raw_height / 2
v0 = self.project_point_onto_camera(v0_global) v0 = self.project_point_onto_camera(v0_global)
@@ -719,8 +721,6 @@ class SvgWriter:
line = self.svg.add( line = self.svg.add(
self.svg.line(start=tuple(start * self.scale), end=tuple(end * self.scale), class_=" ".join(classes)) self.svg.line(start=tuple(start * self.scale), end=tuple(end * self.scale), class_=" ".join(classes))
) )
line["marker-start"] = "url(#dimension-marker-start)"
line["marker-end"] = "url(#dimension-marker-end)"
if text_override is not None: if text_override is not None:
text = text_override text = text_override
else: else:
@@ -47,13 +47,13 @@ class BIM_PT_camera(Panel):
col = layout.column(align=True) col = layout.column(align=True)
row = col.row(align=True) row = col.row(align=True)
row.prop(dprops, "has_underlay", icon="OUTLINER_OB_IMAGE") row.prop(props, "has_underlay", icon="OUTLINER_OB_IMAGE")
row.prop(dprops, "should_use_underlay_cache", text="", icon="FILE_REFRESH") row.prop(dprops, "should_use_underlay_cache", text="", icon="FILE_REFRESH")
row = col.row(align=True) row = col.row(align=True)
row.prop(dprops, "has_linework", icon="IMAGE_DATA") row.prop(props, "has_linework", icon="IMAGE_DATA")
row.prop(dprops, "should_use_linework_cache", text="", icon="FILE_REFRESH") row.prop(dprops, "should_use_linework_cache", text="", icon="FILE_REFRESH")
row = col.row(align=True) row = col.row(align=True)
row.prop(dprops, "has_annotation", icon="MOD_EDGESPLIT") row.prop(props, "has_annotation", icon="MOD_EDGESPLIT")
row.prop(dprops, "should_use_annotation_cache", text="", icon="FILE_REFRESH") row.prop(dprops, "should_use_annotation_cache", text="", icon="FILE_REFRESH")
row = layout.row() row = layout.row()