2024-08-13 15:40:54 +10:00
|
|
|
# Bonsai - OpenBIM Blender Add-on
|
2021-08-17 08:55:29 +10:00
|
|
|
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
|
|
|
|
#
|
2024-08-13 15:40:54 +10:00
|
|
|
# This file is part of Bonsai.
|
2021-08-17 08:55:29 +10:00
|
|
|
#
|
2024-08-13 15:40:54 +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.
|
|
|
|
|
#
|
2024-08-13 15:40:54 +10:00
|
|
|
# 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
|
2024-08-13 15:40:54 +10:00
|
|
|
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
2021-08-17 08:55:29 +10:00
|
|
|
|
2025-01-29 12:40:03 +05:00
|
|
|
from __future__ import annotations
|
2026-01-26 17:11:12 +05:00
|
|
|
|
2021-01-20 16:53:34 +11:00
|
|
|
import os
|
2026-01-26 17:11:12 +05:00
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
|
|
|
|
|
|
import bpy
|
|
|
|
|
from bpy.types import Menu, Panel, UIList
|
|
|
|
|
|
2024-08-14 14:10:36 +05:00
|
|
|
import bonsai.bim
|
|
|
|
|
import bonsai.tool as tool
|
2026-01-26 17:11:12 +05:00
|
|
|
from bonsai.bim.helper import draw_attributes, prop_with_search
|
2024-08-14 14:10:36 +05:00
|
|
|
from bonsai.bim.ifc import IfcStore
|
2026-01-26 17:11:12 +05:00
|
|
|
from bonsai.bim.module.project.data import LinksData, ProjectData
|
2025-01-29 12:40:03 +05:00
|
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
2026-01-26 17:11:12 +05:00
|
|
|
from bonsai.bim.module.project.prop import (
|
|
|
|
|
BIMProjectProperties,
|
|
|
|
|
FilterCategory,
|
|
|
|
|
LibraryElement,
|
|
|
|
|
Link,
|
|
|
|
|
)
|
2021-01-05 21:15:52 +11:00
|
|
|
|
|
|
|
|
|
2024-07-30 23:56:42 +10:00
|
|
|
def file_import_menu(self, context):
|
|
|
|
|
op = self.layout.operator("bim.load_project", text="IFC (Geometry Only) (.ifc/.ifczip/.ifcxml)")
|
2024-09-13 11:34:39 +05:00
|
|
|
op.import_without_ifc_data = True
|
2024-07-30 23:56:42 +10:00
|
|
|
op.should_start_fresh_session = False
|
|
|
|
|
|
|
|
|
|
|
2025-02-07 10:53:44 -05:00
|
|
|
def refresh():
|
|
|
|
|
UIData.is_loaded = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class UIData:
|
|
|
|
|
data = {}
|
|
|
|
|
is_loaded = False
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def load(cls):
|
|
|
|
|
cls.data = {"menu_item_icon_color_mode": cls.icon_color_mode("user_interface.wcol_menu_item.text")}
|
|
|
|
|
cls.is_loaded = True
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def icon_color_mode(cls, color_path):
|
|
|
|
|
return tool.Blender.detect_icon_color_mode(color_path)
|
|
|
|
|
|
|
|
|
|
|
2023-07-08 14:39:39 +10:00
|
|
|
class BIM_MT_project(Menu):
|
|
|
|
|
bl_idname = "BIM_MT_project"
|
|
|
|
|
bl_label = "New IFC Project"
|
|
|
|
|
|
|
|
|
|
def draw(self, context):
|
2023-07-10 17:16:20 +10:00
|
|
|
self.layout.operator("bim.new_project", text="New Metric (m) Project").preset = "metric_m"
|
|
|
|
|
self.layout.operator("bim.new_project", text="New Metric (mm) Project").preset = "metric_mm"
|
|
|
|
|
self.layout.operator("bim.new_project", text="New Imperial (ft) Project").preset = "imperial_ft"
|
|
|
|
|
self.layout.operator("bim.new_project", text="New Demo Project").preset = "demo"
|
|
|
|
|
self.layout.operator("bim.new_project", text="New Project Wizard").preset = "wizard"
|
|
|
|
|
|
|
|
|
|
|
2024-06-28 15:33:34 +05:00
|
|
|
class BIM_MT_recent_projects(Menu):
|
|
|
|
|
bl_idname = "BIM_MT_recent_projects"
|
|
|
|
|
bl_label = "Open Recent IFC Project"
|
|
|
|
|
|
|
|
|
|
def draw(self, context):
|
2025-02-07 10:53:44 -05:00
|
|
|
if not UIData.is_loaded:
|
|
|
|
|
UIData.load()
|
|
|
|
|
ifc_icon = f"{UIData.data['menu_item_icon_color_mode']}_ifc"
|
2025-02-04 12:38:48 +05:00
|
|
|
layout = self.layout
|
2024-07-01 14:35:56 +05:00
|
|
|
paths = tool.Project.get_recent_ifc_projects()
|
2024-06-28 15:33:34 +05:00
|
|
|
if not paths:
|
|
|
|
|
self.layout.label(text="No Recent IFC Projects")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
for path in paths:
|
2025-02-04 12:38:48 +05:00
|
|
|
row = layout.row()
|
2025-02-07 10:53:44 -05:00
|
|
|
op = row.operator("bim.load_project", text=path.name, icon_value=bonsai.bim.icons[ifc_icon].icon_id)
|
2024-06-28 15:33:34 +05:00
|
|
|
op.filepath = str(path)
|
|
|
|
|
op.should_start_fresh_session = True
|
|
|
|
|
op.use_detailed_tooltip = True
|
2025-02-04 12:38:48 +05:00
|
|
|
row.enabled = path.is_file()
|
2024-06-28 15:33:34 +05:00
|
|
|
|
|
|
|
|
self.layout.separator()
|
|
|
|
|
self.layout.operator("bim.clear_recent_ifc_projects", icon="TRASH")
|
|
|
|
|
|
|
|
|
|
|
2023-07-10 17:16:20 +10:00
|
|
|
class BIM_MT_new_project(Menu):
|
|
|
|
|
bl_idname = "BIM_MT_new_project"
|
|
|
|
|
bl_label = "New Project"
|
|
|
|
|
|
|
|
|
|
def draw(self, context):
|
2023-07-14 13:40:09 +10:00
|
|
|
self.layout.operator_context = "INVOKE_DEFAULT"
|
|
|
|
|
op = self.layout.operator("bim.load_project", text="Open IFC Project", icon="FILEBROWSER")
|
|
|
|
|
op.should_start_fresh_session = True
|
2024-06-28 15:33:34 +05:00
|
|
|
self.layout.menu("BIM_MT_recent_projects", icon="NONE")
|
2023-07-14 13:40:09 +10:00
|
|
|
# Do we need to set it back to exec default?
|
|
|
|
|
# self.layout.operator_context = "EXEC_DEFAULT"
|
|
|
|
|
self.layout.separator()
|
2024-08-14 14:10:36 +05:00
|
|
|
self.layout.label(text="New IFC Project", icon_value=bonsai.bim.icons["IFC"].icon_id)
|
2023-07-10 17:16:20 +10:00
|
|
|
self.layout.operator("bim.new_project", text="Metric (m) Project").preset = "metric_m"
|
|
|
|
|
self.layout.operator("bim.new_project", text="Metric (mm) Project").preset = "metric_mm"
|
|
|
|
|
self.layout.operator("bim.new_project", text="Imperial (ft) Project").preset = "imperial_ft"
|
|
|
|
|
self.layout.operator("bim.new_project", text="Demo Project").preset = "demo"
|
|
|
|
|
self.layout.operator("bim.new_project", text="Project Wizard").preset = "wizard"
|
|
|
|
|
self.layout.separator()
|
|
|
|
|
self.layout.label(text="New Blender Project", icon="BLENDER")
|
|
|
|
|
# In theory we can dynamically grab the list from list(bpy.utils.app_template_paths())
|
|
|
|
|
self.layout.operator("wm.read_homefile", text="General Project").app_template = ""
|
|
|
|
|
self.layout.operator("wm.read_homefile", text="2D Animation Project").app_template = "2D_Animation"
|
|
|
|
|
self.layout.operator("wm.read_homefile", text="Sculpting Project").app_template = "Sculpting"
|
|
|
|
|
self.layout.operator("wm.read_homefile", text="VFX Project").app_template = "VFX"
|
|
|
|
|
self.layout.operator("wm.read_homefile", text="Video Editing Project").app_template = "Video_Editing"
|
2023-07-08 14:39:39 +10:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def file_menu(self, context):
|
|
|
|
|
self.layout.menu("BIM_MT_project", icon="COLLECTION_NEW")
|
|
|
|
|
op = self.layout.operator("bim.load_project", text="Open IFC Project", icon="FILEBROWSER")
|
|
|
|
|
op.should_start_fresh_session = True
|
2024-06-28 15:33:34 +05:00
|
|
|
self.layout.menu("BIM_MT_recent_projects", icon="NONE")
|
2023-07-08 14:39:39 +10:00
|
|
|
self.layout.separator()
|
2024-05-23 16:25:38 +05:00
|
|
|
op = self.layout.operator("bim.save_project", icon="FILE_TICK", text="Save IFC Project")
|
2023-07-08 14:39:39 +10:00
|
|
|
op.should_save_as = False
|
2024-05-23 16:25:38 +05:00
|
|
|
op = self.layout.operator("bim.save_project", text="Save IFC Project As...")
|
2023-07-08 14:39:39 +10:00
|
|
|
op.should_save_as = True
|
|
|
|
|
self.layout.separator()
|
2023-10-02 15:30:32 +05:00
|
|
|
self.layout.operator("bim.revert_project")
|
|
|
|
|
self.layout.separator()
|
2023-07-08 14:39:39 +10:00
|
|
|
|
|
|
|
|
|
2021-01-05 21:15:52 +11:00
|
|
|
class BIM_PT_project(Panel):
|
2023-07-15 21:02:30 +10:00
|
|
|
bl_label = "Current Project"
|
2021-01-05 21:15:52 +11:00
|
|
|
bl_idname = "BIM_PT_project"
|
|
|
|
|
bl_space_type = "PROPERTIES"
|
|
|
|
|
bl_region_type = "WINDOW"
|
|
|
|
|
bl_context = "scene"
|
2023-07-22 23:09:48 +10:00
|
|
|
bl_options = {"HIDE_HEADER"}
|
2023-10-20 22:15:37 +11:00
|
|
|
bl_parent_id = "BIM_PT_tab_project_info"
|
2021-01-05 21:15:52 +11:00
|
|
|
|
|
|
|
|
def draw(self, context):
|
2022-01-10 10:45:46 +11:00
|
|
|
if not ProjectData.is_loaded:
|
|
|
|
|
ProjectData.load()
|
|
|
|
|
|
2021-01-05 21:15:52 +11:00
|
|
|
self.layout.use_property_decorate = False
|
|
|
|
|
self.layout.use_property_split = True
|
2025-02-20 14:50:55 +05:00
|
|
|
props = tool.Blender.get_bim_props()
|
2025-02-03 17:40:46 +05:00
|
|
|
pprops = self.props = tool.Project.get_project_props()
|
2025-02-19 12:15:39 +05:00
|
|
|
self.file = tool.Ifc.get()
|
2021-09-11 20:29:14 +10:00
|
|
|
if pprops.is_loading:
|
2024-06-17 16:38:05 +03:00
|
|
|
self.draw_advanced_loading_ui(context)
|
2021-09-11 20:29:14 +10:00
|
|
|
elif self.file or props.ifc_file:
|
2024-08-02 15:54:15 +10:00
|
|
|
if props.has_blend_warning:
|
|
|
|
|
box = self.layout.box()
|
|
|
|
|
box.alert = True
|
|
|
|
|
row = box.row(align=True)
|
|
|
|
|
row.label(text="Your Model May Be Outdated", icon="ERROR")
|
|
|
|
|
op = row.operator("bim.open_uri", text="", icon="QUESTION")
|
2024-09-03 13:18:55 -05:00
|
|
|
op.uri = "https://docs.bonsaibim.org/guides/troubleshooting.html#saving-and-loading-blend-files"
|
2024-08-02 15:54:15 +10:00
|
|
|
row.operator("bim.close_blend_warning", text="", icon="CANCEL")
|
|
|
|
|
|
2024-06-17 16:38:05 +03:00
|
|
|
if props.ifc_file:
|
|
|
|
|
self.draw_loaded_project_ui(context)
|
|
|
|
|
else:
|
|
|
|
|
self.draw_unsaved_project_ui(context)
|
2021-01-20 16:53:34 +11:00
|
|
|
|
2024-06-17 16:38:05 +03:00
|
|
|
def draw_advanced_loading_ui(self, context):
|
2025-02-03 17:40:46 +05:00
|
|
|
pprops = self.props
|
2022-07-03 22:37:41 +02:00
|
|
|
prop_with_search(self.layout, pprops, "filter_mode")
|
2022-06-08 11:32:00 +10:00
|
|
|
if pprops.filter_mode in ["DECOMPOSITION", "IFC_CLASS", "IFC_TYPE"]:
|
2022-06-08 09:09:35 +10:00
|
|
|
row = self.layout.row(align=True)
|
2022-06-08 11:53:58 +10:00
|
|
|
row.label(text=f"Total: {pprops.total_elements}")
|
2022-06-08 09:09:35 +10:00
|
|
|
row.operator("bim.toggle_filter_categories", text="", icon="CHECKBOX_HLT").should_select = True
|
|
|
|
|
row.operator("bim.toggle_filter_categories", text="", icon="CHECKBOX_DEHLT").should_select = False
|
2021-09-12 15:25:03 +10:00
|
|
|
self.layout.template_list(
|
|
|
|
|
"BIM_UL_filter_categories",
|
|
|
|
|
"",
|
|
|
|
|
pprops,
|
|
|
|
|
"filter_categories",
|
|
|
|
|
pprops,
|
|
|
|
|
"active_filter_category_index",
|
|
|
|
|
)
|
2021-09-12 19:19:53 +10:00
|
|
|
elif pprops.filter_mode in ["WHITELIST", "BLACKLIST"]:
|
|
|
|
|
row = self.layout.row()
|
|
|
|
|
row.prop(pprops, "filter_query")
|
2021-10-15 13:16:26 +11:00
|
|
|
if pprops.filter_mode != "NONE":
|
|
|
|
|
row = self.layout.row()
|
|
|
|
|
row.prop(pprops, "should_filter_spatial_elements")
|
2021-09-12 19:19:53 +10:00
|
|
|
row = self.layout.row()
|
|
|
|
|
row.prop(pprops, "should_use_cpu_multiprocessing")
|
|
|
|
|
row = self.layout.row()
|
2022-06-08 12:09:19 +10:00
|
|
|
row.prop(pprops, "should_clean_mesh")
|
2021-09-12 19:19:53 +10:00
|
|
|
row = self.layout.row()
|
2022-06-08 11:56:22 +10:00
|
|
|
row.prop(pprops, "should_cache")
|
2021-09-12 19:19:53 +10:00
|
|
|
row = self.layout.row()
|
2023-06-26 17:13:34 +10:00
|
|
|
row.prop(pprops, "should_load_geometry")
|
|
|
|
|
row = self.layout.row()
|
2021-09-12 19:19:53 +10:00
|
|
|
row.prop(pprops, "should_merge_materials_by_colour")
|
2024-12-12 16:41:23 +05:00
|
|
|
self.layout.prop(pprops, "load_indexed_maps")
|
2021-09-12 19:19:53 +10:00
|
|
|
row = self.layout.row()
|
2024-08-29 16:22:10 +10:00
|
|
|
row.prop(pprops, "geometry_library")
|
|
|
|
|
row = self.layout.row()
|
2021-09-12 19:19:53 +10:00
|
|
|
row.prop(pprops, "deflection_tolerance")
|
|
|
|
|
row = self.layout.row()
|
|
|
|
|
row.prop(pprops, "angular_tolerance")
|
|
|
|
|
row = self.layout.row()
|
2024-01-24 10:39:20 +11:00
|
|
|
row.prop(pprops, "void_limit")
|
|
|
|
|
row = self.layout.row()
|
2025-03-07 17:02:05 +11:00
|
|
|
row.prop(pprops, "style_limit")
|
|
|
|
|
row = self.layout.row()
|
2022-06-01 14:25:30 +10:00
|
|
|
row.prop(pprops, "distance_limit")
|
2021-09-12 19:19:53 +10:00
|
|
|
row = self.layout.row()
|
2024-06-16 14:50:33 +10:00
|
|
|
row.prop(pprops, "false_origin_mode")
|
|
|
|
|
if pprops.false_origin_mode == "MANUAL":
|
|
|
|
|
row = self.layout.row()
|
|
|
|
|
row.prop(pprops, "false_origin")
|
2024-07-06 15:43:16 +10:00
|
|
|
row = self.layout.row()
|
|
|
|
|
row.prop(pprops, "project_north")
|
2021-09-12 19:19:53 +10:00
|
|
|
|
2024-10-06 17:16:23 +11:00
|
|
|
if ProjectData.data["total_elements"] > 30000:
|
|
|
|
|
box = self.layout.box()
|
|
|
|
|
box.alert = True
|
|
|
|
|
row = box.row()
|
|
|
|
|
row.alignment = "CENTER"
|
|
|
|
|
row.label(text=f"Large Model ({ProjectData.data['total_elements']} Elements)", icon="ERROR")
|
|
|
|
|
row = box.row()
|
|
|
|
|
row.alignment = "CENTER"
|
|
|
|
|
row.label(text="Large models may slow down Bonsai")
|
|
|
|
|
|
2022-06-08 11:53:58 +10:00
|
|
|
row = self.layout.row()
|
2024-10-06 17:16:23 +11:00
|
|
|
row.prop(pprops, "element_limit_mode")
|
|
|
|
|
|
|
|
|
|
if pprops.element_limit_mode == "RANGE":
|
|
|
|
|
row = self.layout.row()
|
|
|
|
|
row.label(text="Element Range")
|
|
|
|
|
row = self.layout.row(align=True)
|
|
|
|
|
row.prop(pprops, "element_offset", text="")
|
|
|
|
|
row.prop(pprops, "element_limit", text="")
|
2022-06-08 11:53:58 +10:00
|
|
|
|
2021-09-12 15:42:59 +10:00
|
|
|
row = self.layout.row(align=True)
|
2021-09-12 15:25:03 +10:00
|
|
|
row.operator("bim.load_project_elements")
|
2021-09-11 20:29:14 +10:00
|
|
|
|
2024-06-17 16:38:05 +03:00
|
|
|
def draw_editing_buttons(self, context, row):
|
2025-02-03 17:40:46 +05:00
|
|
|
pprops = self.props
|
2025-02-19 12:15:39 +05:00
|
|
|
if tool.Ifc.get():
|
2021-05-28 09:50:55 +10:00
|
|
|
if pprops.is_editing:
|
|
|
|
|
row.operator("bim.edit_header", icon="CHECKMARK", text="")
|
|
|
|
|
row.operator("bim.disable_editing_header", icon="CANCEL", text="")
|
|
|
|
|
else:
|
|
|
|
|
row.operator("bim.enable_editing_header", icon="GREASEPENCIL", text="")
|
|
|
|
|
|
2024-06-17 16:38:05 +03:00
|
|
|
def draw_editable_file_info(self, context):
|
2025-02-03 17:40:46 +05:00
|
|
|
pprops = self.props
|
2025-09-15 09:52:30 +05:00
|
|
|
assert self.layout
|
2024-06-17 16:38:05 +03:00
|
|
|
|
2025-09-04 19:03:13 +05:00
|
|
|
if ifc_file := tool.Ifc.get():
|
2021-02-03 19:15:25 +11:00
|
|
|
row = self.layout.row(align=True)
|
|
|
|
|
row.label(text="IFC Schema", icon="FILE_CACHE")
|
2025-09-04 19:03:13 +05:00
|
|
|
row.label(text=ifc_file.schema)
|
2021-05-28 09:50:55 +10:00
|
|
|
|
2025-09-15 18:16:35 +05:00
|
|
|
if not ProjectData.is_loaded:
|
|
|
|
|
ProjectData.load()
|
|
|
|
|
|
|
|
|
|
headers_props_and_icons = [
|
|
|
|
|
("mvd", "FILE_HIDDEN"),
|
|
|
|
|
("author_name", None),
|
|
|
|
|
("author_email", None),
|
|
|
|
|
("organisation_name", None),
|
|
|
|
|
("organisation_email", None),
|
|
|
|
|
("authorisation", None),
|
|
|
|
|
]
|
|
|
|
|
rna_props = pprops.bl_rna.properties
|
|
|
|
|
for prop_name, prop_icon in headers_props_and_icons:
|
|
|
|
|
if pprops.is_editing:
|
|
|
|
|
row = self.layout.row(align=True)
|
|
|
|
|
row.prop(pprops, prop_name)
|
2025-09-04 19:03:13 +05:00
|
|
|
else:
|
2025-09-15 18:16:35 +05:00
|
|
|
value = getattr(ProjectData.data["header_info"], prop_name)
|
|
|
|
|
if not value:
|
|
|
|
|
continue
|
|
|
|
|
row = self.layout.row(align=True)
|
|
|
|
|
icon = {} if prop_icon is None else {"icon": prop_icon}
|
|
|
|
|
row.label(text=rna_props[prop_name].name, **icon)
|
|
|
|
|
row.label(text=value)
|
2021-02-03 19:15:25 +11:00
|
|
|
else:
|
|
|
|
|
row = self.layout.row(align=True)
|
|
|
|
|
row.label(text="File Not Loaded", icon="ERROR")
|
2021-01-20 16:53:34 +11:00
|
|
|
|
2024-06-17 16:38:05 +03:00
|
|
|
def draw_unsaved_project_ui(self, context):
|
|
|
|
|
# file name row
|
|
|
|
|
file_name_row = self.layout.row(align=True)
|
|
|
|
|
file_name_row.label(text="No File Found", icon="FILE")
|
|
|
|
|
self.draw_editing_buttons(context, file_name_row)
|
2022-02-03 21:29:44 +11:00
|
|
|
|
2024-06-17 16:38:05 +03:00
|
|
|
# main section
|
|
|
|
|
self.draw_editable_file_info(context)
|
|
|
|
|
|
|
|
|
|
# file path row and actions section
|
|
|
|
|
row = self.layout.row()
|
|
|
|
|
row.label(text="File Not Saved", icon="ERROR")
|
|
|
|
|
|
|
|
|
|
def draw_loaded_project_ui(self, context):
|
|
|
|
|
# file name row
|
2025-02-20 14:50:55 +05:00
|
|
|
props = tool.Blender.get_bim_props()
|
2024-06-17 16:38:05 +03:00
|
|
|
file_name_row = self.layout.row(align=True)
|
|
|
|
|
file_name_row.label(text=os.path.basename(props.ifc_file), icon="FILE")
|
|
|
|
|
self.draw_editing_buttons(context, file_name_row)
|
|
|
|
|
|
|
|
|
|
# main section
|
|
|
|
|
self.draw_editable_file_info(context)
|
|
|
|
|
|
|
|
|
|
# file path row and actions section
|
|
|
|
|
row = self.layout.row(align=True)
|
2025-02-20 14:50:55 +05:00
|
|
|
if props.is_dirty:
|
2024-06-17 16:38:05 +03:00
|
|
|
row.label(text="Saved*", icon="EXPORT")
|
2023-07-11 23:07:51 +10:00
|
|
|
else:
|
2024-06-17 16:38:05 +03:00
|
|
|
row.label(text="Saved", icon="EXPORT")
|
|
|
|
|
row.label(text=ProjectData.data["last_saved"])
|
|
|
|
|
|
|
|
|
|
row = self.layout.row(align=True)
|
2025-03-11 18:38:45 +11:00
|
|
|
col = row.column()
|
|
|
|
|
col.enabled = False
|
|
|
|
|
col.prop(props, "ifc_file", text="")
|
2024-06-17 16:38:05 +03:00
|
|
|
row.operator("bim.select_ifc_file", icon="FILE_FOLDER", text="")
|
2022-02-03 21:29:44 +11:00
|
|
|
|
2025-12-12 09:13:15 +01:00
|
|
|
if tool.Blender.get_addon_preferences().save_metadata_blend_file:
|
2025-12-23 06:54:43 +01:00
|
|
|
suffix = tool.Blender.get_addon_preferences().metadata_blend_file_suffix
|
|
|
|
|
if props.ifc_file.lower().endswith(".ifc"):
|
|
|
|
|
metadata_filename = os.path.basename(props.ifc_file)[:-4] + suffix
|
|
|
|
|
else:
|
|
|
|
|
metadata_filename = os.path.basename(props.ifc_file) + suffix
|
2025-12-11 14:34:42 +01:00
|
|
|
row = self.layout.row(align=True)
|
2026-01-06 22:14:08 +01:00
|
|
|
row.use_property_split = False
|
|
|
|
|
pprops = tool.Project.get_project_props()
|
|
|
|
|
row.prop(pprops, "should_save_metadata_for_this_file", text=f"Save session data to: {metadata_filename}")
|
2025-11-21 17:24:10 +01:00
|
|
|
|
2024-06-28 22:49:56 +10:00
|
|
|
|
|
|
|
|
class BIM_PT_new_project_wizard(Panel):
|
|
|
|
|
bl_label = "New Project Wizard"
|
|
|
|
|
bl_idname = "BIM_PT_new_project_wizard"
|
|
|
|
|
bl_space_type = "PROPERTIES"
|
|
|
|
|
bl_region_type = "WINDOW"
|
|
|
|
|
bl_context = "scene"
|
|
|
|
|
bl_options = {"HIDE_HEADER"}
|
|
|
|
|
bl_parent_id = "BIM_PT_tab_new_project_wizard"
|
|
|
|
|
|
|
|
|
|
def draw(self, context):
|
|
|
|
|
self.layout.use_property_decorate = False
|
|
|
|
|
self.layout.use_property_split = True
|
|
|
|
|
|
2025-02-20 14:50:55 +05:00
|
|
|
props = tool.Blender.get_bim_props()
|
2025-02-03 17:40:46 +05:00
|
|
|
pprops = tool.Project.get_project_props()
|
2022-07-03 22:37:41 +02:00
|
|
|
prop_with_search(self.layout, pprops, "export_schema")
|
2021-01-20 16:53:34 +11:00
|
|
|
row = self.layout.row()
|
|
|
|
|
row.prop(context.scene.unit_settings, "system")
|
|
|
|
|
row = self.layout.row()
|
|
|
|
|
row.prop(context.scene.unit_settings, "length_unit")
|
|
|
|
|
row = self.layout.row()
|
|
|
|
|
row.prop(props, "area_unit", text="Area Unit")
|
|
|
|
|
row = self.layout.row()
|
|
|
|
|
row.prop(props, "volume_unit", text="Volume Unit")
|
2025-09-01 21:29:05 +02:00
|
|
|
row = self.layout.row()
|
2026-01-23 22:03:28 +11:00
|
|
|
row.prop(props, "mass_unit", text="Mass Unit")
|
|
|
|
|
row = self.layout.row()
|
|
|
|
|
row.prop(props, "time_unit", text="Time Unit")
|
2022-07-03 22:37:41 +02:00
|
|
|
prop_with_search(self.layout, pprops, "template_file", text="Template")
|
2025-09-29 09:31:00 +02:00
|
|
|
|
2025-09-29 09:13:18 +02:00
|
|
|
self.layout.use_property_split = True
|
2025-09-16 18:24:20 +02:00
|
|
|
row = self.layout.row()
|
2025-09-29 09:13:18 +02:00
|
|
|
row.operator("bim.create_project")
|
|
|
|
|
|
|
|
|
|
|
2021-04-20 12:45:20 +10:00
|
|
|
class BIM_PT_project_library(Panel):
|
2023-07-15 21:02:30 +10:00
|
|
|
bl_label = "Project Library"
|
2021-04-20 12:45:20 +10:00
|
|
|
bl_idname = "BIM_PT_project_library"
|
2021-04-21 16:45:11 +10:00
|
|
|
bl_options = {"DEFAULT_CLOSED"}
|
2021-04-20 12:45:20 +10:00
|
|
|
bl_space_type = "PROPERTIES"
|
|
|
|
|
bl_region_type = "WINDOW"
|
|
|
|
|
bl_context = "scene"
|
2023-10-20 22:15:37 +11:00
|
|
|
bl_parent_id = "BIM_PT_tab_project_setup"
|
2021-04-20 12:45:20 +10:00
|
|
|
|
|
|
|
|
def draw(self, context):
|
|
|
|
|
self.layout.use_property_decorate = False
|
|
|
|
|
self.layout.use_property_split = True
|
2025-01-31 13:19:42 +05:00
|
|
|
self.props = tool.Project.get_project_props()
|
2021-04-20 12:45:20 +10:00
|
|
|
row = self.layout.row(align=True)
|
2024-03-03 15:24:46 +11:00
|
|
|
row.prop(self.props, "library_file", text="")
|
|
|
|
|
if self.props.library_file == "0":
|
|
|
|
|
row.operator("bim.select_library_file", icon="FILE_FOLDER", text="")
|
|
|
|
|
row = self.layout.row(align=True)
|
2021-04-20 13:51:41 +10:00
|
|
|
if IfcStore.library_file:
|
2024-03-03 15:24:46 +11:00
|
|
|
row.label(
|
|
|
|
|
text=os.path.splitext(os.path.basename(IfcStore.library_path))[0]
|
|
|
|
|
+ f" - {IfcStore.library_file.schema}",
|
|
|
|
|
icon="ASSET_MANAGER",
|
|
|
|
|
)
|
2023-07-29 22:43:50 +10:00
|
|
|
row.operator("bim.append_library_element_by_query", text="", icon="APPEND_BLEND")
|
2021-04-20 13:51:41 +10:00
|
|
|
row.operator("bim.save_library_file", text="", icon="EXPORT")
|
2024-03-11 14:53:32 +05:00
|
|
|
self.draw_library_ul()
|
2024-03-03 15:24:46 +11:00
|
|
|
else:
|
|
|
|
|
row.label(text="No Library Loaded", icon="ASSET_MANAGER")
|
2021-04-20 12:45:20 +10:00
|
|
|
|
|
|
|
|
def draw_library_ul(self):
|
2025-01-31 12:17:43 +05:00
|
|
|
layout = self.layout
|
2025-01-31 13:19:42 +05:00
|
|
|
props = self.props
|
2025-01-31 12:17:43 +05:00
|
|
|
|
2025-01-31 14:35:22 +05:00
|
|
|
library_file = IfcStore.library_file
|
|
|
|
|
assert library_file
|
2025-02-14 17:12:03 +05:00
|
|
|
library_is_selected = props.selected_project_library != "-"
|
2025-01-31 14:35:22 +05:00
|
|
|
|
2025-01-31 12:17:43 +05:00
|
|
|
row = layout.row(align=True)
|
|
|
|
|
row.prop(self.props, "selected_project_library", text="")
|
2025-01-31 13:19:42 +05:00
|
|
|
|
2025-01-31 14:35:22 +05:00
|
|
|
if library_file.schema != "IFC2X3":
|
|
|
|
|
row.operator("bim.add_project_library", text="", icon="ADD")
|
|
|
|
|
|
2025-01-31 13:19:42 +05:00
|
|
|
if library_is_selected and not props.is_editing_project_library:
|
|
|
|
|
row.prop(props, "is_editing_project_library", text="", icon="GREASEPENCIL")
|
2025-08-28 17:04:21 +05:00
|
|
|
row.operator("bim.remove_project_library", text="", icon="X")
|
2025-01-31 13:19:42 +05:00
|
|
|
|
2025-02-14 17:12:03 +05:00
|
|
|
row.prop(self.props, "show_library_tree", text="", icon="OUTLINER")
|
2025-01-31 12:17:43 +05:00
|
|
|
|
2025-01-31 13:19:42 +05:00
|
|
|
if props.is_editing_project_library:
|
|
|
|
|
row = layout.row(align=True)
|
|
|
|
|
row.operator("bim.edit_project_library", text="Save Attributes", icon="GREASEPENCIL")
|
|
|
|
|
row.prop(props, "is_editing_project_library", text="", icon="CANCEL")
|
|
|
|
|
draw_attributes(props.project_library_attributes, layout)
|
2025-02-04 13:31:47 +05:00
|
|
|
row = layout.row()
|
|
|
|
|
row.prop(props, "parent_library", text="Parent Library")
|
2025-01-31 13:19:42 +05:00
|
|
|
layout.separator()
|
|
|
|
|
|
2025-01-31 14:35:22 +05:00
|
|
|
if not self.props.library_elements:
|
|
|
|
|
row = self.layout.row()
|
|
|
|
|
row.label(text="No Assets Found", icon="ERROR")
|
|
|
|
|
return
|
|
|
|
|
|
2021-04-20 12:45:20 +10:00
|
|
|
row = self.layout.row(align=True)
|
2025-02-14 17:12:03 +05:00
|
|
|
active_library_element = self.props.get_active_library_breadcrumb()
|
|
|
|
|
row.label(text=(active_library_element.name if active_library_element else "Top Level Assets"))
|
|
|
|
|
if active_library_element:
|
2021-04-20 12:45:20 +10:00
|
|
|
row.operator("bim.rewind_library", icon="FRAME_PREV", text="")
|
|
|
|
|
row.operator("bim.refresh_library", icon="FILE_REFRESH", text="")
|
|
|
|
|
self.layout.template_list(
|
|
|
|
|
"BIM_UL_library",
|
|
|
|
|
"",
|
|
|
|
|
self.props,
|
|
|
|
|
"library_elements",
|
|
|
|
|
self.props,
|
|
|
|
|
"active_library_element_index",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2021-09-13 10:00:54 +10:00
|
|
|
class BIM_PT_links(Panel):
|
2023-07-15 21:02:30 +10:00
|
|
|
bl_label = "Links"
|
2021-09-13 10:00:54 +10:00
|
|
|
bl_idname = "BIM_PT_links"
|
|
|
|
|
bl_options = {"DEFAULT_CLOSED"}
|
|
|
|
|
bl_space_type = "PROPERTIES"
|
|
|
|
|
bl_region_type = "WINDOW"
|
|
|
|
|
bl_context = "scene"
|
2023-10-20 22:15:37 +11:00
|
|
|
bl_parent_id = "BIM_PT_tab_project_setup"
|
2021-09-13 10:00:54 +10:00
|
|
|
|
|
|
|
|
def draw(self, context):
|
2025-02-03 17:40:46 +05:00
|
|
|
self.props = tool.Project.get_project_props()
|
2026-02-15 09:28:43 +01:00
|
|
|
|
2021-09-13 10:00:54 +10:00
|
|
|
row = self.layout.row(align=True)
|
|
|
|
|
row.operator("bim.link_ifc")
|
|
|
|
|
if self.props.links:
|
2026-02-15 09:28:43 +01:00
|
|
|
if self.props.active_link:
|
|
|
|
|
row = self.layout.row(align=True)
|
|
|
|
|
row.alignment = "RIGHT"
|
|
|
|
|
index = self.props.active_link_index
|
|
|
|
|
if self.props.active_link.is_loaded:
|
2026-02-18 19:12:56 +01:00
|
|
|
if self.props.active_link.is_editing:
|
|
|
|
|
row.operator("bim.edit_link", text="", icon="CHECKMARK")
|
|
|
|
|
row.operator("bim.disable_editing_link", text="", icon="CANCEL")
|
|
|
|
|
else:
|
|
|
|
|
row.operator("bim.enable_editing_link", text="", icon="GREASEPENCIL")
|
2026-03-05 15:26:27 +05:00
|
|
|
row.operator("bim.select_linked_model_element", icon="VIEWZOOM", text="")
|
2026-02-15 09:28:43 +01:00
|
|
|
row.operator("bim.select_link_handle", text="", icon="OBJECT_DATA").link_index = index
|
|
|
|
|
row.operator("bim.unload_link", text="", icon="UNLINKED").link_index = index
|
|
|
|
|
row.operator("bim.reload_link", text="", icon="FILE_REFRESH").link_index = index
|
|
|
|
|
else:
|
|
|
|
|
row.operator("bim.load_link", text="", icon="LINKED").link_index = index
|
|
|
|
|
row.operator("bim.unlink_ifc", text="", icon="X").link_index = index
|
|
|
|
|
self.layout.template_list("BIM_UL_links", "", self.props, "links", self.props, "active_link_index")
|
2021-09-13 10:00:54 +10:00
|
|
|
|
2024-02-29 16:45:01 +11:00
|
|
|
if LinksData.enable_culling:
|
|
|
|
|
row = self.layout.row(align=True)
|
|
|
|
|
row.label(text="Object Culling Enabled", icon="MOD_TRIANGULATE")
|
|
|
|
|
|
2024-03-20 11:26:45 +05:00
|
|
|
if not LinksData.linked_data or not self.props.links:
|
|
|
|
|
if self.props.links:
|
|
|
|
|
row = self.layout.row(align=True)
|
2025-01-28 14:59:50 +05:00
|
|
|
row.label(text="No Object Queried with Explore Tool", icon="QUESTION")
|
2024-02-29 16:45:01 +11:00
|
|
|
return
|
|
|
|
|
|
2024-03-19 17:12:55 +05:00
|
|
|
row = self.layout.row(align=True)
|
|
|
|
|
row.label(text="")
|
|
|
|
|
row.operator("bim.append_inspected_linked_element", icon="APPEND_BLEND", text="")
|
|
|
|
|
|
2024-02-29 16:45:01 +11:00
|
|
|
for name, value in LinksData.linked_data["attributes"].items():
|
|
|
|
|
row = self.layout.row(align=True)
|
|
|
|
|
row.label(text=name)
|
|
|
|
|
row.label(text=value)
|
|
|
|
|
|
|
|
|
|
for pset in LinksData.linked_data["properties"]:
|
|
|
|
|
box = self.layout.box()
|
|
|
|
|
row = box.row(align=True)
|
|
|
|
|
row.label(text=pset[0], icon="COPY_ID")
|
|
|
|
|
for name, value in pset[1].items():
|
|
|
|
|
row = box.row(align=True)
|
|
|
|
|
row.label(text=name)
|
|
|
|
|
row.label(text=value)
|
|
|
|
|
|
|
|
|
|
if LinksData.linked_data["type_properties"]:
|
|
|
|
|
row = self.layout.row()
|
|
|
|
|
row.label(icon="LINKED", text="Type Properties")
|
|
|
|
|
for pset in LinksData.linked_data["type_properties"]:
|
|
|
|
|
box = self.layout.box()
|
|
|
|
|
row = box.row(align=True)
|
|
|
|
|
row.label(text=pset[0], icon="COPY_ID")
|
|
|
|
|
for name, value in pset[1].items():
|
|
|
|
|
row = box.row(align=True)
|
|
|
|
|
row.label(text=name)
|
|
|
|
|
row.label(text=value)
|
|
|
|
|
|
2021-09-13 10:00:54 +10:00
|
|
|
|
2021-04-20 12:45:20 +10:00
|
|
|
class BIM_UL_library(UIList):
|
2025-01-29 12:40:03 +05:00
|
|
|
def draw_item(
|
|
|
|
|
self,
|
|
|
|
|
context,
|
|
|
|
|
layout: bpy.types.UILayout,
|
|
|
|
|
data: BIMProjectProperties,
|
|
|
|
|
item: LibraryElement,
|
|
|
|
|
icon,
|
|
|
|
|
active_data,
|
|
|
|
|
active_propname,
|
|
|
|
|
):
|
2021-04-20 12:45:20 +10:00
|
|
|
if item:
|
|
|
|
|
row = layout.row(align=True)
|
2025-02-20 11:29:17 +05:00
|
|
|
if item.element_type != "ASSET" and (item.asset_count > 0 or item.has_sublibraries):
|
2021-04-20 12:45:20 +10:00
|
|
|
op = row.operator("bim.change_library_element", text="", icon="DISCLOSURE_TRI_RIGHT", emboss=False)
|
|
|
|
|
op.element_name = item.name
|
2025-02-14 17:12:03 +05:00
|
|
|
op.breadcrumb_type = item.element_type
|
|
|
|
|
op.library_id = item.ifc_definition_id
|
2025-02-17 13:13:05 +05:00
|
|
|
if item.ifc_definition_id:
|
|
|
|
|
row.prop(item, "name", text="", emboss=False)
|
|
|
|
|
else:
|
|
|
|
|
row.label(text=item.name)
|
2025-01-29 16:33:14 +05:00
|
|
|
if item.ifc_definition_id and item.is_declarable:
|
2021-04-21 16:45:11 +10:00
|
|
|
if item.is_declared:
|
|
|
|
|
op = row.operator("bim.unassign_library_declaration", text="", icon="KEYFRAME_HLT", emboss=False)
|
|
|
|
|
op.definition = item.ifc_definition_id
|
|
|
|
|
else:
|
|
|
|
|
op = row.operator("bim.assign_library_declaration", text="", icon="KEYFRAME", emboss=False)
|
|
|
|
|
op.definition = item.ifc_definition_id
|
2025-02-14 17:12:03 +05:00
|
|
|
if item.element_type == "ASSET":
|
2021-09-01 00:01:44 +02:00
|
|
|
if item.is_appended:
|
|
|
|
|
row.label(text="", icon="CHECKMARK")
|
|
|
|
|
else:
|
|
|
|
|
op = row.operator("bim.append_library_element", text="", icon="APPEND_BLEND")
|
|
|
|
|
op.definition = item.ifc_definition_id
|
|
|
|
|
op.prop_index = data.get_library_element_index(item)
|
2025-01-29 17:03:33 +05:00
|
|
|
else:
|
|
|
|
|
row_ = row.row()
|
|
|
|
|
row_.alignment = "RIGHT"
|
|
|
|
|
row_.label(text=str(item.asset_count))
|
2021-09-12 15:25:03 +10:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class BIM_UL_filter_categories(UIList):
|
2025-02-20 14:50:55 +05:00
|
|
|
def draw_item(
|
|
|
|
|
self,
|
|
|
|
|
context,
|
|
|
|
|
layout: bpy.types.UILayout,
|
|
|
|
|
data: BIMProjectProperties,
|
|
|
|
|
item: FilterCategory,
|
|
|
|
|
icon,
|
|
|
|
|
active_data,
|
|
|
|
|
active_propname,
|
|
|
|
|
):
|
2021-09-12 15:25:03 +10:00
|
|
|
if item:
|
|
|
|
|
row = layout.row(align=True)
|
|
|
|
|
row.label(text=f"{item.name} ({item.total_elements})")
|
|
|
|
|
row.prop(
|
|
|
|
|
item,
|
|
|
|
|
"is_selected",
|
|
|
|
|
icon="CHECKBOX_HLT" if item.is_selected else "CHECKBOX_DEHLT",
|
|
|
|
|
text="",
|
|
|
|
|
emboss=False,
|
|
|
|
|
)
|
2021-09-13 10:00:54 +10:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class BIM_UL_links(UIList):
|
2025-02-20 14:50:55 +05:00
|
|
|
def draw_item(
|
|
|
|
|
self,
|
|
|
|
|
context,
|
|
|
|
|
layout: bpy.types.UILayout,
|
|
|
|
|
data: BIMProjectProperties,
|
|
|
|
|
item: Link,
|
|
|
|
|
icon,
|
|
|
|
|
active_data,
|
|
|
|
|
active_propname,
|
|
|
|
|
index,
|
|
|
|
|
):
|
2026-02-15 09:28:43 +01:00
|
|
|
row = layout.row(align=True)
|
|
|
|
|
if item.is_loaded:
|
|
|
|
|
if item.georeferenced == "NONE":
|
|
|
|
|
row.label(text="", icon="QUESTION")
|
|
|
|
|
elif item.georeferenced == "NOT_COMPATIBLE":
|
|
|
|
|
row.label(text="", icon="ERROR")
|
|
|
|
|
elif item.georeferenced == "FULL_COMPATIBLE":
|
|
|
|
|
row.label(text="", icon="WORLD")
|
|
|
|
|
if item.has_transformation:
|
|
|
|
|
row.label(text="", icon="OBJECT_ORIGIN")
|
|
|
|
|
|
|
|
|
|
row.label(text=item.filepath)
|
|
|
|
|
icon = "RESTRICT_SELECT_OFF" if item.is_selectable else "RESTRICT_SELECT_ON"
|
|
|
|
|
row.operator("bim.toggle_link_selectability", text="", icon=icon, emboss=False).link_index = index
|
|
|
|
|
icon = "CUBE" if item.is_wireframe else "MESH_CUBE"
|
|
|
|
|
op = row.operator("bim.toggle_link_visibility", text="", icon=icon, emboss=False)
|
|
|
|
|
op.link_index = index
|
|
|
|
|
op.mode = "WIREFRAME"
|
|
|
|
|
icon = "HIDE_ON" if item.is_hidden else "HIDE_OFF"
|
|
|
|
|
op = row.operator("bim.toggle_link_visibility", text="", icon=icon, emboss=False)
|
|
|
|
|
op.link_index = index
|
|
|
|
|
op.mode = "VISIBLE"
|
|
|
|
|
else:
|
|
|
|
|
row.label(text=item.filepath)
|
2023-08-17 16:26:21 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class BIM_PT_purge(Panel):
|
|
|
|
|
bl_label = "Purge Data"
|
|
|
|
|
bl_options = {"DEFAULT_CLOSED"}
|
|
|
|
|
bl_space_type = "PROPERTIES"
|
|
|
|
|
bl_region_type = "WINDOW"
|
|
|
|
|
bl_context = "scene"
|
|
|
|
|
bl_parent_id = "BIM_PT_tab_quality_control"
|
|
|
|
|
|
|
|
|
|
def draw(self, context):
|
2025-11-28 10:41:34 -06:00
|
|
|
props = tool.Debug.get_debug_props()
|
2023-08-17 16:26:21 +02:00
|
|
|
layout = self.layout
|
2024-09-12 17:31:48 +05:00
|
|
|
layout.operator("bim.purge_unused_objects", text="Purge Unused Profiles").object_type = "PROFILE"
|
|
|
|
|
layout.operator("bim.purge_unused_objects", text="Purge Unused Types").object_type = "TYPE"
|
2025-01-03 17:37:46 +01:00
|
|
|
layout.operator("bim.purge_unused_openings", text="Purge Unused Openings in Selected Objects")
|
2025-06-17 17:23:47 +05:00
|
|
|
|
2025-06-20 12:10:40 +05:00
|
|
|
MERGEABLE_OBJECT_TYPES = (
|
|
|
|
|
"MATERIAL",
|
|
|
|
|
"STYLE",
|
|
|
|
|
"ORGANIZATION",
|
|
|
|
|
"APPLICATION",
|
|
|
|
|
"PERSON",
|
|
|
|
|
"PERSON_AND_ORGANIZATION",
|
|
|
|
|
)
|
2025-06-17 17:23:47 +05:00
|
|
|
|
2025-06-18 19:08:37 +05:00
|
|
|
for object_type in MERGEABLE_OBJECT_TYPES:
|
|
|
|
|
row = layout.row(align=True)
|
2025-06-20 12:10:40 +05:00
|
|
|
row.label(text=f"{object_type.replace('_', ' ').capitalize()}:")
|
2025-06-18 19:08:37 +05:00
|
|
|
row.operator("bim.purge_unused_objects", text="Purge Unused").object_type = object_type
|
2025-10-13 11:25:36 -05:00
|
|
|
merge_op = row.operator("bim.merge_identical_objects", text="Merge Identical")
|
|
|
|
|
merge_op.object_type = object_type
|
2025-11-28 10:41:34 -06:00
|
|
|
|
|
|
|
|
layout.operator("bim.purge_unused_representations")
|
|
|
|
|
|
|
|
|
|
row = layout.row(align=True)
|
|
|
|
|
row.prop(props, "ifc_class_purge", text="")
|
|
|
|
|
row.operator("bim.purge_unused_elements_by_class", text="Purge Orphaned", icon="TRASH")
|
|
|
|
|
row.operator("bim.print_unused_elements_stats", text="", icon="INFO")
|