Files
IfcOpenShell/src/bonsai/test/tool/test_blender.py
T

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

186 lines
6.6 KiB
Python
Raw Normal View History

2024-08-13 15:47:27 +10:00
# Bonsai - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
2024-08-13 15:47:27 +10:00
# This file is part of Bonsai.
#
2024-08-13 15:47:27 +10:00
# 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.
#
2024-08-13 15:47:27 +10:00
# 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
2024-08-13 15:47:27 +10:00
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
#
# This file was modified with the assistance of an AI coding tool.
2023-12-08 16:57:05 +05:00
2026-03-04 15:41:39 +05:00
import tempfile
from pathlib import Path
2026-01-26 17:11:12 +05:00
from typing import TYPE_CHECKING
2023-12-08 16:57:05 +05:00
import bpy
import ifcopenshell
import numpy as np
2026-01-26 17:11:12 +05:00
import pytest
2026-03-13 13:30:02 +05:00
import bonsai
2024-08-14 13:56:39 +05:00
import bonsai.core.tool
import bonsai.tool as tool
from bonsai.tool.blender import Blender as subject
2026-01-26 17:11:12 +05:00
from test.bim.bootstrap import NewFile
2023-12-08 16:57:05 +05:00
2025-05-15 10:40:23 +05:00
if TYPE_CHECKING:
2025-06-04 11:01:48 +05:00
import bpy.stub_internal.rna_enums as rna_enums
2025-05-15 10:40:23 +05:00
2023-12-08 16:57:05 +05:00
class TestImplementsTool(NewFile):
def test_run(self):
2024-08-14 13:56:39 +05:00
assert isinstance(subject(), bonsai.core.tool.Blender)
2023-12-08 16:57:05 +05:00
class TestCopyNodeGraph(NewFile):
def test_run(self):
material_to = bpy.data.materials.new("material_to")
tool.Style.set_use_nodes(material_to, True)
2025-05-15 10:40:23 +05:00
assert material_to.node_tree
2023-12-08 16:57:05 +05:00
material_to_nodes = material_to.node_tree.nodes
assert len(material_to_nodes) == 2
for node in material_to_nodes:
material_to_nodes.remove(node)
assert len(material_to_nodes) == 0
material_from = bpy.data.materials.new("material_from")
tool.Style.set_use_nodes(material_from, True)
2023-12-08 16:57:05 +05:00
subject.copy_node_graph(material_to, material_from)
assert len(material_to_nodes) == 2
class TestSortPanelsForRegister(NewFile):
def test_run(self):
items = ["A", "B", "C", "D"]
items_to_parents = {"A": "D", "D": "C", "C": "B"}
sorted_items = subject.sort_panels_for_register(items, items_to_parents)
assert tuple(sorted_items) == ("B", "C", "D", "A")
with pytest.raises(AssertionError):
subject.sort_panels_for_register(items, {"A": "K"})
with pytest.raises(AssertionError):
subject.sort_panels_for_register(items, {"J": "A"})
class TestBlenderErrorMessageExtraction(NewFile):
def test_extract_operator_reports(self) -> None:
ERROR_REPORTS = ["ERROR!!!\nERROR", "ERROR"]
class OBJECT_OT_test_fail_operator(bpy.types.Operator):
bl_idname = "object.test_fail_operator"
bl_label = "Test Fail Operator"
2025-05-15 10:40:23 +05:00
def execute(self, context) -> "set[rna_enums.OperatorReturnItems]":
self.report({"INFO"}, "Info message.")
subject.report_operator_errors(self, ERROR_REPORTS)
return {"FINISHED"}
bpy.utils.register_class(OBJECT_OT_test_fail_operator)
try:
bpy.ops.object.test_fail_operator()
except RuntimeError as e:
error_reports = subject.extract_error_reports(e)
assert error_reports == ERROR_REPORTS
bpy.utils.unregister_class(OBJECT_OT_test_fail_operator)
def test_ignore_actual_runtime_errors_from_operators(self) -> None:
class OBJECT_OT_test_fail_operator(bpy.types.Operator):
bl_idname = "object.test_fail_operator"
bl_label = "Test Fail Operator"
def execute(self, context):
raise RuntimeError("Intentional runtime error.")
bpy.utils.register_class(OBJECT_OT_test_fail_operator)
try:
bpy.ops.object.test_fail_operator()
except RuntimeError as e:
error_reports = subject.extract_error_reports(e)
assert error_reports == []
bpy.utils.unregister_class(OBJECT_OT_test_fail_operator)
class TestGetSelectedFiles(NewFile):
def test_get_a_single_file(self) -> None:
with tempfile.NamedTemporaryFile() as f:
file = type("", (object,), {"name": f.name})()
assert subject.get_selected_files(Path(f.name).parent, [file]) == [f.name]
def test_get_multiple_files(self) -> None:
with tempfile.NamedTemporaryFile() as f:
with tempfile.NamedTemporaryFile() as g:
file = type("", (object,), {"name": f.name})()
file2 = type("", (object,), {"name": g.name})()
assert subject.get_selected_files(Path(f.name).parent, [file, file2]) == [f.name, g.name]
def test_exclude_directories(self) -> None:
with tempfile.NamedTemporaryFile() as f:
with tempfile.TemporaryDirectory() as d:
file = type("", (object,), {"name": f.name})()
directory = type("", (object,), {"name": d})()
assert subject.get_selected_files(Path(f.name).parent, [file, directory]) == [f.name]
def test_get_relative_paths(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
base_path = Path(tmp_dir)
with tempfile.NamedTemporaryFile(dir=tmp_dir, suffix=".ifc") as f:
tool.Ifc.set_path(str(f.name))
with tempfile.NamedTemporaryFile(dir=tmp_dir) as g:
file = type("", (object,), {"name": g.name})
assert subject.get_selected_files(Path(g.name).parent, [file], use_relative_path=True) == [
Path(g.name).name
]
2026-03-13 13:30:02 +05:00
class TestGetDebugInfo(NewFile):
# Only keys that are safe to set if Bonsai fails to load.
EXPECTED_KEYS = {
"os",
"os_version",
"python_version",
"architecture",
"machine",
"processor",
"blender_version",
"bonsai_version",
"bonsai_commit_hash",
"bonsai_commit_date",
"last_actions",
"last_error",
}
def test_failed_to_load_returns_only_base_keys(self):
info = bonsai.get_debug_info(bonsai_failed_to_load=True)
assert set(info.keys()) == self.EXPECTED_KEYS
class TestNpFrombufferLegacy(NewFile):
"""Decoding ``n`` floats from a buffer must yield a length-``n`` array
regardless of whether the buffer was written as ``float32`` or ``float64``."""
@pytest.mark.parametrize("n", [3, 9])
@pytest.mark.parametrize("dtype", [np.float32, np.float64])
def test_decodes_to_n_elements(self, n, dtype):
data = np.arange(n, dtype=dtype).tobytes()
result = subject.np_frombuffer_legacy(data, n)
assert result.shape == (n,)
np.testing.assert_allclose(result, np.arange(n))