Files
IfcOpenShell/src/bonsai/test/tool/test_drawing.py
T
Andrej730 2581c36a5f black .
2025-12-18 18:47:13 +05:00

998 lines
39 KiB
Python

# Bonsai - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of Bonsai.
#
# Bonsai 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.
#
# Bonsai 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 Bonsai. If not, see <http://www.gnu.org/licenses/>.
import os
from pathlib import Path
import numpy as np
import bpy
import mathutils
import ifcopenshell
import ifcopenshell.api.drawing
import ifcopenshell.api.group
import ifcopenshell.api.pset
import ifcopenshell.api.root
import ifcopenshell.guid
import ifcopenshell.util.element
import bonsai.core.tool
import bonsai.tool as tool
from test.bim.bootstrap import NewFile
from bonsai.tool.drawing import Drawing as subject
from bonsai.bim.module.drawing.data import DecoratorData
from mathutils import Vector
import xml.etree.ElementTree as ET
class TestImplementsTool(NewFile):
def test_run(self):
assert isinstance(subject(), bonsai.core.tool.Drawing)
class TestCopyRepresentation(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
source = ifc.createIfcAnnotation(Representation=ifc.createIfcProductDefinitionShape())
dest = ifc.createIfcAnnotation()
subject.copy_representation(source, dest)
assert dest.Representation.is_a("IfcProductDefinitionShape")
class TestCreateAnnotationObject(NewFile):
def test_nothing(self):
pass
class TestCreateCamera(NewFile):
def test_run(self):
obj = subject.create_camera("Name", mathutils.Matrix(), "PERSPECTIVE", "PLAN_VIEW")
assert obj.name == "Name"
assert obj.matrix_world == mathutils.Matrix()
assert isinstance(obj.data, bpy.types.Camera)
assert obj.data.type == "PERSP"
assert obj.data.ortho_scale == 50
assert obj.data.clip_end == 10
assert obj.users_collection == tuple()
class TestCreateSvgSheet(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcProject")
tool.Ifc.set(ifc)
ifc_path = Path("test/files/temp/test.ifc").absolute()
bpy.ops.bim.save_project(filepath=str(ifc_path), should_save_as=True)
document = ifc.createIfcDocumentInformation(
Identification="X",
Name="FOOBAR",
Scope="SHEET",
Location=(ifc_path.parent / "layouts" / "X - FOOBAR.svg").as_posix(),
)
uri = subject.create_svg_sheet(document, "A1")
assert uri.endswith(".svg")
assert os.path.isfile(uri)
class TestDeleteCollection(NewFile):
def test_run(self):
collection = bpy.data.collections.new("Foobar")
subject.delete_collection(collection)
assert not bpy.data.collections.get("Foobar")
class TestDeleteDrawingElements(NewFile):
def test_run(self):
assert bpy.context.scene
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh"))
collection = bpy.data.collections.new("Collection")
bpy.context.scene.collection.children.link(collection)
collection.objects.link(obj)
element = ifc.createIfcAnnotation(GlobalId=ifcopenshell.guid.new())
tool.Ifc.link(element, obj)
element_id = element.id()
subject.delete_drawing_elements([element])
try:
ifc.by_id(element_id)
assert False
except:
pass
assert bpy.data.objects.get("Object") is None
class TestDisableEditingDrawings(NewFile):
def test_run(self):
props = tool.Drawing.get_document_props()
props.is_editing_drawings = True
subject.disable_editing_drawings()
assert props.is_editing_drawings == False
class TestDisableEditingSchedules(NewFile):
def test_run(self):
props = tool.Drawing.get_document_props()
props.is_editing_schedules = True
subject.disable_editing_schedules()
assert props.is_editing_schedules == False
class TestDisableEditingReferences(NewFile):
def test_run(self):
props = tool.Drawing.get_document_props()
props.is_editing_references = True
subject.disable_editing_references()
assert props.is_editing_references == False
class TestDisableEditingSheets(NewFile):
def test_run(self):
props = tool.Drawing.get_document_props()
props.is_editing_sheets = True
subject.disable_editing_sheets()
assert props.is_editing_sheets == False
class TestDisableEditingText(NewFile):
def test_run(self):
obj = bpy.data.objects.new("Object", None)
props = tool.Drawing.get_text_props(obj)
props.is_editing = True
subject.disable_editing_text(obj)
# Props might get invalidated, since no properties set.
props = tool.Drawing.get_text_props(obj)
assert props.is_editing == False
class TestDisableEditingAssignedProduct(NewFile):
def test_run(self):
obj = bpy.data.objects.new("Object", None)
props = subject.get_object_assigned_product_props(obj)
props.is_editing_product = True
subject.disable_editing_assigned_product(obj)
assert props.is_editing_product == False
class TestEnableEditing(NewFile):
def test_run(self):
assert bpy.context.scene
obj = bpy.data.objects.new("Object", None)
bpy.context.scene.collection.objects.link(obj)
subject.enable_editing(obj)
assert obj in bpy.context.selected_objects
class TestEnableEditingDrawings(NewFile):
def test_run(self):
props = tool.Drawing.get_document_props()
props.is_editing_drawings = False
subject.enable_editing_drawings()
assert props.is_editing_drawings == True
class TestEnableEditingSchedules(NewFile):
def test_run(self):
props = tool.Drawing.get_document_props()
props.is_editing_schedules = False
subject.enable_editing_schedules()
assert props.is_editing_schedules == True
class TestEnableEditingReferences(NewFile):
def test_run(self):
props = tool.Drawing.get_document_props()
props.is_editing_references = False
subject.enable_editing_references()
assert props.is_editing_references == True
class TestEnableEditingSheets(NewFile):
def test_run(self):
props = tool.Drawing.get_document_props()
props.is_editing_sheets = False
subject.enable_editing_sheets()
assert props.is_editing_sheets == True
class TestEnableEditingText(NewFile):
def test_run(self):
obj = bpy.data.objects.new("Object", None)
subject.enable_editing_text(obj)
props = tool.Drawing.get_text_props(obj)
assert props.is_editing == True
class TestEnableEditingAssignedProduct(NewFile):
def test_run(self):
obj = bpy.data.objects.new("Object", None)
props = subject.get_object_assigned_product_props(obj)
subject.enable_editing_assigned_product(obj)
assert props.is_editing_product == True
class TestEnsureUniqueDrawingName(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
assert subject.ensure_unique_drawing_name("FOOBAR") == "FOOBAR"
ifc.createIfcAnnotation(Name="FOOBAR", ObjectType="DRAWING")
assert subject.ensure_unique_drawing_name("FOOBAR") == "FOOBAR-X"
ifc.createIfcAnnotation(Name="FOOBAR-X", ObjectType="DRAWING")
assert subject.ensure_unique_drawing_name("FOOBAR") == "FOOBAR-X-X"
class TestEnsureUniqueIdentification(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
assert subject.ensure_unique_identification("FOOBAR") == "FOOBAR"
ifc.createIfcDocumentInformation(Identification="FOOBAR", Scope="SHEET")
assert subject.ensure_unique_identification("FOOBAR") == "FOOBAR-X"
ifc.createIfcDocumentInformation(Identification="FOOBAR-X", Scope="SHEET")
assert subject.ensure_unique_identification("FOOBAR") == "FOOBAR-X-X"
def test_unique_document_id_ifc2x3(self):
ifc = ifcopenshell.file(schema="IFC2X3")
tool.Ifc.set(ifc)
assert subject.ensure_unique_identification("FOOBAR") == "FOOBAR"
ifc.createIfcDocumentInformation(DocumentId="FOOBAR", Scope="SHEET")
assert subject.ensure_unique_identification("FOOBAR") == "FOOBAR-X"
ifc.createIfcDocumentInformation(DocumentId="FOOBAR-X", Scope="SHEET")
assert subject.ensure_unique_identification("FOOBAR") == "FOOBAR-X-X"
class TestExportTextLiteralAttributes(NewFile):
def test_run(self):
TestImportTextAttributes().test_run()
assert subject.export_text_literal_attributes(bpy.data.objects["Object"]) == [
{
"Literal": "Literal",
"Path": "RIGHT",
"BoxAlignment": "bottom-left",
}
]
class TestGetAnnotationContext(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
context = ifc.createIfcGeometricRepresentationSubContext(
ContextType="Plan", ContextIdentifier="Annotation", TargetView="PLAN_VIEW"
)
context2 = ifc.createIfcGeometricRepresentationSubContext(
ContextType="Model", ContextIdentifier="Annotation", TargetView="ELEVATION_VIEW"
)
context3 = ifc.createIfcGeometricRepresentationSubContext(
ContextType="Model", ContextIdentifier="Annotation", TargetView="PLAN_VIEW"
)
tool.Ifc.set(ifc)
assert subject.get_annotation_context("PLAN_VIEW") == context
assert subject.get_annotation_context("ELEVATION_VIEW") == context2
assert subject.get_annotation_context("PLAN_VIEW", "FALL") == context3
class TestGetBodyContext(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
context = ifc.createIfcGeometricRepresentationSubContext(
ContextType="Model", ContextIdentifier="Body", TargetView="MODEL_VIEW"
)
tool.Ifc.set(ifc)
assert subject.get_body_context() == context
class TestGetDocumentUri(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
document = ifc.createIfcDocumentInformation(
Identification="X", Name="FOOBAR", Scope="SHEET", Location="Location"
)
assert subject.get_document_uri(document) == os.path.abspath(os.path.join(tool.Ifc.get_path(), "Location"))
def test_get_indirect_locations(self):
ifc = ifcopenshell.file()
document = ifc.createIfcDocumentInformation(Identification="X", Name="FOOBAR", Scope="SHEET")
reference = ifc.createIfcDocumentReference(Location="Location", ReferencedDocument=document)
assert subject.get_document_uri(document) == os.path.abspath(os.path.join(tool.Ifc.get_path(), "Location"))
assert subject.get_document_uri(reference) == os.path.abspath(os.path.join(tool.Ifc.get_path(), "Location"))
def test_run_ifc2x3(self):
ifc = ifcopenshell.file(schema="IFC2X3")
tool.Ifc.set(ifc)
reference = ifc.createIfcDocumentReference(Location="Location")
document = ifc.createIfcDocumentInformation(
DocumentId="X", Name="FOOBAR", Scope="SHEET", DocumentReferences=[reference]
)
assert subject.get_document_uri(document) == os.path.abspath(os.path.join(tool.Ifc.get_path(), "Location"))
assert subject.get_document_uri(reference) == os.path.abspath(os.path.join(tool.Ifc.get_path(), "Location"))
class TestGetDrawingCollection(NewFile):
def test_run(self):
assert bpy.context.scene
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
obj = bpy.data.objects.new("Object", None)
collection = bpy.data.collections.new("Collection")
bpy.context.scene.collection.children.link(collection)
collection.objects.link(obj)
props = tool.Blender.get_object_bim_props(obj)
props.collection = collection
collection.BIMCollectionProperties.obj = obj
element = ifc.createIfcAnnotation()
tool.Ifc.link(element, obj)
assert subject.get_drawing_collection(element) == collection
class TestGetDrawingGroup(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
element = ifc.createIfcAnnotation()
group = ifcopenshell.api.group.add_group(ifc)
group.ObjectType = "DRAWING"
ifcopenshell.api.group.assign_group(ifc, products=[element], group=group)
assert subject.get_drawing_group(element) == group
class TestGetDrawingTargetView(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
element = ifc.createIfcAnnotation()
pset = ifcopenshell.api.pset.add_pset(ifc, product=element, name="EPset_Drawing")
ifcopenshell.api.pset.edit_pset(ifc, pset=pset, properties={"TargetView": "PLAN_VIEW"})
assert subject.get_drawing_target_view(element) == "PLAN_VIEW"
class TestGetGroupElements(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
element = ifc.createIfcAnnotation()
group = ifcopenshell.api.group.add_group(ifc)
ifcopenshell.api.group.assign_group(ifc, products=[element], group=group)
assert subject.get_group_elements(group) == (element,)
class TestGetIfcRepresentationClass(NewFile):
def test_run(self):
assert subject.get_ifc_representation_class("TEXT") == "IfcTextLiteral"
assert subject.get_ifc_representation_class("TEXT_LEADER") == "IfcGeometricCurveSet/IfcTextLiteral"
assert subject.get_ifc_representation_class("FOOBAR") == ""
class TestGetName(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
assert subject.get_name(ifc.createIfcWall(Name="Foobar")) == "Foobar"
class TestGenerateDrawingMatrix(NewFile):
def test_returning_the_origin_as_a_fallback(self):
assert subject.generate_drawing_matrix("PLAN_VIEW", 0) == mathutils.Matrix()
def test_creating_a_plan_view_at_the_cursor_at_a_storey(self):
assert bpy.context.scene
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
obj = bpy.data.objects.new("Object", None)
element = ifc.createIfcBuildingStorey()
tool.Ifc.link(element, obj)
bpy.context.scene.collection.objects.link(obj)
obj.matrix_world[2][3] = 3
bpy.context.scene.cursor.location = (1.0, 2.0, 0.0)
m = subject.generate_drawing_matrix("PLAN_VIEW", element.id())
assert round(m[0][3], 3) == 1
assert round(m[1][3], 3) == 2
assert round(m[2][3], 3) == 4.6
def test_creating_an_rcp_at_the_origin(self):
assert subject.generate_drawing_matrix("REFLECTED_PLAN_VIEW", 0) == mathutils.Matrix(
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, -1, 0), (0, 0, 0, 1))
)
def test_creating_an_rcp_at_the_cursor_at_a_storey(self):
assert bpy.context.scene
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
obj = bpy.data.objects.new("Object", None)
element = ifc.createIfcBuildingStorey()
tool.Ifc.link(element, obj)
bpy.context.scene.collection.objects.link(obj)
obj.matrix_world[2][3] = 3
bpy.context.scene.cursor.location = (1.0, 2.0, 0.0)
assert subject.generate_drawing_matrix("REFLECTED_PLAN_VIEW", element.id()) == mathutils.Matrix(
((1, 0, 0, 1), (0, 1, 0, 2), (0, 0, -1, 3 + 1.6), (0, 0, 0, 1))
)
def test_creating_a_north_elevation_at_the_cursor(self):
assert bpy.context.scene
bpy.context.scene.cursor.location = (1.0, 2.0, 3.0)
assert subject.generate_drawing_matrix("ELEVATION_VIEW", "NORTH") == mathutils.Matrix(
((-1, 0, 0, 1), (0, 0, 1, 2), (0, 1, 0, 3), (0, 0, 0, 1))
)
def test_creating_a_south_elevation_at_the_cursor(self):
assert bpy.context.scene
bpy.context.scene.cursor.location = (1.0, 2.0, 3.0)
assert subject.generate_drawing_matrix("ELEVATION_VIEW", "SOUTH") == mathutils.Matrix(
((1, 0, 0, 1), (0, 0, -1, 2), (0, 1, 0, 3), (0, 0, 0, 1))
)
def test_creating_an_east_elevation_at_the_cursor(self):
assert bpy.context.scene
bpy.context.scene.cursor.location = (1.0, 2.0, 3.0)
assert subject.generate_drawing_matrix("ELEVATION_VIEW", "EAST") == mathutils.Matrix(
((0, 0, 1, 1), (1, 0, 0, 2), (0, 1, 0, 3), (0, 0, 0, 1))
)
def test_creating_a_west_elevation_at_the_cursor(self):
assert bpy.context.scene
bpy.context.scene.cursor.location = (1.0, 2.0, 3.0)
assert subject.generate_drawing_matrix("ELEVATION_VIEW", "WEST") == mathutils.Matrix(
((0, 0, -1, 1), (-1, 0, 0, 2), (0, 1, 0, 3), (0, 0, 0, 1))
)
def test_creating_a_north_section_at_the_cursor(self):
assert bpy.context.scene
bpy.context.scene.cursor.location = (1.0, 2.0, 3.0)
assert subject.generate_drawing_matrix("SECTION_VIEW", "NORTH") == mathutils.Matrix(
((1, 0, 0, 1), (0, 0, -1, 2), (0, 1, 0, 3), (0, 0, 0, 1))
)
def test_creating_a_south_section_at_the_cursor(self):
assert bpy.context.scene
bpy.context.scene.cursor.location = (1.0, 2.0, 3.0)
assert subject.generate_drawing_matrix("SECTION_VIEW", "SOUTH") == mathutils.Matrix(
((-1, 0, 0, 1), (0, 0, 1, 2), (0, 1, 0, 3), (0, 0, 0, 1))
)
def test_creating_an_east_section_at_the_cursor(self):
assert bpy.context.scene
bpy.context.scene.cursor.location = (1.0, 2.0, 3.0)
assert subject.generate_drawing_matrix("SECTION_VIEW", "EAST") == mathutils.Matrix(
((0, 0, -1, 1), (-1, 0, 0, 2), (0, 1, 0, 3), (0, 0, 0, 1))
)
def test_creating_a_west_section_at_the_cursor(self):
assert bpy.context.scene
bpy.context.scene.cursor.location = (1.0, 2.0, 3.0)
assert subject.generate_drawing_matrix("SECTION_VIEW", "WEST") == mathutils.Matrix(
((0, 0, 1, 1), (1, 0, 0, 2), (0, 1, 0, 3), (0, 0, 0, 1))
)
def test_aligning_model_view_camera_to_viewport(self):
assert (space := tool.Blender.get_view3d_space())
assert (r3d := space.region_3d)
viewport = r3d.view_matrix.inverted()
generated = subject.generate_drawing_matrix("MODEL_VIEW", "PERSPECTIVE")
assert np.allclose(generated, viewport)
generated = subject.generate_drawing_matrix("MODEL_VIEW", "ORTHOGRAPHIC")
assert np.allclose(generated, viewport)
class TestGenerateSheetIdentification(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
assert subject.generate_sheet_identification() == "A01"
document = ifc.createIfcDocumentInformation(Scope="SHEET")
assert subject.generate_sheet_identification() == "A02"
class TestGetTextLiteral(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
obj = bpy.data.objects.new("Object", None)
element = ifc.createIfcAnnotation()
element.Representation = ifc.createIfcProductDefinitionShape()
context = ifc.createIfcGeometricRepresentationSubContext(ContextType="Plan", ContextIdentifier="Annotation")
item = ifc.createIfcTextLiteralWithExtent(Literal="Literal", Path="RIGHT", BoxAlignment="bottom-left")
representation = ifc.createIfcShapeRepresentation(ContextOfItems=context, Items=[item])
element.Representation.Representations = [representation]
element.ObjectType = "TEXT" # TODO: double check if it's valid to set this
tool.Ifc.link(element, obj)
assert subject.get_text_literal(obj) == item
class TestGetAssignedProduct(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
wall = ifc.createIfcWall()
label = ifc.createIfcAnnotation()
ifcopenshell.api.drawing.assign_product(ifc, relating_product=wall, related_object=label)
assert subject.get_assigned_product(label) == wall
class TestImportDrawings(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
drawing = ifc.createIfcAnnotation(Name="FOOBAR", ObjectType="DRAWING")
pset = ifcopenshell.api.pset.add_pset(ifc, product=drawing, name="EPset_Drawing")
ifcopenshell.api.pset.edit_pset(ifc, pset=pset, properties={"TargetView": "PLAN_VIEW"})
subject.import_drawings()
props = tool.Drawing.get_document_props()
for d in props.drawings:
d.is_expanded = True
subject.import_drawings()
assert props.drawings[1].target_view == "PLAN_VIEW"
assert props.drawings[2].ifc_definition_id == drawing.id()
assert props.drawings[2].name == "FOOBAR"
class TestImportSchedules(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
ifc.createIfcDocumentInformation(Identification="Y", Name="FOOBAZ")
document = ifc.createIfcDocumentInformation(Identification="X", Name="FOOBAR", Scope="SCHEDULE")
subject.import_documents("SCHEDULE")
props = tool.Drawing.get_document_props()
assert props.schedules[0].ifc_definition_id == document.id()
assert props.schedules[0].identification == "X"
assert props.schedules[0].name == "FOOBAR"
def test_run_ifc2x3(self):
ifc = ifcopenshell.file(schema="IFC2X3")
tool.Ifc.set(ifc)
ifc.createIfcDocumentInformation(DocumentId="Y", Name="FOOBAZ")
document = ifc.createIfcDocumentInformation(DocumentId="X", Name="FOOBAR", Scope="SCHEDULE")
subject.import_documents("SCHEDULE")
props = tool.Drawing.get_document_props()
assert props.schedules[0].ifc_definition_id == document.id()
assert props.schedules[0].identification == "X"
assert props.schedules[0].name == "FOOBAR"
class TestImportReferences(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
ifc.createIfcDocumentInformation(Identification="Y", Name="FOOBAZ")
document = ifc.createIfcDocumentInformation(Identification="X", Name="FOOBAR", Scope="REFERENCE")
subject.import_documents("REFERENCE")
props = tool.Drawing.get_document_props()
assert props.references[0].ifc_definition_id == document.id()
assert props.references[0].identification == "X"
assert props.references[0].name == "FOOBAR"
def test_run_ifc2x3(self):
ifc = ifcopenshell.file(schema="IFC2X3")
tool.Ifc.set(ifc)
ifc.createIfcDocumentInformation(DocumentId="Y", Name="FOOBAZ")
document = ifc.createIfcDocumentInformation(DocumentId="X", Name="FOOBAR", Scope="REFERENCE")
subject.import_documents("REFERENCE")
props = tool.Drawing.get_document_props()
assert props.references[0].ifc_definition_id == document.id()
assert props.references[0].identification == "X"
assert props.references[0].name == "FOOBAR"
class TestImportSheets(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
ifc.createIfcDocumentInformation(Identification="Y", Name="FOOBAZ")
document = ifc.createIfcDocumentInformation(Identification="X", Name="FOOBAR", Scope="SHEET")
subject.import_sheets()
props = tool.Drawing.get_document_props()
assert props.sheets[0].ifc_definition_id == document.id()
assert props.sheets[0].identification == "X"
assert props.sheets[0].name == "FOOBAR"
def test_run_ifc2x3(self):
ifc = ifcopenshell.file(schema="IFC2X3")
tool.Ifc.set(ifc)
ifc.createIfcDocumentInformation(DocumentId="Y", Name="FOOBAZ")
document = ifc.createIfcDocumentInformation(DocumentId="X", Name="FOOBAR", Scope="SHEET")
subject.import_sheets()
props = tool.Drawing.get_document_props()
assert props.sheets[0].ifc_definition_id == document.id()
assert props.sheets[0].identification == "X"
assert props.sheets[0].name == "FOOBAR"
class TestImportTextAttributes(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
obj = bpy.data.objects.new("Object", None)
element = ifc.createIfcAnnotation()
element.Representation = ifc.createIfcProductDefinitionShape()
context = ifc.createIfcGeometricRepresentationSubContext(ContextType="Plan", ContextIdentifier="Annotation")
item = ifc.createIfcTextLiteralWithExtent(Literal="Literal", Path="RIGHT", BoxAlignment="bottom-left")
representation = ifc.createIfcShapeRepresentation(ContextOfItems=context, Items=[item])
element.Representation.Representations = [representation]
element.ObjectType = "TEXT"
tool.Ifc.link(element, obj)
subject.import_text_attributes(obj)
props = tool.Drawing.get_text_props(obj)
assert props.font_size == "2.5"
literal_props = props.literals[0]
assert literal_props.ifc_definition_id == item.id()
assert literal_props.box_alignment[:] == tuple([False] * 6 + [True] + [False] * 2)
assert literal_props.attributes["Literal"].string_value == "Literal"
assert literal_props.attributes["Path"].enum_value == "RIGHT"
assert literal_props.attributes["BoxAlignment"].string_value == "bottom-left"
class TestReplaceTextLiteralVariables(NewFile):
def test_using_attribute_variables(self):
ifc = ifcopenshell.file()
wall = ifc.create_entity("IfcWall", Name="Baz")
text = "Foo {{Name}} Bar"
updated_text = subject.replace_text_literal_variables(text, wall)
assert updated_text == "Foo Baz Bar"
def test_using_property_variables(self):
ifc = ifcopenshell.file()
wall = ifc.createIfcWall()
pset = ifcopenshell.api.pset.add_pset(ifc, name="Custom_Pset", product=wall)
ifcopenshell.api.pset.edit_pset(ifc, pset=pset, properties={"Key": "Baz"})
text = "Foo {{Custom_Pset.Key}} Bar"
updated_text = subject.replace_text_literal_variables(text, wall)
assert updated_text == "Foo Baz Bar"
class TestEditText(NewFile):
def test_change_text_font_size(self):
TestGetTextLiteral().test_run()
obj = bpy.data.objects["Object"]
assert DecoratorData.get_text_data(obj)["FontSize"] == 2.5
with bpy.context.temp_override(active_object=obj):
bpy.ops.bim.enable_editing_text()
props = tool.Drawing.get_text_props(obj)
props.font_size = "7.0"
bpy.ops.bim.edit_text()
annotation_classes = ifcopenshell.util.element.get_pset(tool.Ifc.get_entity(obj), "EPset_Annotation", "Classes")
assert "title" in annotation_classes
assert DecoratorData.get_text_data(obj)["FontSize"] == 7.0
def test_add_second_literal(self, setup=True):
if setup:
TestGetTextLiteral().test_run()
obj = bpy.data.objects["Object"]
with bpy.context.temp_override(active_object=obj):
bpy.ops.bim.enable_editing_text()
bpy.ops.bim.add_text_literal()
props = tool.Drawing.get_text_props(obj)
literal = props.literals[1]
literal.attributes["Literal"].string_value = "test_value"
bpy.ops.bim.edit_text()
ifc = tool.Ifc.get()
assert ifc.by_type("IfcTextLiteralWithExtent")[1].Literal == "test_value"
class TestDisableTextEditing(NewFile):
def test_disable_text_editing(self):
# add second literal and change font size to test disable editing keeps those changes.
TestEditText.test_change_text_font_size(self) # Set font size to "7.0".
TestEditText.test_add_second_literal(self, setup=False)
obj = bpy.data.objects["Object"]
props = tool.Drawing.get_text_props(obj)
assert obj is not None, obj
with bpy.context.temp_override(active_object=obj):
bpy.ops.bim.enable_editing_text()
bpy.ops.bim.remove_text_literal(literal_prop_id=1)
props.literals[0].attributes["Literal"].string_value = "changed_value"
props.font_size = "2.5"
bpy.ops.bim.disable_editing_text()
ifc = tool.Ifc.get()
# Test font size.
annotation_classes = ifcopenshell.util.element.get_pset(tool.Ifc.get_entity(obj), "EPset_Annotation", "Classes")
assert "title" in annotation_classes
text_data = DecoratorData.get_text_data(obj)
assert text_data["FontSize"] == 7.0
# Test second literal is present.
assert text_data["Literals"][1]["Literal"] == "test_value"
assert ifc.by_type("IfcTextLiteralWithExtent")[1].Literal == "test_value"
# Test first literal value is unchanged.
assert text_data["Literals"][0]["Literal"] == "Literal"
assert ifc.by_type("IfcTextLiteralWithExtent")[0].Literal == "Literal"
class TestImportAssignedProduct(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
wall = ifc.createIfcWall()
label = ifc.createIfcAnnotation()
ifcopenshell.api.drawing.assign_product(ifc, relating_product=wall, related_object=label)
wall_obj = bpy.data.objects.new("Object", None)
label_obj = bpy.data.objects.new("Object", None)
tool.Ifc.link(wall, wall_obj)
tool.Ifc.link(label, label_obj)
subject.import_assigned_product(label_obj)
props = subject.get_object_assigned_product_props(label_obj)
assert props.relating_product == wall_obj
def test_doing_nothing_if_no_product_to_import(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
label = ifc.createIfcAnnotation()
label_obj = bpy.data.objects.new("Object", None)
tool.Ifc.link(label, label_obj)
subject.import_assigned_product(label_obj)
props = subject.get_object_assigned_product_props(label_obj)
assert props.relating_product is None
class TestOpenSchedule(NewFile):
def open_spreadsheet(self):
pass
class TestOpenReference(NewFile):
def open_svg(self):
pass
class TestOpenSvg(NewFile):
def test_nothing(self):
pass
class TestRunRootAssignClassOperator(NewFile):
def test_nothing(self):
pass
class TestSetDrawingCollectionName(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
group = ifc.createIfcGroup(Name="Foobaz")
collection = bpy.data.collections.new("Foobar")
subject.set_drawing_collection_name(group, collection)
assert collection.name == "IfcGroup/Foobaz"
class TestSetName(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
drawing = ifc.createIfcAnnotation()
subject.set_name(drawing, "Name")
assert drawing.Name == "Name"
class TestShowDecorations(NewFile):
def test_run(self):
props = tool.Drawing.get_document_props()
props.should_draw_decorations = False
subject.show_decorations()
assert props.should_draw_decorations is True
class TestDrawingMaintainingSheetPosition(NewFile):
def get_sheet_drawing_data(self, layout_path: Path) -> dict[str, tuple[float, ...]]:
SVG = "{http://www.w3.org/2000/svg}"
ET.register_namespace("", SVG)
layout_tree = ET.parse(layout_path)
layout_root = layout_tree.getroot()
drawing_view = layout_root.findall(f'{SVG}g[@data-type="drawing"]')[0]
drawing_data: dict[str, tuple[float, ...]] = {}
for image in drawing_view.findall(f"{SVG}image"):
attribs = ["x", "y", "width", "height"]
image_type = image.attrib["data-type"]
drawing_data[image_type] = tuple([round(float(image.attrib[attr]), 2) for attr in attribs])
return drawing_data
def test_run(self):
props = tool.Drawing.get_document_props()
bpy.ops.bim.create_project()
tool.Project.save_test_project()
ifc = tool.Ifc.get()
sheet_path = Path(tool.Ifc.get_path()).parent / "layouts" / "A01 - UNTITLED.svg"
bpy.ops.mesh.primitive_cube_add(size=10, location=(0, 0, 4))
obj = bpy.data.objects["Cube"]
bpy.ops.bim.assign_class(ifc_class="IfcActuator", predefined_type="ELECTRICACTUATOR", userdefined_type="")
bpy.ops.bim.load_sheets()
bpy.ops.bim.add_sheet()
bpy.ops.bim.load_drawings()
for d in props.drawings:
d.is_expanded = True
bpy.ops.bim.add_drawing()
drawing = ifc.by_type("IfcAnnotation")[0]
for i, d in enumerate(props.drawings):
if d.ifc_definition_id == drawing.id():
props.active_drawing_index = i
bpy.ops.bim.activate_drawing(drawing=drawing.id())
bpy.ops.bim.create_drawing()
bpy.ops.bim.add_drawing_to_sheet()
bpy.ops.bim.open_sheet()
# uncomment if debugging
# without `sleep` `bim.open_sheet` tend to open sheet in browser
# with PLAN_VIEW.svg that will be created only later
# from time import sleep
# sleep(0.1)
# check drawing default position
drawing_data = self.get_sheet_drawing_data(sheet_path)
assert drawing_data["foreground"] == (30.0, 30.0, 500.0, 500.0)
assert drawing_data["view-title"] == (30.0, 535.0, 50.22, 10.0)
assert (scene := bpy.context.scene) and (camera := scene.camera)
props = tool.Drawing.get_camera_props(camera)
props.width = 25
props.height = 25
tool.Blender.force_depsgraph_update()
bpy.ops.bim.create_drawing()
bpy.ops.bim.open_sheet()
assert sheet_path.is_file(), f"Sheet path {sheet_path} doesn't exist"
# check drawing position on the sheet
drawing_data = self.get_sheet_drawing_data(sheet_path)
assert drawing_data["foreground"] == (30.0, 30.0, 500.0, 500.0)
assert drawing_data["view-title"] == (30.0, 535.0, 50.22, 10.0)
class TestDrawingStyles(NewFile):
def setup_project_with_drawing(self):
bpy.ops.bim.create_project()
tool.Project.save_test_project()
bpy.ops.bim.load_drawings()
bpy.ops.bim.add_drawing()
ifc = tool.Ifc.get()
drawing = ifc.by_type("IfcAnnotation")[0]
bpy.ops.bim.toggle_target_view(option="EXPAND", target_view="PLAN_VIEW")
props = tool.Drawing.get_document_props()
props.active_drawing_index = 2
bpy.ops.bim.activate_drawing(drawing=drawing.id())
props = tool.Drawing.get_document_props()
self.drawing_styles = props.drawing_styles
def test_drawing_styles_are_loaded_even_if_underlay_is_inactive(self):
self.setup_project_with_drawing()
assert len(self.drawing_styles) == 3
def test_drawing_styles_reload(self):
self.setup_project_with_drawing()
self.drawing_styles.clear()
bpy.ops.bim.reload_drawing_styles()
assert len(self.drawing_styles) == 3
class TestAddReferenceImage(NewFile):
def test_run(self):
props = tool.Project.get_project_props()
props.template_file = "0"
bpy.ops.bim.create_project()
ifc_path = Path("test/files/temp/test.ifc").absolute()
bpy.ops.bim.save_project(filepath=str(ifc_path), should_save_as=True)
filepath = Path("test/files/image.jpg").absolute()
bpy.ops.bim.add_reference_image(filepath=str(filepath))
obj = bpy.data.objects["IfcAnnotation/image"]
assert obj is not None
assert tool.Cad.are_vectors_equal(obj.dimensions, Vector((3.53982, 2.0, 0.0)))
material = obj.active_material
assert material
assert material.name == "image"
assert tool.Blender.get_ifc_definition_id(material) != 0
style = tool.Ifc.get_entity(material)
assert style
styled_items = set(tool.Style.get_styled_items(style))
representation_items = set(tool.Geometry.get_active_representation(obj).Items)
assert styled_items == representation_items
material_nodes = material.node_tree.nodes
texture_filepath = material_nodes["Image Texture"].image.filepath
texture_filepath = Path(tool.Blender.blender_path_to_posix(texture_filepath))
assert texture_filepath == filepath
uv_node = material_nodes["Texture Coordinate"]
assert len(uv_node.outputs["Generated"].links[:]) == 1
class TestAddReference(NewFile):
def test_add_single_reference(self):
"""Test adding a single reference file (backward compatibility)"""
bpy.ops.bim.create_project()
ifc_path = Path("test/files/temp/test.ifc").absolute()
bpy.ops.bim.save_project(filepath=str(ifc_path), should_save_as=True)
# Create a temporary SVG file
svg_path = Path("test/files/temp/reference.svg").absolute()
svg_path.parent.mkdir(parents=True, exist_ok=True)
with open(svg_path, "w") as f:
f.write('<svg xmlns="http://www.w3.org/2000/svg"></svg>')
try:
# Add single reference
bpy.ops.bim.add_reference(filepath=str(svg_path))
# Verify reference was added
ifc = tool.Ifc.get()
references = [doc for doc in ifc.by_type("IfcDocumentInformation") if doc.Scope == "REFERENCE"]
assert len(references) == 1
assert references[0].Name == "reference"
finally:
# Cleanup
if svg_path.exists():
svg_path.unlink()
def test_add_multiple_references(self):
"""Test adding multiple reference files at once"""
bpy.ops.bim.create_project()
ifc_path = Path("test/files/temp/test.ifc").absolute()
bpy.ops.bim.save_project(filepath=str(ifc_path), should_save_as=True)
# Create temporary SVG files
temp_dir = Path("test/files/temp").absolute()
temp_dir.mkdir(parents=True, exist_ok=True)
svg_files = []
for i in range(3):
svg_path = temp_dir / f"reference_{i}.svg"
with open(svg_path, "w") as f:
f.write('<svg xmlns="http://www.w3.org/2000/svg"></svg>')
svg_files.append(svg_path)
try:
# Test by directly calling core.add_document multiple times
# (simulating what the operator does with multiple files)
ifc = tool.Ifc.get()
for svg_file in svg_files:
uri = tool.Ifc.get_uri(str(svg_file), use_relative_path=True)
from bonsai.bim import core
core.drawing.add_document(tool.Ifc, tool.Drawing, "REFERENCE", uri=uri)
# Verify all references were added
references = [doc for doc in ifc.by_type("IfcDocumentInformation") if doc.Scope == "REFERENCE"]
assert len(references) == 3
reference_names = {ref.Name for ref in references}
expected_names = {f"reference_{i}" for i in range(3)}
assert reference_names == expected_names
finally:
# Cleanup
for svg_file in svg_files:
if svg_file.exists():
svg_file.unlink()