diff --git a/src/blenderbim/blenderbim/bim/data/pset/Psets_BBIM_Annotation.ifc b/src/blenderbim/blenderbim/bim/data/pset/Psets_BBIM_Annotation.ifc
index a466449dcc..1478bcfba2 100644
--- a/src/blenderbim/blenderbim/bim/data/pset/Psets_BBIM_Annotation.ifc
+++ b/src/blenderbim/blenderbim/bim/data/pset/Psets_BBIM_Annotation.ifc
@@ -5,14 +5,14 @@ FILE_NAME('Psets_BBIM_Annotation.ifc','2020-01-01T00:00:00',(),(),'Psets_BBIM_An
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
-#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4));
+#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation,IfcTypeProduct',(#2,#3,#4));
#2=IFCSIMPLEPROPERTYTEMPLATE('2P7JN79n96Q9pElZ83LKe4',$,'ZIndex','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
#3=IFCSIMPLEPROPERTYTEMPLATE('1Wpx_r2xj1_9w5JpI0QRJy',$,'Symbol','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#4=IFCSIMPLEPROPERTYTEMPLATE('3q0oxMUKP47vZ4jnyG$dDb',$,'Classes','Classes separarated by spaces that end up in classes for this element in svg. Can be used to specify the text font size: small - 1.8mm; regular - 2.5mm; large - 3.5mm; header - 5mm; title - 7mm. By default regular size is used.',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
-#5=IFCPROPERTYSETTEMPLATE('0iKwujnQL9IevVQato8f7Z',$,'BBIM_Batting','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#6,#7));
+#5=IFCPROPERTYSETTEMPLATE('0iKwujnQL9IevVQato8f7Z',$,'BBIM_Batting','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation,IfcTypeProduct',(#6,#7));
#6=IFCSIMPLEPROPERTYTEMPLATE('0t2LEesGT1QRQtrIZUAR8L',$,'Thickness','Batting thickness',.P_SINGLEVALUE.,'IfcPositiveLengthMeasure',$,$,$,$,$,.READWRITE.);
#7=IFCSIMPLEPROPERTYTEMPLATE('082PndS6v2kBOiJoSboMnh',$,'Reverse pattern direction','Reverse batting pattern (swap starting and ending points)',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
-#8=IFCPROPERTYSETTEMPLATE('1Dx2EiZnP67xotInXpwz90',$,'BBIM_Section','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#9,#10,#11,#12,#13));
+#8=IFCPROPERTYSETTEMPLATE('1Dx2EiZnP67xotInXpwz90',$,'BBIM_Section','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation,IfcTypeProduct',(#9,#10,#11,#12,#13));
#9=IFCSIMPLEPROPERTYTEMPLATE('2a_9s8spHDc9dZHtgg71XL',$,'ShowStartArrow','Display start arrow.',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#10=IFCSIMPLEPROPERTYTEMPLATE('3i6SH_GbT7zhKea$wVE56A',$,'StartArrowSymbol','Custom symbol for the start of the section marker arrow. Need to make sure it''s present in "symbols.svg".',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#11=IFCSIMPLEPROPERTYTEMPLATE('1DtsPn5a9FG8$zXHDDMavY',$,'ShowEndArrow','Display end arrow.',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/__init__.py b/src/blenderbim/blenderbim/bim/module/drawing/__init__.py
index 49e7f38939..d828f6dea5 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/__init__.py
@@ -17,7 +17,7 @@
# along with BlenderBIM Add-on. If not, see .
import bpy
-from . import ui, prop, operator, handler, gizmos
+from . import ui, prop, operator, handler, gizmos, workspace
classes = (
operator.ActivateDrawing,
@@ -80,6 +80,7 @@ classes = (
prop.Literal,
prop.BIMTextProperties,
prop.BIMAssignedProductProperties,
+ prop.BIMAnnotationProperties,
ui.BIM_PT_camera,
ui.BIM_PT_drawing_underlay,
ui.BIM_PT_annotation_utilities,
@@ -95,11 +96,15 @@ classes = (
gizmos.DimensionLabelGizmo,
gizmos.ExtrusionGuidesGizmo,
gizmos.ExtrusionWidget,
+ workspace.Hotkey,
)
def register():
+ if not bpy.app.background:
+ bpy.utils.register_tool(workspace.AnnotationTool, after={"bim.bim_tool"}, separator=True, group=True)
bpy.types.Scene.DocProperties = bpy.props.PointerProperty(type=prop.DocProperties)
+ bpy.types.Scene.BIMAnnotationProperties = bpy.props.PointerProperty(type=prop.BIMAnnotationProperties)
bpy.types.Camera.BIMCameraProperties = bpy.props.PointerProperty(type=prop.BIMCameraProperties)
bpy.types.Object.BIMAssignedProductProperties = bpy.props.PointerProperty(type=prop.BIMAssignedProductProperties)
bpy.types.Object.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties)
@@ -109,7 +114,10 @@ def register():
def unregister():
+ if not bpy.app.background:
+ bpy.utils.unregister_tool(workspace.AnnotationTool)
del bpy.types.Scene.DocProperties
+ del bpy.types.Scene.BIMAnnotationProperties
del bpy.types.Camera.BIMCameraProperties
del bpy.types.Object.BIMAssignedProductProperties
del bpy.types.Object.BIMTextProperties
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/annotation.py b/src/blenderbim/blenderbim/bim/module/drawing/annotation.py
index 2eaf8d353b..062cb4ec04 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/annotation.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/annotation.py
@@ -77,17 +77,20 @@ class Annotator:
co1, co2, _, _ = Annotator.get_placeholder_coords()
co1 = obj.matrix_world.inverted() @ co1
co2 = obj.matrix_world.inverted() @ co2
+
if isinstance(obj.data, bpy.types.Mesh):
obj.data.vertices.add(2)
obj.data.vertices[-2].co = co1
obj.data.vertices[-1].co = co2
obj.data.edges.add(1)
obj.data.edges[-1].vertices = (obj.data.vertices[-2].index, obj.data.vertices[-1].index)
+
if isinstance(obj.data, bpy.types.Curve):
polyline = obj.data.splines.new("POLY")
polyline.points.add(1)
polyline.points[-2].co = list(co1) + [1]
polyline.points[-1].co = list(co2) + [1]
+
return obj
@staticmethod
@@ -114,11 +117,13 @@ class Annotator:
matrix_world[1][3] = co1.y
matrix_world[2][3] = co1.z
collection = camera.users_collection[0]
+
if object_type == "TEXT":
obj = bpy.data.objects.new(object_type, None)
obj.matrix_world = matrix_world
collection.objects.link(obj)
return obj
+
elif object_type in ("TEXT_LEADER", "SECTION_LEVEL"):
data = bpy.data.curves.new(object_type, type="CURVE")
data.dimensions = "3D"
@@ -127,17 +132,20 @@ class Annotator:
obj.matrix_world = matrix_world
collection.objects.link(obj)
return obj
+
if object_type != "ANGLE":
for obj in collection.objects:
element = tool.Ifc.get_entity(obj)
if element and element.ObjectType == object_type and obj.type == object_type.upper():
return obj
+
if data_type == "mesh":
data = bpy.data.meshes.new(object_type)
elif data_type == "curve":
data = bpy.data.curves.new(object_type, type="CURVE")
data.dimensions = "3D"
data.resolution_u = 2
+
obj = bpy.data.objects.new(object_type, data)
obj.matrix_world = matrix_world
collection.objects.link(obj)
@@ -148,14 +156,13 @@ class Annotator:
if not camera:
camera = bpy.context.scene.camera
z_offset = camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
- if bpy.context.scene.render.resolution_x > bpy.context.scene.render.resolution_y:
- y = (
- camera.data.ortho_scale
- * (bpy.context.scene.render.resolution_y / bpy.context.scene.render.resolution_x)
- / 4
- )
- else:
- y = camera.data.ortho_scale / 4
+ y = camera.data.ortho_scale / 4
+
+ res_x = bpy.context.scene.render.resolution_x
+ res_y = bpy.context.scene.render.resolution_y
+ if res_x > res_y:
+ y *= res_y / res_x
+
y_offset = camera.matrix_world.to_quaternion() @ Vector((0, y, 0))
x_offset = camera.matrix_world.to_quaternion() @ Vector((y / 2, 0, 0))
return (
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/data.py b/src/blenderbim/blenderbim/bim/module/drawing/data.py
index d9b70cf9a4..2911449977 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/data.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/data.py
@@ -31,6 +31,7 @@ def refresh():
DrawingsData.is_loaded = False
DecoratorData.data = {}
DecoratorData.cut_cache = {}
+ AnnotationData.is_loaded = False
class ProductAssignmentsData:
@@ -220,7 +221,7 @@ class DecoratorData:
return result
element = tool.Ifc.get_entity(obj)
- if not element or not element.is_a("IfcAnnotation") or element.ObjectType not in ["TEXT", "TEXT_LEADER"]:
+ if not element or not tool.Drawing.is_annotation_object_type(element, ["TEXT", "TEXT_LEADER"]):
return None
props = obj.BIMTextProperties
@@ -276,3 +277,28 @@ class DecoratorData:
cls.data[obj.name] = dimension_style
return dimension_style
+
+
+class AnnotationData:
+ data = {}
+ is_loaded = False
+
+ @classmethod
+ def load(cls):
+ cls.is_loaded = True
+ cls.props = bpy.context.scene.BIMAnnotationProperties
+ cls.data["relating_types"] = cls.get_relating_types()
+
+ @classmethod
+ def get_relating_types(cls):
+ object_type = cls.props.object_type
+ relating_types = []
+ for relating_type in tool.Ifc.get().by_type("IfcTypeProduct"):
+ if tool.Drawing.is_annotation_object_type(relating_type, object_type):
+ relating_types.append(relating_type)
+
+ enum_items = [(str(e.id()), e.Name or "Unnamed", e.Description or "") for e in relating_types]
+
+ # item to create anootations without relating types
+ enum_items.insert(0, ("0", "-", ""))
+ return enum_items
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/ops.authoring.annotation.dat b/src/blenderbim/blenderbim/bim/module/drawing/ops.authoring.annotation.dat
new file mode 100644
index 0000000000..32a87c24e3
Binary files /dev/null and b/src/blenderbim/blenderbim/bim/module/drawing/ops.authoring.annotation.dat differ
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/prop.py b/src/blenderbim/blenderbim/bim/module/drawing/prop.py
index 2a04df4342..f34c7982c8 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/prop.py
@@ -25,7 +25,7 @@ import blenderbim.tool as tool
import blenderbim.core.drawing as core
import blenderbim.bim.module.drawing.annotation as annotation
import blenderbim.bim.module.drawing.decoration as decoration
-from blenderbim.bim.module.drawing.data import DrawingsData, DecoratorData, SheetsData
+from blenderbim.bim.module.drawing.data import DrawingsData, DecoratorData, SheetsData, AnnotationData
from blenderbim.bim.module.drawing.data import refresh as refresh_drawing_data
from pathlib import Path
from blenderbim.bim.prop import Attribute, StrProperty
@@ -465,3 +465,55 @@ class BIMTextProperties(PropertyGroup):
class BIMAssignedProductProperties(PropertyGroup):
is_editing_product: BoolProperty(name="Is Editing Product", default=False)
relating_product: PointerProperty(name="Relating Product", type=bpy.types.Object)
+
+
+# ObjectType: annotation_name, description, icon, data_type
+ANNOTATION_TYPES_DATA = {
+ "DIMENSION": ("Dimension", "", "FIXED_SIZE", "curve"),
+ "ANGLE": ("Angle", "", "DRIVER_ROTATIONAL_DIFFERENCE", "curve"),
+ "RADIUS": ("Radius", "", "FORWARD", "curve"),
+ "DIAMETER": ("Diameter", "", "ARROW_LEFTRIGHT", "curve"),
+ "TEXT": ("Text", "", "SMALL_CAPS", "empty"),
+ "TEXT_LEADER": ("Leader", "", "TRACKING_BACKWARDS", "curve"),
+ "STAIR_ARROW": ("Stair Arrow", "Add stair arrow annotation.\nIf you have IfcStairFlight object selected, it will be used as a reference for the annotation", "SCREEN_BACK", "curve"),
+ "HIDDEN_LINE": ("Hidden", "", "CON_TRACKTO", "mesh"),
+ "PLAN_LEVEL": ("Level (Plan)", "", "SORTBYEXT", "curve"),
+ "SECTION_LEVEL": ("Level (Section)", "", "TRIA_DOWN", "curve"),
+ "BREAKLINE": ("Breakline", "", "FCURVE", "mesh"),
+ "LINEWORK": ("Line", "", "MESH_MONKEY", "mesh"),
+ "BATTING": ("Batting", "Add batting annotation.\nThickness could be changed through Thickness property of BBIM_Batting property set", "FORCE_FORCE", "mesh"),
+ "FILL_AREA": ("Fill Area", "", "NODE_TEXTURE", "mesh"),
+ "FALL": ("Fall", "", "SORT_ASC", "curve"),
+}
+
+annotation_classes = [(x, *ANNOTATION_TYPES_DATA[x][:3], i) for i, x in enumerate(ANNOTATION_TYPES_DATA)]
+
+
+def get_annotation_data_prop(prop_name):
+ def function(self, context):
+ if not AnnotationData.is_loaded:
+ AnnotationData.load()
+ return AnnotationData.data[prop_name]
+
+ return function
+
+
+def update_annotation_object_type(self, context):
+ self.relating_type_id = "0"
+ # changing enum doesn't trigger refresh by itself
+ AnnotationData.is_loaded = False
+
+
+class BIMAnnotationProperties(PropertyGroup):
+ object_type: bpy.props.EnumProperty(
+ name="Annotation Object Type", items=annotation_classes, default="TEXT", update=update_annotation_object_type
+ )
+ relating_type_id: bpy.props.EnumProperty(
+ name="Relating Annotation Type", items=get_annotation_data_prop("relating_types")
+ )
+ create_representation_for_type: bpy.props.BoolProperty(
+ name="Create Representation For Type",
+ default=False,
+ description='Whether "Add type" should define a representation for the type \n'
+ "or allow occurences to have their own",
+ )
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/ui.py b/src/blenderbim/blenderbim/bim/module/drawing/ui.py
index 64210467b0..aead40f808 100644
--- a/src/blenderbim/blenderbim/bim/module/drawing/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/drawing/ui.py
@@ -371,7 +371,7 @@ class BIM_PT_text(Panel):
element = tool.Ifc.get_entity(context.active_object)
if not element:
return
- return element.is_a("IfcAnnotation") and element.ObjectType in ["TEXT", "TEXT_LEADER"]
+ return tool.Drawing.is_annotation_object_type(element, ["TEXT", "TEXT_LEADER"])
def draw(self, context):
obj = context.active_object
@@ -474,7 +474,6 @@ class BIM_PT_annotation_utilities(Panel):
op = row.operator("bim.add_annotation", text="Stair Arrow", icon="SCREEN_BACK")
op.object_type = "STAIR_ARROW"
op.data_type = "curve"
- op.description = "Add stair arrow annotation.\nIf you have IfcStairFlight object selected, it will be used as a reference for the annotation"
op = row.operator("bim.add_annotation", text="Hidden", icon="CON_TRACKTO")
op.object_type = "HIDDEN_LINE"
op.data_type = "mesh"
diff --git a/src/blenderbim/blenderbim/bim/module/drawing/workspace.py b/src/blenderbim/blenderbim/bim/module/drawing/workspace.py
new file mode 100644
index 0000000000..b0a13b6187
--- /dev/null
+++ b/src/blenderbim/blenderbim/bim/module/drawing/workspace.py
@@ -0,0 +1,296 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2023 @Andrej730
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+
+import os
+import bpy
+import blenderbim.tool as tool
+from blenderbim.bim.helper import prop_with_search
+from bpy.types import WorkSpaceTool
+
+# from blenderbim.bim.module.model.data import AuthoringData, RailingData, RoofData
+from blenderbim.bim.module.drawing.prop import ANNOTATION_TYPES_DATA
+from blenderbim.bim.module.drawing.data import DecoratorData, AnnotationData
+from blenderbim.bim.ifc import IfcStore
+import blenderbim.bim.handler
+
+
+# declaring it here to avoid circular import problems
+class Operator:
+ def execute(self, context):
+ IfcStore.execute_ifc_operator(self, context)
+ blenderbim.bim.handler.refresh_ui_data()
+ return {"FINISHED"}
+
+
+class AnnotationTool(WorkSpaceTool):
+ bl_space_type = "VIEW_3D"
+ bl_context_mode = "OBJECT"
+
+ bl_idname = "bim.annotation_tool"
+ bl_label = "Annotation Tool"
+ bl_description = "Gives you Annotation related superpowers"
+ bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.annotation")
+ bl_widget = None
+ # https://docs.blender.org/api/current/bpy.types.KeyMapItems.html
+ bl_keymap = tool.Blender.get_default_selection_keypmap() + (
+ ("bim.annotation_hotkey", {"type": "A", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_A")]}),
+ ("bim.annotation_hotkey", {"type": "C", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_C")]}),
+ ("bim.annotation_hotkey", {"type": "E", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_E")]}),
+ ("bim.annotation_hotkey", {"type": "F", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_F")]}),
+ ("bim.annotation_hotkey", {"type": "G", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_G")]}),
+ ("bim.annotation_hotkey", {"type": "K", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_K")]}),
+ ("bim.annotation_hotkey", {"type": "M", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_M")]}),
+ ("bim.annotation_hotkey", {"type": "O", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_O")]}),
+ ("bim.annotation_hotkey", {"type": "Q", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_Q")]}),
+ ("bim.annotation_hotkey", {"type": "R", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_R")]}),
+ ("bim.annotation_hotkey", {"type": "T", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_T")]}),
+ ("bim.annotation_hotkey", {"type": "V", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_V")]}),
+ ("bim.annotation_hotkey", {"type": "X", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_X")]}),
+ ("bim.annotation_hotkey", {"type": "Y", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_Y")]}),
+ ("bim.annotation_hotkey", {"type": "D", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_D")]}),
+ ("bim.annotation_hotkey", {"type": "E", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_E")]}),
+ ("bim.annotation_hotkey", {"type": "O", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_O")]}),
+ )
+
+ def draw_settings(context, layout, ws_tool):
+ # Unlike operators, Blender doesn't treat workspace tools as a class, so we'll create our own.
+ AnnotationToolUI.draw(context, layout)
+
+
+def add_layout_hotkey_operator(layout, text, hotkey, description):
+ modifiers = {
+ "A": "EVENT_ALT",
+ "S": "EVENT_SHIFT",
+ }
+ modifier, key = hotkey.split("_")
+
+ row = layout.row(align=True)
+ row.label(text="", icon=modifiers[modifier])
+ row.label(text="", icon=f"EVENT_{key}")
+
+ op = row.operator("bim.annotation_hotkey", text=text)
+ op.hotkey = hotkey
+ op.description = description
+ return op, row
+
+
+# TODO: move to operator
+def create_annotation_type(context):
+ # just empty to store parameters
+ props = context.scene.BIMAnnotationProperties
+ object_type = props.object_type
+ create_representation = props.create_representation_for_type
+ drawing = tool.Ifc.get_entity(bpy.context.scene.camera)
+
+ if props.create_representation_for_type:
+ obj = tool.Drawing.create_annotation_object(drawing, object_type)
+ else:
+ obj = bpy.data.objects.new(object_type, None)
+
+ obj.name = f"{object_type}_TYPE"
+ obj.location = context.scene.cursor.location
+ tool.Drawing.ensure_annotation_in_drawing_plane(obj)
+
+ drawing = tool.Ifc.get_entity(context.scene.camera)
+ ifc_context = tool.Drawing.get_annotation_context(tool.Drawing.get_drawing_target_view(drawing))
+
+ element = tool.Drawing.run_root_assign_class(
+ obj=obj,
+ ifc_class="IfcTypeProduct",
+ predefined_type=object_type,
+ should_add_representation=create_representation,
+ context=ifc_context,
+ ifc_representation_class=tool.Drawing.get_ifc_representation_class(object_type),
+ )
+ element.ApplicableOccurrence = f"IfcAnnotation/{object_type}"
+
+ tool.Blender.select_and_activate_single_object(context, obj)
+
+
+# TODO: move to operator
+def create_annotation_occurence(context):
+ # object_type = context.scene.BIMAnnotationProperties.object_type
+ props = context.scene.BIMAnnotationProperties
+ relating_type = tool.Ifc.get().by_id(int(props.relating_type_id))
+ object_type = props.object_type
+
+ drawing = tool.Ifc.get_entity(context.scene.camera)
+ obj = tool.Drawing.create_annotation_object(drawing, object_type)
+ obj.name = relating_type.Name
+ ifc_context = tool.Drawing.get_annotation_context(tool.Drawing.get_drawing_target_view(drawing))
+ relating_type_repr = tool.Drawing.get_annotation_representation(relating_type)
+ element = tool.Drawing.run_root_assign_class(
+ obj=obj,
+ ifc_class="IfcAnnotation",
+ predefined_type=object_type,
+ should_add_representation=not bool(relating_type_repr),
+ context=ifc_context,
+ ifc_representation_class=tool.Drawing.get_ifc_representation_class(object_type),
+ )
+
+ blenderbim.core.type.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type)
+
+ tool.Ifc.run("group.assign_group", group=tool.Drawing.get_drawing_group(drawing), products=[element])
+ tool.Collector.assign(obj)
+ tool.Blender.select_and_activate_single_object(context, obj)
+
+
+def create_annotation():
+ props = bpy.context.scene.BIMAnnotationProperties
+ create_type_occurence = props.relating_type_id != "0"
+ if create_type_occurence:
+ create_annotation_occurence(bpy.context)
+ else:
+ object_type = props.object_type
+ bpy.ops.bim.add_annotation(object_type=object_type, data_type=ANNOTATION_TYPES_DATA[object_type][-1])
+
+
+class AnnotationToolUI:
+ @classmethod
+ def draw(cls, context, layout):
+ cls.layout = layout
+ cls.props = context.scene.BIMAnnotationProperties
+
+ row = cls.layout.row(align=True)
+ if not tool.Ifc.get():
+ row.label(text="No IFC Project", icon="ERROR")
+ return
+
+ if not AnnotationData.is_loaded:
+ AnnotationData.load()
+
+ cls.draw_type_selection_interface()
+
+ if context.active_object and context.selected_objects:
+ cls.draw_edit_object_interface(context)
+ elif not context.selected_objects:
+ cls.draw_create_object_interface()
+
+ @classmethod
+ def draw_create_object_interface(cls):
+ row = cls.layout.row(align=True)
+ op, row = add_layout_hotkey_operator(cls.layout, "Add Type", "S_C", "Create a new annotation type")
+ selected_icon = "CHECKBOX_HLT" if cls.props.create_representation_for_type else "CHECKBOX_DEHLT"
+ row.prop(cls.props, "create_representation_for_type", text="", icon=selected_icon)
+
+ @classmethod
+ def draw_edit_object_interface(cls, context):
+ if DecoratorData.get_ifc_text_data(bpy.context.object):
+ add_layout_hotkey_operator(cls.layout, "Edit Text", "S_E", "")
+
+ @classmethod
+ def draw_header_interface(cls):
+ cls.draw_type_selection_interface()
+
+ @classmethod
+ def draw_basic_annotation_tool_interface(cls):
+ cls.draw_type_selection_interface()
+
+ @classmethod
+ def draw_type_selection_interface(cls):
+ # shared by both sidebar and header
+ object_type = cls.props.object_type
+
+ row = cls.layout.row(align=True)
+ row.label(text="", icon="FILE_VOLUME")
+ prop_with_search(row, cls.props, "object_type", text="")
+
+ row = cls.layout.row(align=True)
+ row.label(text="", icon="FILE_3D")
+ prop_with_search(row, cls.props, "relating_type_id", text="")
+
+ create_type_occurence = cls.props.relating_type_id != "0"
+ label = "Add Type Occurence" if create_type_occurence else "Add Annotation"
+ add_layout_hotkey_operator(cls.layout, label, "S_A", "Create a new annotation")
+
+ if object_type in ("TEXT", "STAIR_ARROW"):
+ add_layout_hotkey_operator(
+ cls.layout,
+ "Bulk Tag",
+ "S_T",
+ "Create new annotations and automatically adjust them to the selected objects",
+ )
+ add_layout_hotkey_operator(
+ cls.layout, "Readjust", "S_G", "Readjust tags based on the products they are assigned to"
+ )
+
+
+class Hotkey(bpy.types.Operator, Operator):
+ bl_idname = "bim.annotation_hotkey"
+ bl_label = "Hotkey"
+ bl_options = {"REGISTER", "UNDO"}
+ hotkey: bpy.props.StringProperty()
+ description: bpy.props.StringProperty()
+
+ @classmethod
+ def poll(cls, context):
+ return tool.Ifc.get()
+
+ @classmethod
+ def description(cls, context, operator):
+ return operator.description or ""
+
+ def _execute(self, context):
+ self.props = context.scene.BIMAnnotationProperties
+ getattr(self, f"hotkey_{self.hotkey}")()
+
+ def invoke(self, context, event):
+ # https://blender.stackexchange.com/questions/276035/how-do-i-make-operators-remember-their-property-values-when-called-from-a-hotkey
+ self.props = context.scene.BIMAnnotationProperties
+ return self.execute(context)
+
+ def draw(self, context):
+ pass
+
+ def hotkey_S_T(self):
+ props = bpy.context.scene.BIMAnnotationProperties
+ object_type = props.object_type
+
+ related_objects = bpy.context.selected_objects
+ for related_object in related_objects:
+ create_annotation()
+ obj = bpy.context.active_object
+ bpy.ops.object.mode_set(mode="OBJECT")
+ tool.Drawing.setup_annotation_object(obj, object_type, related_object)
+
+ def hotkey_S_A(self):
+ create_annotation()
+
+ def hotkey_S_E(self):
+ if not bpy.context.object:
+ return
+
+ if DecoratorData.get_ifc_text_data(bpy.context.object):
+ bpy.ops.bim.edit_text_popup()
+
+ def hotkey_S_G(self):
+ for obj in bpy.context.selected_objects:
+ element = tool.Ifc.get_entity(obj)
+ if not element or not element.is_a("IfcAnnotation"):
+ continue
+
+ related_product = tool.Drawing.get_assigned_product(element)
+ if not related_product:
+ continue
+ related_object = tool.Ifc.get_object(related_product)
+
+ tool.Drawing.setup_annotation_object(obj, element.ObjectType, related_object)
+
+ def hotkey_S_C(self):
+ create_annotation_type(bpy.context)
diff --git a/src/blenderbim/blenderbim/bim/module/type/operator.py b/src/blenderbim/blenderbim/bim/module/type/operator.py
index 094fd5f033..dc41a89912 100644
--- a/src/blenderbim/blenderbim/bim/module/type/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/type/operator.py
@@ -68,9 +68,7 @@ class UnassignType(bpy.types.Operator):
return attribute.is_a("IfcProfileDef") and attribute.ProfileName
self.file = IfcStore.get_file()
- objs = (
- [bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
- )
+ objs = [bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
for obj in objs:
element = tool.Ifc.get_entity(obj)
if not element or element.is_a("IfcElementType"):
@@ -90,7 +88,10 @@ class UnassignType(bpy.types.Operator):
else:
# We must unmap representations.
copied_representation = ifcopenshell.util.element.copy_deep(
- tool.Ifc.get(), resolved_representation, exclude=["IfcGeometricRepresentationContext"], exclude_callback=exclude_callback
+ tool.Ifc.get(),
+ resolved_representation,
+ exclude=["IfcGeometricRepresentationContext"],
+ exclude_callback=exclude_callback,
)
representations.append(copied_representation)
if representation.ContextOfItems == active_context:
@@ -227,6 +228,7 @@ class AddType(bpy.types.Operator, tool.Ifc.Operator):
context=body,
ifc_representation_class=None,
)
+
elif template in ("LAYERSET_AXIS2", "LAYERSET_AXIS3"):
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
obj = bpy.data.objects.new(name, None)
@@ -260,6 +262,7 @@ class AddType(bpy.types.Operator, tool.Ifc.Operator):
elif template == "LAYERSET_AXIS3":
axis = "AXIS3"
ifcopenshell.api.run("pset.edit_pset", ifc_file, pset=pset, properties={"LayerSetDirection": axis})
+
elif template == "PROFILESET":
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
obj = bpy.data.objects.new(name, None)
diff --git a/src/blenderbim/blenderbim/core/drawing.py b/src/blenderbim/blenderbim/core/drawing.py
index c6854767fa..4471987541 100644
--- a/src/blenderbim/blenderbim/core/drawing.py
+++ b/src/blenderbim/blenderbim/core/drawing.py
@@ -321,7 +321,6 @@ def add_annotation(ifc, collector, drawing_tool, drawing=None, object_type=None)
)
ifc.run("group.assign_group", group=drawing_tool.get_drawing_group(drawing), products=[element])
collector.assign(obj)
- drawing_tool.setup_annotation_object(obj, object_type)
drawing_tool.enable_editing(obj)
diff --git a/src/blenderbim/blenderbim/tool/blender.py b/src/blenderbim/blenderbim/tool/blender.py
index 2cb2ccde57..57726dca98 100644
--- a/src/blenderbim/blenderbim/tool/blender.py
+++ b/src/blenderbim/blenderbim/tool/blender.py
@@ -19,6 +19,7 @@
import bpy
import ifcopenshell.api
import blenderbim.tool as tool
+from mathutils import Vector
class Blender:
@@ -175,9 +176,17 @@ class Blender:
"max_y": bound_box[6][1],
"min_z": bound_box[0][2],
"max_z": bound_box[6][2],
+ "center": (Vector(bound_box[6]) - Vector(bound_box[0])) / 2,
}
return bbox_dict
+ @classmethod
+ def select_and_activate_single_object(cls, context, active_object):
+ for obj in context.selected_objects:
+ obj.select_set(False)
+ context.view_layer.objects.active = active_object
+ active_object.select_set(True)
+
## BMESH UTILS ##
@classmethod
def apply_bmesh(cls, mesh, bm, obj=None):
diff --git a/src/blenderbim/blenderbim/tool/drawing.py b/src/blenderbim/blenderbim/tool/drawing.py
index 9f004b2edb..f42c445aa6 100644
--- a/src/blenderbim/blenderbim/tool/drawing.py
+++ b/src/blenderbim/blenderbim/tool/drawing.py
@@ -40,6 +40,7 @@ from blenderbim.bim.module.drawing.data import FONT_SIZES, DecoratorData
from blenderbim.bim.module.drawing.prop import get_diagram_scales, BOX_ALIGNMENT_POSITIONS
from mathutils import Vector
+import collections
class Drawing(blenderbim.core.tool.Drawing):
@@ -80,8 +81,28 @@ class Drawing(blenderbim.core.tool.Drawing):
return obj
- # TODO: add a way for users to run this method to readjust the annotations
- # based on non-modifier stairs
+ @classmethod
+ def ensure_annotation_in_drawing_plane(cls, obj, camera=None):
+ """Make sure annotation object is going to be visible in the camera view"""
+
+ def get_camera_from_annotation_object(obj):
+ entity = tool.Ifc.get_entity(obj)
+ if not entity:
+ return
+ for rel in entity.HasAssignments:
+ if rel.is_a("IfcRelAssignsToGroup"):
+ for e in rel.RelatedObjects:
+ if e.ObjectType == "DRAWING":
+ return tool.Ifc.get_object(e)
+
+ if not camera:
+ camera = get_camera_from_annotation_object(obj) or bpy.context.scene.camera
+
+ current_pos = camera.matrix_world.inverted() @ obj.location
+ current_pos.z = -camera.data.clip_start
+ current_pos = camera.matrix_world @ current_pos
+ obj.location = current_pos
+
@classmethod
def setup_annotation_object(cls, obj, object_type, related_object=None):
"""Finish object's adjustments after both object and entity are created"""
@@ -89,27 +110,70 @@ class Drawing(blenderbim.core.tool.Drawing):
if not related_object:
related_object = bpy.context.active_object
related_entity = tool.Ifc.get_entity(related_object)
+ if not related_entity:
+ return
+
+ obj_entity = tool.Ifc.get_entity(obj)
+ assign_product = False
if object_type == "STAIR_ARROW":
- if related_entity and related_entity.is_a("IfcStairFlight"):
- stair, stair_entity = related_object, related_entity
- arrow = obj
- arrow_element = tool.Ifc.get_entity(arrow)
- arrow.parent = stair
+ if related_entity.is_a("IfcStairFlight"):
+ stair, arrow = related_object, obj
# place the arrow
# NOTE: may not work correctly in EDIT mode
bbox = tool.Blender.get_object_bounding_box(stair)
float_is_zero = lambda f: 0.0001 >= f >= -0.0001
- arrow.location = Vector((bbox["min_x"], (bbox["max_y"] - bbox["min_y"]) / 2, bbox["max_z"]))
+ arrow.location = stair.matrix_world @ Vector(
+ (bbox["min_x"], (bbox["max_y"] - bbox["min_y"]) / 2, bbox["max_z"])
+ )
last_step_x = max(v.co.x for v in stair.data.vertices if float_is_zero(v.co.z - bbox["max_z"]))
-
arrow.data.splines[0].points[0].co = Vector((0, 0, 0, 1))
arrow.data.splines[0].points[1].co = Vector((last_step_x, 0, 0, 1))
- if not cls.get_assigned_product(arrow_element):
- tool.Ifc.run("drawing.assign_product", relating_product=stair_entity, related_object=arrow_element)
- pass
+ cls.ensure_annotation_in_drawing_plane(obj)
+ assign_product = True
+
+ elif object_type == "TEXT":
+ bbox = tool.Blender.get_object_bounding_box(related_object)
+
+ obj.location = related_object.matrix_world @ bbox["center"]
+ cls.ensure_annotation_in_drawing_plane(obj)
+ assign_product = True
+
+ if assign_product and not cls.get_assigned_product(obj_entity):
+ tool.Ifc.run("drawing.assign_product", relating_product=related_entity, related_object=obj_entity)
+
+ if object_type == "TEXT":
+ tool.Drawing.update_text_value(obj)
+
+ @classmethod
+ def is_annotation_object_type(cls, element, object_types):
+ if not isinstance(object_types, collections.abc.Iterable):
+ object_types = [object_types]
+
+ element_type = element.is_a()
+
+ if element_type == "IfcAnnotation" and element.ObjectType in object_types:
+ return True
+
+ if element_type == "IfcTypeProduct" and element.ApplicableOccurrence.startswith("IfcAnnotation/"):
+ applicable_object_type = element.ApplicableOccurrence.split("/")[1]
+ if applicable_object_type in object_types:
+ return True
+
+ return False
+
+ @classmethod
+ def get_annotation_representation(cls, element):
+ rep = ifcopenshell.util.representation.get_representation(
+ element, "Plan", "Annotation"
+ ) or ifcopenshell.util.representation.get_representation(element, "Model", "Annotation")
+ if not rep:
+ return None
+
+ rep = tool.Geometry.resolve_mapped_representation(rep)
+ return rep
@classmethod
def create_camera(cls, name, matrix):
@@ -376,13 +440,11 @@ class Drawing(blenderbim.core.tool.Drawing):
element = tool.Ifc.get_entity(obj)
if not element:
return
- rep = ifcopenshell.util.representation.get_representation(
- element, "Plan", "Annotation"
- ) or ifcopenshell.util.representation.get_representation(element, "Model", "Annotation")
+ rep = cls.get_annotation_representation(element)
if not rep:
- return
- items = [i for i in rep.Items if i.is_a("IfcTextLiteral")]
+ return [] if return_list else None
+ items = [i for i in rep.Items if i.is_a("IfcTextLiteral")]
if not items:
return [] if return_list else None
if return_list:
@@ -395,9 +457,7 @@ class Drawing(blenderbim.core.tool.Drawing):
if not element:
return
- rep = ifcopenshell.util.representation.get_representation(
- element, "Plan", "Annotation"
- ) or ifcopenshell.util.representation.get_representation(element, "Model", "Annotation")
+ rep = cls.get_annotation_representation(element)
if not rep:
return
@@ -435,9 +495,7 @@ class Drawing(blenderbim.core.tool.Drawing):
if not element:
return
- rep = ifcopenshell.util.representation.get_representation(
- element, "Plan", "Annotation"
- ) or ifcopenshell.util.representation.get_representation(element, "Model", "Annotation")
+ rep = cls.get_annotation_representation(element)
if not rep:
return
diff --git a/src/blenderbim/blenderbim_icons.blend b/src/blenderbim/blenderbim_icons.blend
new file mode 100644
index 0000000000..75dd8bf260
Binary files /dev/null and b/src/blenderbim/blenderbim_icons.blend differ
diff --git a/src/blenderbim/icon-cad.blend b/src/blenderbim/icon-cad.blend
deleted file mode 100644
index 92dc30b4a1..0000000000
Binary files a/src/blenderbim/icon-cad.blend and /dev/null differ
diff --git a/src/blenderbim/icon.blend b/src/blenderbim/icon.blend
deleted file mode 100644
index b804c2ab9b..0000000000
Binary files a/src/blenderbim/icon.blend and /dev/null differ
diff --git a/src/blenderbim/test/core/test_drawing.py b/src/blenderbim/test/core/test_drawing.py
index c1ac757556..1d18d840a8 100644
--- a/src/blenderbim/test/core/test_drawing.py
+++ b/src/blenderbim/test/core/test_drawing.py
@@ -420,7 +420,6 @@ class TestAddAnnotation:
drawing.get_drawing_group("drawing").should_be_called().will_return("group")
ifc.run("group.assign_group", group="group", products=["element"]).should_be_called()
collector.assign("obj").should_be_called()
- drawing.setup_annotation_object("obj", "object_type").should_be_called()
drawing.enable_editing("obj").should_be_called()
subject.add_annotation(ifc, collector, drawing, drawing="drawing", object_type="object_type")