Files
IfcOpenShell/src/bonsai/bonsai/bim/module/drawing/annotation.py
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

218 lines
8.3 KiB
Python
Raw Normal View History

# Bonsai - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Maxim Vasilyev <qwiglydee@gmail.com>
2021-08-17 08:55:29 +10:00
#
# This file is part of Bonsai.
2021-08-17 08:55:29 +10:00
#
# Bonsai is free software: you can redistribute it and/or modify
2021-08-17 08:55:29 +10:00
# 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.
#
# Bonsai is distributed in the hope that it will be useful,
2021-08-17 08:55:29 +10:00
# 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 Bonsai. If not, see <http://www.gnu.org/licenses/>.
2021-08-17 08:55:29 +10:00
2025-02-21 14:45:17 +05:00
from __future__ import annotations
import os
import bpy
import math
2023-05-27 15:22:07 +05:00
import bmesh
2024-08-14 14:10:36 +05:00
import bonsai.tool as tool
import ifcopenshell.util.element
2025-01-20 18:46:53 +05:00
from pathlib import Path
from mathutils import Vector, Matrix
2024-10-04 12:10:15 +05:00
from typing import Optional
2020-05-03 15:41:05 +10:00
2020-11-01 20:08:48 +07:00
class Annotator:
2020-05-03 15:41:05 +10:00
@staticmethod
2024-10-04 12:10:15 +05:00
def add_text(related_element: Optional[ifcopenshell.entity_instance] = None) -> bpy.types.Object:
curve = bpy.data.curves.new(type="FONT", name="Text")
2020-11-01 20:08:48 +07:00
curve.body = "TEXT"
obj = bpy.data.objects.new("Text", curve)
obj.matrix_world = bpy.context.scene.camera.matrix_world
if related_element is None:
location, _, _, _ = Annotator.get_placeholder_coords(bpy.context)
else:
2025-02-10 13:31:50 +05:00
curve.BIMAssignedProductProperties.related_element = related_element
location = related_element.location
obj.location = location
2020-05-03 15:41:05 +10:00
obj.hide_render = True
2020-11-01 20:08:48 +07:00
font = bpy.data.fonts.get("OpenGost TypeB TT")
if not font:
2020-11-01 20:08:48 +07:00
font = bpy.data.fonts.load(
2025-01-20 18:46:53 +05:00
tool.Blender.get_data_dir_path(Path("fonts") / "OpenGost Type B TT.ttf").__str__()
2020-11-01 20:08:48 +07:00
)
font.name = "OpenGost Type B TT"
2025-02-10 13:31:50 +05:00
curve.font = font
props = tool.Drawing.get_text_props(obj)
props.font_size = "2.5"
2025-02-25 11:43:29 +05:00
collection = tool.Blender.get_object_bim_props(bpy.context.scene.camera).collection
collection.objects.link(obj)
Annotator.resize_text(obj)
2020-05-03 15:41:05 +10:00
return obj
@staticmethod
2024-10-04 12:10:15 +05:00
def resize_text(text_obj: bpy.types.Object) -> None:
camera = None
group = tool.Drawing.get_drawing_group(tool.Ifc.get_entity(text_obj))
for element in tool.Drawing.get_drawing_elements(group):
if element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
camera = tool.Ifc.get_object(element)
break
if not camera:
return
# This is a magic number for OpenGost
font_size = 1.6 / 1000
2025-02-10 13:31:50 +05:00
props = tool.Drawing.get_text_props(text_obj)
font_size *= float(props.font_size)
font_size /= tool.Drawing.get_scale_ratio(tool.Drawing.get_diagram_scale(camera)["Scale"])
text_obj.data.size = font_size
2020-05-03 15:41:05 +10:00
@staticmethod
2024-10-04 12:10:15 +05:00
def add_line_to_annotation(
obj: bpy.types.Object, co1: Optional[Vector] = None, co2: Optional[Vector] = None
) -> bpy.types.Object:
if co1 is None:
co1, co2, _, _ = Annotator.get_placeholder_coords()
co1 = obj.matrix_world.inverted() @ co1
co2 = obj.matrix_world.inverted() @ co2
2023-04-10 14:02:50 +05:00
2020-05-03 15:41:05 +10:00
if isinstance(obj.data, bpy.types.Mesh):
obj.data.vertices.add(2)
obj.data.vertices[-2].co = co1
obj.data.vertices[-1].co = co2
2020-06-06 23:42:24 +02:00
obj.data.edges.add(1)
2020-05-03 15:41:05 +10:00
obj.data.edges[-1].vertices = (obj.data.vertices[-2].index, obj.data.vertices[-1].index)
2023-04-10 14:02:50 +05:00
2020-05-03 15:41:05 +10:00
if isinstance(obj.data, bpy.types.Curve):
2020-11-01 20:08:48 +07:00
polyline = obj.data.splines.new("POLY")
2020-05-03 15:41:05 +10:00
polyline.points.add(1)
polyline.points[-2].co = list(co1) + [1]
polyline.points[-1].co = list(co2) + [1]
2023-04-10 14:02:50 +05:00
2020-05-03 15:41:05 +10:00
return obj
@staticmethod
2024-10-04 12:10:15 +05:00
def add_vertex_to_annotation(obj: bpy.types.Object) -> bpy.types.Object:
verts_world_space = Annotator.get_placeholder_coords()
vert_local = obj.matrix_world.inverted() @ verts_world_space[0]
bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True)
bm.verts.new(vert_local)
tool.Blender.apply_bmesh(obj.data, bm, obj)
return obj
2020-12-26 21:59:57 +07:00
@staticmethod
2024-10-04 12:10:15 +05:00
def add_plane_to_annotation(obj: bpy.types.Object, remove_face: bool = False) -> bpy.types.Object:
# default order = bot left, top left, bot right, top right
# therefore we redefine the order
face_verts = [0, 2, 3, 1]
2020-12-26 21:59:57 +07:00
verts_world_space = Annotator.get_placeholder_coords()
verts_local = [obj.matrix_world.inverted() @ v for v in verts_world_space]
bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True)
new_verts = [bm.verts.new(v) for v in verts_local]
2020-12-26 21:59:57 +07:00
2023-05-27 15:22:07 +05:00
face = bm.faces.new([new_verts[i] for i in face_verts])
if remove_face:
bmesh.ops.delete(bm, geom=[face], context="FACES_ONLY")
2023-04-03 12:06:56 +05:00
tool.Blender.apply_bmesh(obj.data, bm, obj)
2020-12-26 21:59:57 +07:00
return obj
2020-05-03 15:41:05 +10:00
@staticmethod
2025-02-21 14:45:17 +05:00
def get_annotation_obj(
drawing: ifcopenshell.entity_instance, object_type: str, data_type: tool.Drawing.ANNOTATION_DATA_TYPE
) -> bpy.types.Object:
2025-05-15 10:40:23 +05:00
assert bpy.context.scene
camera = tool.Ifc.get_object(drawing)
2025-05-15 10:40:23 +05:00
assert isinstance(camera, bpy.types.Object)
# those annotations you want to obey the depth of the 3d cursor
2025-05-09 19:26:20 +05:00
if object_type == "PLAN_LEVEL":
co1 = bpy.context.scene.cursor.location.copy()
else:
co1, _, _, _ = Annotator.get_placeholder_coords(camera)
matrix_world = tool.Drawing.get_camera_matrix(camera)
matrix_world.translation = co1
2025-02-25 11:43:29 +05:00
collection = tool.Blender.get_object_bim_props(camera).collection
2023-04-10 14:02:50 +05:00
if object_type == "TEXT":
obj = bpy.data.objects.new(object_type, None)
obj.matrix_world = matrix_world
collection.objects.link(obj)
return obj
2023-04-10 14:02:50 +05:00
elif object_type in ("TEXT_LEADER", "SECTION_LEVEL"):
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)
return obj
2023-04-10 14:02:50 +05:00
2025-02-24 16:44:54 +05:00
# TODO: remove as outdated?
# Is reusing the same objects preventing the creation of new annotations.
# if object_type != "ANGLE":
# for obj in collection.objects:
# element = tool.Ifc.get_entity(obj)
# if (
# element
# and ifcopenshell.util.element.get_predefined_type(element) == object_type
# and obj.type == data_type.upper()
# ):
# return obj
2023-04-10 14:02:50 +05:00
2020-11-01 20:08:48 +07:00
if data_type == "mesh":
data = bpy.data.meshes.new(object_type)
2020-11-01 20:08:48 +07:00
elif data_type == "curve":
data = bpy.data.curves.new(object_type, type="CURVE")
2020-11-01 20:08:48 +07:00
data.dimensions = "3D"
2020-05-03 15:41:05 +10:00
data.resolution_u = 2
elif data_type == "empty":
data = None
2023-04-10 14:02:50 +05:00
obj = bpy.data.objects.new(object_type, data)
obj.matrix_world = matrix_world
2020-05-03 15:41:05 +10:00
collection.objects.link(obj)
return obj
@staticmethod
2024-10-04 12:10:15 +05:00
def get_placeholder_coords(camera: Optional[bpy.types.Object] = None) -> tuple[Vector, Vector, Vector, Vector]:
if not camera:
camera = bpy.context.scene.camera
2020-05-03 15:41:05 +10:00
z_offset = camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
if (
ifcopenshell.util.element.get_pset(tool.Ifc.get_entity(camera), "EPset_Drawing", "TargetView")
== "REFLECTED_PLAN_VIEW"
):
z_offset *= -1
2023-04-10 14:02:50 +05:00
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
2020-05-03 15:41:05 +10:00
y_offset = camera.matrix_world.to_quaternion() @ Vector((0, y, 0))
x_offset = camera.matrix_world.to_quaternion() @ Vector((y / 2, 0, 0))
center = camera.matrix_world.inverted() @ bpy.context.scene.cursor.location
center.z = 0
center = camera.matrix_world @ center
return (
center + z_offset,
center + z_offset + y_offset,
center + z_offset + x_offset,
center + z_offset + x_offset + y_offset,
)