diff --git a/src/bonsai/Makefile b/src/bonsai/Makefile index d5a2ac99b5..d7e799ae0d 100644 --- a/src/bonsai/Makefile +++ b/src/bonsai/Makefile @@ -362,11 +362,7 @@ endif .PHONY: test-modal test-modal: -ifndef MODULE - blender --enable-event-simulate --python test/modal/test_snap.py -else - blender --enable-event-simulate --python test/modal/test_$(MODULE).py -endif + blender --enable-event-simulate --python test/modal/test_modal.py # Reregistering test is not added to the standard test suite because during unregister # Blender removes all Bonsai dependencies breaking dev-environment symlinks. diff --git a/src/bonsai/test/modal/test_modal.py b/src/bonsai/test/modal/test_modal.py new file mode 100644 index 0000000000..4c88c1903f --- /dev/null +++ b/src/bonsai/test/modal/test_modal.py @@ -0,0 +1,276 @@ +# Bonsai - OpenBIM Blender Add-on +# Copyright (C) 2026 Bruno Perdigão +# +# 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 . + + +import inspect +import os +import sys + +import bpy +import ifcopenshell +import pytest + +from bonsai import tool as tool +from bonsai.bim.ifc import IfcStore +from bonsai.bim.module.model.data import AuthoringData as Model + +GREEN = "\033[32m" +RED = "\033[31m" +RESET = "\033[0m" + + +def _assert_pass(message: str) -> None: + caller_name = inspect.stack()[1].function + print(f"{GREEN}{caller_name} PASSED: {message}{RESET}") + + +def _handle_error(e: Exception, on_done) -> None: + print(f"{RED}Assertion failed: {e}{RESET}") + if on_done: + on_done() + + +def run_iter_from_timer(event_iter, on_complete=None, on_error=None): + i = iter(event_iter) + done = False + + def event_step(): + nonlocal done, on_complete + try: + ret = next(i, "STOP") + if ret in (None, "STOP", "FINISHED"): + done = True + if on_complete: + on_complete() + return None + except StopIteration: + done = True + if on_complete: + on_complete() + return None + except Exception as e: + done = True + print(f"Exception: {e}") + if on_error: + on_error(e) + elif on_complete: + on_complete() + return None + return 0.0 + + bpy.app.timers.register(event_step, first_interval=0.0) + + +def preset_event_simulate(window, event_type, value, x, y): + if value == "TAP": + yield window.event_simulate(event_type, "PRESS", x=x, y=y) + yield window.event_simulate(event_type, "RELEASE", x=x, y=y) + else: + yield window.event_simulate(event_type, value, x=x, y=y) + + +def cleanup(): + bpy.app.use_event_simulate = False + bpy.ops.wm.quit_blender() + + +def _get_valid_window() -> bpy.types.Window: + win = bpy.context.window + if win is not None: + return win + wm = getattr(bpy.context, "window_manager", None) + if wm and wm.windows: + return wm.windows[0] + raise RuntimeError("Unable to locate a Blender UI window.") + + +def new_project(): + IfcStore.purge() + bpy.ops.wm.read_homefile(app_template="", use_factory_startup=True) + if len(bpy.data.objects) > 0: + bpy.data.batch_remove(bpy.data.objects) + bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True) + if len(bpy.data.materials) > 0: + bpy.data.batch_remove(bpy.data.materials) + bpy.context.scene.unit_settings.system = "METRIC" + bpy.context.scene.unit_settings.length_unit = "MILLIMETERS" + props = tool.Project.get_project_props() + props.template_file = "0" + tool.Blender.get_addon_preferences().should_play_chaching_sound = False + + +def test_snap_object_detection(window, x, y): + yield from preset_event_simulate(window, "ESC", "TAP", x, y) + + measure_settings = tool.Project.get_measure_tool_settings() + measure_settings.measurement_type = "POLYLINE" + for obj in tool.Blender.get_selected_objects(): + obj.select_set(False) + for area in bpy.context.screen.areas: + if area.type == "VIEW_3D": + for region in area.regions: + if region.type == "WINDOW": + with bpy.context.temp_override(area=area, region=region, space_data=area.spaces[0]): + bpy.ops.bim.measure_tool("INVOKE_DEFAULT", measure_type="POLYLINE") + break + + yield from preset_event_simulate(window, "MOUSEMOVE", "NOTHING", x, y) + yield from preset_event_simulate(window, "LEFTMOUSE", "TAP", x, y) + snap_point = tool.Model.get_polyline_props().snap_mouse_point[0] + assert_msg = "First click should have a snap_object" + assert snap_point.snap_object, assert_msg + _assert_pass(assert_msg) + assert_msg = "snap_object should be a string with the object name" + assert type(snap_point.snap_object) == str, assert_msg + _assert_pass(assert_msg) + assert_msg = "Object should be an IfcWall" + assert snap_point.snap_object.split("/")[0] == "IfcWall", assert_msg + _assert_pass(assert_msg) + + offset = 200 + yield from preset_event_simulate(window, "MOUSEMOVE", "NOTHING", x - offset, y) + yield from preset_event_simulate(window, "MOUSEMOVE", "NOTHING", x - offset, y) + yield from preset_event_simulate(window, "MOUSEMOVE", "NOTHING", x - offset, y) + yield from preset_event_simulate(window, "MOUSEMOVE", "NOTHING", x - offset, y) + yield from preset_event_simulate(window, "LEFTMOUSE", "TAP", x - offset, y) + snap_point = tool.Model.get_polyline_props().snap_mouse_point[0] + assert_msg = "Second click should not have a snap_object" + assert not snap_point.snap_object, assert_msg + _assert_pass(assert_msg) + + yield from preset_event_simulate(window, "RET", "TAP", x, y) + yield "FINISHED" + + +def test_snap_in_xray_mode(window, x, y): + area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") + area.spaces[0].shading.show_xray = True + + yield from preset_event_simulate(window, "ESC", "TAP", x, y) + + measure_settings = tool.Project.get_measure_tool_settings() + measure_settings.measurement_type = "POLYLINE" + for obj in tool.Blender.get_selected_objects(): + obj.select_set(False) + for area in bpy.context.screen.areas: + if area.type == "VIEW_3D": + for region in area.regions: + if region.type == "WINDOW": + with bpy.context.temp_override(area=area, region=region, space_data=area.spaces[0]): + bpy.ops.bim.measure_tool("INVOKE_DEFAULT", measure_type="POLYLINE") + break + + yield from preset_event_simulate(window, "MOUSEMOVE", "NOTHING", x, y) + yield from preset_event_simulate(window, "LEFTMOUSE", "TAP", x, y) + snap_point = tool.Model.get_polyline_props().snap_mouse_point[0] + assert_msg = "First click should have a snap_object" + assert snap_point.snap_object, assert_msg + _assert_pass(assert_msg) + assert_msg = "snap_object should be a string with the object name" + assert type(snap_point.snap_object) == str, assert_msg + _assert_pass(assert_msg) + assert_msg = "Object should be an IfcFurniture" + assert snap_point.snap_object.split("/")[0] == "IfcFurniture", assert_msg + _assert_pass(assert_msg) + yield "FINISHED" + + +def test_draw_polyline_wall(window, x, y): + yield from preset_event_simulate(window, "ESC", "TAP", x, y) + + for obj in tool.Blender.get_selected_objects(): + obj.select_set(False) + for area in bpy.context.screen.areas: + if area.type == "VIEW_3D": + for region in area.regions: + if region.type == "WINDOW": + with bpy.context.temp_override(area=area, region=region, space_data=area.spaces[0]): + props = tool.Model.get_model_props() + ifc = tool.Ifc.get() + relating_type = ifc.by_type("IfcWallType")[0] + + if tool.Model.get_usage_type(relating_type) == "LAYER2": + props.ifc_class = "IfcWallType" + props.relating_type_id = str(relating_type.id()) + + bpy.ops.bim.draw_polyline_wall("INVOKE_DEFAULT") + break + + yield from preset_event_simulate(window, "MOUSEMOVE", "NOTHING", x, y) + yield from preset_event_simulate(window, "MOUSEMOVE", "NOTHING", x, y) + yield from preset_event_simulate(window, "MOUSEMOVE", "NOTHING", x, y) + yield from preset_event_simulate(window, "MOUSEMOVE", "NOTHING", x, y) + yield from preset_event_simulate(window, "LEFTMOUSE", "TAP", x, y) + yield from preset_event_simulate(window, "X", "TAP", x, y) + + offset = 200 + yield from preset_event_simulate(window, "MOUSEMOVE", "NOTHING", x + offset, y) + yield from preset_event_simulate(window, "MOUSEMOVE", "NOTHING", x + offset, y) + yield from preset_event_simulate(window, "MOUSEMOVE", "NOTHING", x + offset, y) + yield from preset_event_simulate(window, "MOUSEMOVE", "NOTHING", x + offset, y) + yield from preset_event_simulate(window, "LEFTMOUSE", "TAP", x + offset, y) + + yield from preset_event_simulate(window, "RET", "TAP", x, y) + element = tool.Ifc.get_entity(bpy.context.selected_objects[0]) + + assert_msg = "Created object should be IfcWall" + assert element.is_a() == "IfcWall" + _assert_pass(assert_msg) + assert_msg = "Created object should be typed by IfcWallType" + assert ifcopenshell.util.element.get_type(element).is_a() == "IfcWallType" + _assert_pass(assert_msg) + # TODO Asset the axis has the same X value + + yield "FINISHED" + + +def run_tests(): + module_name = os.getenv("MODULE", "snap") + if module_name == "wall": + filepath = f"./test/files/wall.ifc" + bpy.ops.bim.load_project(filepath=filepath) + window = _get_valid_window() + test_queue = [lambda w=window: test_draw_polyline_wall(w, 960, 540)] + elif module_name == "snap": + filepath = f"./test/files/snap.ifc" + bpy.ops.bim.load_project(filepath=filepath) + window = _get_valid_window() + test_queue = [ + lambda w=window: test_snap_object_detection(w, 960, 540), + lambda w=window: test_snap_in_xray_mode(w, 1200, 540), + ] + else: + cleanup() + + def _next(): + if not test_queue: + cleanup() + return + test_fn = test_queue.pop(0) + # use the shared timer infrastructure + run_iter_from_timer( + test_fn(), + on_complete=_next, + on_error=lambda e: _handle_error(e, lambda: None), + ) + + _next() + +if __name__ == "__main__": + new_project() + run_tests() diff --git a/src/bonsai/test/modal/test_snap.py b/src/bonsai/test/modal/test_snap.py deleted file mode 100644 index 6558c2fff0..0000000000 --- a/src/bonsai/test/modal/test_snap.py +++ /dev/null @@ -1,213 +0,0 @@ -import inspect -import time - -import bpy -import pytest - -import bonsai.tool as tool -from bonsai.bim.ifc import IfcStore - - -def _assert_pass(message: str) -> None: - caller_name = inspect.stack()[1].function - GREEN = "\033[32m" - RESET = "\033[0m" - print(f"{GREEN}{caller_name} PASSED: {message}{RESET}") - - -def _handle_error(e: Exception, on_done) -> None: - RED = "\033[31m" - RESET = "\033[0m" - print(f"{RED}Assertion failed: {e}{RESET}") - on_done() - - -def run_iter_from_timer(event_iter, on_complete=None, on_error=None): - i = iter(event_iter) - done = False - - def event_step(): - nonlocal done, on_complete - try: - ret = next(i, "STOP") - # print(f"Iter step returned: {ret!r}") - if ret is None or ret == "STOP" or ret == "FINISHED": - done = True - # print("Iterator done, calling on_complete") - if on_complete: - on_complete() - return None - except StopIteration: - done = True - # print("StopIteration, calling on_complete") - if on_complete: - on_complete() - return None - except Exception as e: - done = True - print(f"Exception: {e}") - if on_error: - on_error(e) - elif on_complete: - on_complete() - return None - return 0.0 - - bpy.app.timers.register(event_step, first_interval=0.0) - - -def preset_event_simulate(window, event_type, value, x, y): - if value == "TAP": - yield window.event_simulate(event_type, "PRESS", x=x, y=y) - yield window.event_simulate(event_type, "RELEASE", x=x, y=y) - else: - yield window.event_simulate(event_type, value, x=x, y=y) - - -def test_snap_object_detection(window, x, y): - yield from preset_event_simulate(window, "ESC", "TAP", x, y) - - measure_settings = tool.Project.get_measure_tool_settings() - measure_settings.measurement_type = "POLYLINE" - for obj in tool.Blender.get_selected_objects(): - obj.select_set(False) - for area in bpy.context.screen.areas: - if area.type == "VIEW_3D": - for region in area.regions: - if region.type == "WINDOW": - with bpy.context.temp_override( - area=area, region=region, space_data=area.spaces[0] - ): - bpy.ops.bim.measure_tool("INVOKE_DEFAULT", measure_type="POLYLINE") - break - - yield from preset_event_simulate(window, "MOUSEMOVE", "NOTHING", x, y) - yield from preset_event_simulate(window, "LEFTMOUSE", "TAP", x, y) - snap_point = tool.Model.get_polyline_props().snap_mouse_point[0] - assert snap_point.snap_object, "First click should have a snap_object" - _assert_pass("First click should have a snap_object") - assert type(snap_point.snap_object) == str, "snap_object should be an object name string." - _assert_pass("snap_object should be an object name string.") - assert snap_point.snap_object.split("/")[0] == "IfcWall", "Object should be an IfcWall" - _assert_pass("Object should be an IfcWall") - - offset = 200 - yield from preset_event_simulate(window, "MOUSEMOVE", "NOTHING", x-offset, y) - yield from preset_event_simulate(window, "MOUSEMOVE", "NOTHING", x-offset, y) - yield from preset_event_simulate(window, "MOUSEMOVE", "NOTHING", x-offset, y) - yield from preset_event_simulate(window, "MOUSEMOVE", "NOTHING", x-offset, y) - yield from preset_event_simulate(window, "LEFTMOUSE", "TAP", x-offset, y) - snap_point = tool.Model.get_polyline_props().snap_mouse_point[0] - assert not snap_point.snap_object, "Second click should not have a snap_object" - _assert_pass("Second click should not have a snap_object") - - yield from preset_event_simulate(window, "RET", "TAP", x, y) - yield "FINISHED" - - -def test_snap_in_xray_mode(window, x, y): - area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") - area.spaces[0].shading.show_xray = True - - yield from preset_event_simulate(window, "ESC", "TAP", x, y) - - measure_settings = tool.Project.get_measure_tool_settings() - measure_settings.measurement_type = "POLYLINE" - for obj in tool.Blender.get_selected_objects(): - obj.select_set(False) - for area in bpy.context.screen.areas: - if area.type == "VIEW_3D": - for region in area.regions: - if region.type == "WINDOW": - with bpy.context.temp_override( - area=area, region=region, space_data=area.spaces[0] - ): - bpy.ops.bim.measure_tool("INVOKE_DEFAULT", measure_type="POLYLINE") - break - - yield from preset_event_simulate(window, "MOUSEMOVE", "NOTHING", x, y) - yield from preset_event_simulate(window, "LEFTMOUSE", "TAP", x, y) - snap_point = tool.Model.get_polyline_props().snap_mouse_point[0] - assert snap_point.snap_object, "First click should have a snap_object" - _assert_pass("First click should have a snap_object") - assert type(snap_point.snap_object) == str, "snap_object should be an object name string." - _assert_pass("snap_object should be an object name string.") - assert snap_point.snap_object.split("/")[0] == "IfcFurniture", "Object should be an IfcFurniture" - _assert_pass("Object should be an IfcFurniture") - yield "FINISHED" - - -def cleanup(): - # print("CLEANUP CALLED") - bpy.app.use_event_simulate = False - bpy.ops.wm.quit_blender() - - -def get_test_queue(window): - """Returns a list of test callables to run sequentially, each in its own timer.""" - return [ - lambda w=window: test_snap_object_detection(w, 960, 540), - lambda w=window: test_snap_in_xray_mode(w, 1200, 540), - ] - -def _get_valid_window() -> bpy.types.Window: - """Return a Blender window that works even when ``bpy.context.window`` - is ``None`` (e.g. during a temporary operator context).""" - win = bpy.context.window - if win is not None: - return win - wm = getattr(bpy.context, "window_manager", None) - if wm and wm.windows: - return wm.windows[0] - raise RuntimeError("Unable to locate a Blender UI window.") - -def new_project(): - IfcStore.purge() - bpy.ops.wm.read_homefile(app_template="", use_factory_startup=True) - if len(bpy.data.objects) > 0: - bpy.data.batch_remove(bpy.data.objects) - bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True) - if len(bpy.data.materials) > 0: - bpy.data.batch_remove(bpy.data.materials) - bpy.context.scene.unit_settings.system = "METRIC" - bpy.context.scene.unit_settings.length_unit = "MILLIMETERS" - props = tool.Project.get_project_props() - props.template_file = "0" - tool.Blender.get_addon_preferences().should_play_chaching_sound = False - - -@pytest.mark.snap -def run_tests(window): - tests = get_test_queue(window) - def run_next(): - # print(f"run_next called, {len(tests)} tests remaining, id={id(tests)}") - if not tests: - # print("No tests left, calling cleanup") - cleanup() - return - test_fn = tests.pop(0) - # print(f"Running test: {test_fn}") - current_tests = list(tests) - def on_done(): - # print(f"on_done called, tests had {len(current_tests)} items") - if current_tests: - run_next() - else: - cleanup() - run_iter_from_timer( - test_fn(), - on_complete=on_done, - on_error=lambda e: _handle_error(e, on_done), - ) - - run_next() - - -if __name__ == "__main__": - new_project() - filepath = "./test/files/snap.ifc" - bpy.ops.bim.load_project(filepath=filepath) - # load_project may clear the context.window; reacquire a valid one. - window = _get_valid_window() - run_tests(window) - diff --git a/src/bonsai/test/modal/test_wall.py b/src/bonsai/test/modal/test_wall.py deleted file mode 100644 index b6038ea95a..0000000000 --- a/src/bonsai/test/modal/test_wall.py +++ /dev/null @@ -1,193 +0,0 @@ -import inspect -import time - -import bpy -import ifcopenshell -import pytest - -import bonsai.tool as tool -from bonsai.bim.ifc import IfcStore -from bonsai.bim.module.model.data import AuthoringData - - -def _assert_pass(message: str) -> None: - caller_name = inspect.stack()[1].function - GREEN = "\033[32m" - RESET = "\033[0m" - print(f"{GREEN}{caller_name} PASSED: {message}{RESET}") - - -def _handle_error(e: Exception, on_done) -> None: - RED = "\033[31m" - RESET = "\033[0m" - print(f"{RED}Assertion failed: {e}{RESET}") - on_done() - - -def run_iter_from_timer(event_iter, on_complete=None, on_error=None): - i = iter(event_iter) - done = False - - def event_step(): - nonlocal done, on_complete - try: - ret = next(i, "STOP") - # print(f"Iter step returned: {ret!r}") - if ret is None or ret == "STOP" or ret == "FINISHED": - done = True - # print("Iterator done, calling on_complete") - if on_complete: - on_complete() - return None - except StopIteration: - done = True - # print("StopIteration, calling on_complete") - if on_complete: - on_complete() - return None - except Exception as e: - done = True - print(f"Exception: {e}") - if on_error: - on_error(e) - elif on_complete: - on_complete() - return None - return 0.0 - - bpy.app.timers.register(event_step, first_interval=0.0) - - -def preset_event_simulate(window, event_type, value, x, y): - if value == "TAP": - yield window.event_simulate(event_type, "PRESS", x=x, y=y) - yield window.event_simulate(event_type, "RELEASE", x=x, y=y) - else: - yield window.event_simulate(event_type, value, x=x, y=y) - - -def test_draw_polyline_wall(window, x, y): - yield from preset_event_simulate(window, "ESC", "TAP", x, y) - - for obj in tool.Blender.get_selected_objects(): - obj.select_set(False) - for area in bpy.context.screen.areas: - if area.type == "VIEW_3D": - for region in area.regions: - if region.type == "WINDOW": - with bpy.context.temp_override( - area=area, region=region, space_data=area.spaces[0] - ): - props = tool.Model.get_model_props() - ifc = tool.Ifc.get() - relating_type = ifc.by_type("IfcWallType")[0] - - if tool.Model.get_usage_type(relating_type) == "LAYER2": - print("FOI!") - props.ifc_class = "IfcWallType" - props.relating_type_id = str(relating_type.id()) - # print(wall_type) - - bpy.ops.bim.draw_polyline_wall("INVOKE_DEFAULT") - break - - yield from preset_event_simulate(window, "MOUSEMOVE", "NOTHING", x, y) - yield from preset_event_simulate(window, "MOUSEMOVE", "NOTHING", x, y) - yield from preset_event_simulate(window, "MOUSEMOVE", "NOTHING", x, y) - yield from preset_event_simulate(window, "MOUSEMOVE", "NOTHING", x, y) - yield from preset_event_simulate(window, "LEFTMOUSE", "TAP", x, y) - yield from preset_event_simulate(window, "X", "TAP", x, y) - - offset = 200 - yield from preset_event_simulate(window, "MOUSEMOVE", "NOTHING", x+offset, y) - yield from preset_event_simulate(window, "MOUSEMOVE", "NOTHING", x+offset, y) - yield from preset_event_simulate(window, "MOUSEMOVE", "NOTHING", x+offset, y) - yield from preset_event_simulate(window, "MOUSEMOVE", "NOTHING", x+offset, y) - yield from preset_event_simulate(window, "LEFTMOUSE", "TAP", x+offset, y) - - yield from preset_event_simulate(window, "RET", "TAP", x, y) - element = tool.Ifc.get_entity(bpy.context.selected_objects[0]) - - assert_msg = "Created object should be IfcWall" - assert element.is_a() == "IfcWall" - _assert_pass(assert_msg) - assert_msg = "Created object should be typed by IfcWallType" - assert ifcopenshell.util.element.get_type(element).is_a() == "IfcWallType" - _assert_pass(assert_msg) - # TODO Asset the axis has the same X value - - yield "FINISHED" - - -def cleanup(): - bpy.app.use_event_simulate = False - bpy.ops.wm.quit_blender() - - -def get_test_queue(window): - """Returns a list of test callables to run sequentially, each in its own timer.""" - return [ - lambda w=window: test_draw_polyline_wall(w, 960, 540), - ] - -def _get_valid_window() -> bpy.types.Window: - """Return a Blender window that works even when ``bpy.context.window`` - is ``None`` (e.g. during a temporary operator context).""" - win = bpy.context.window - if win is not None: - return win - wm = getattr(bpy.context, "window_manager", None) - if wm and wm.windows: - return wm.windows[0] - raise RuntimeError("Unable to locate a Blender UI window.") - -def new_project(): - IfcStore.purge() - bpy.ops.wm.read_homefile(app_template="", use_factory_startup=True) - if len(bpy.data.objects) > 0: - bpy.data.batch_remove(bpy.data.objects) - bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True) - if len(bpy.data.materials) > 0: - bpy.data.batch_remove(bpy.data.materials) - bpy.context.scene.unit_settings.system = "METRIC" - bpy.context.scene.unit_settings.length_unit = "MILLIMETERS" - props = tool.Project.get_project_props() - props.template_file = "0" - tool.Blender.get_addon_preferences().should_play_chaching_sound = False - - -@pytest.mark.snap -def run_tests(window): - tests = get_test_queue(window) - def run_next(): - # print(f"run_next called, {len(tests)} tests remaining, id={id(tests)}") - if not tests: - # print("No tests left, calling cleanup") - cleanup() - return - test_fn = tests.pop(0) - # print(f"Running test: {test_fn}") - current_tests = list(tests) - def on_done(): - # print(f"on_done called, tests had {len(current_tests)} items") - if current_tests: - run_next() - else: - cleanup() - run_iter_from_timer( - test_fn(), - on_complete=on_done, - on_error=lambda e: _handle_error(e, on_done), - ) - - run_next() - - -if __name__ == "__main__": - new_project() - filepath = "./test/files/wall.ifc" - bpy.ops.bim.load_project(filepath=filepath) - window = _get_valid_window() - run_tests(window) - -