Files
IfcOpenShell/src/bonsai/bonsai/bim/module/project/ui.py
T

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

593 lines
24 KiB
Python
Raw Normal View History

# Bonsai - OpenBIM Blender Add-on
2021-08-17 08:55:29 +10:00
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of Bonsai.
2021-08-17 08:55:29 +10:00
#
# Bonsai is free software: you can redistribute it and/or modify
2021-08-17 08:55:29 +10:00
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Bonsai is distributed in the hope that it will be useful,
2021-08-17 08:55:29 +10:00
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
2021-08-17 08:55:29 +10:00
2025-01-29 12:40:03 +05:00
from __future__ import annotations
import bpy
import os
2024-08-14 14:10:36 +05:00
import bonsai.bim
import bonsai.tool as tool
2025-01-31 13:19:42 +05:00
from bonsai.bim.helper import prop_with_search, draw_attributes
from bpy.types import Panel, Menu, UIList
2024-08-14 14:10:36 +05:00
from bonsai.bim.ifc import IfcStore
from bonsai.bim.module.project.data import ProjectData, LinksData
2025-01-29 12:40:03 +05:00
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from bonsai.bim.module.project.prop import LibraryElement, BIMProjectProperties
def file_import_menu(self, context):
op = self.layout.operator("bim.load_project", text="IFC (Geometry Only) (.ifc/.ifczip/.ifcxml)")
op.import_without_ifc_data = True
op.should_start_fresh_session = False
class BIM_MT_project(Menu):
bl_idname = "BIM_MT_project"
bl_label = "New IFC Project"
def draw(self, context):
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):
layout = self.layout
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:
row = layout.row()
op = row.operator("bim.load_project", text=path.name, icon_value=bonsai.bim.icons["IFC"].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
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")
class BIM_MT_new_project(Menu):
bl_idname = "BIM_MT_new_project"
bl_label = "New Project"
def draw(self, context):
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")
# 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)
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"
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")
self.layout.separator()
op = self.layout.operator("bim.save_project", icon="FILE_TICK", text="Save IFC Project")
op.should_save_as = False
op = self.layout.operator("bim.save_project", text="Save IFC Project As...")
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()
class BIM_PT_project(Panel):
bl_label = "Current Project"
bl_idname = "BIM_PT_project"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_options = {"HIDE_HEADER"}
bl_parent_id = "BIM_PT_tab_project_info"
def draw(self, context):
if not ProjectData.is_loaded:
ProjectData.load()
self.layout.use_property_decorate = False
self.layout.use_property_split = True
props = context.scene.BIMProperties
2025-02-03 17:40:46 +05:00
pprops = self.props = tool.Project.get_project_props()
self.file = IfcStore.get_file()
if pprops.is_loading:
self.draw_advanced_loading_ui(context)
elif self.file or props.ifc_file:
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"
row.operator("bim.close_blend_warning", text="", icon="CANCEL")
if props.ifc_file:
self.draw_loaded_project_ui(context)
else:
self.draw_unsaved_project_ui(context)
def draw_advanced_loading_ui(self, context):
2025-02-03 17:40:46 +05:00
pprops = self.props
prop_with_search(self.layout, pprops, "filter_mode")
if pprops.filter_mode in ["DECOMPOSITION", "IFC_CLASS", "IFC_TYPE"]:
row = self.layout.row(align=True)
row.label(text=f"Total: {pprops.total_elements}")
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
self.layout.template_list(
"BIM_UL_filter_categories",
"",
pprops,
"filter_categories",
pprops,
"active_filter_category_index",
)
elif pprops.filter_mode in ["WHITELIST", "BLACKLIST"]:
row = self.layout.row()
row.prop(pprops, "filter_query")
if pprops.filter_mode != "NONE":
row = self.layout.row()
row.prop(pprops, "should_filter_spatial_elements")
row = self.layout.row()
row.prop(pprops, "should_use_cpu_multiprocessing")
row = self.layout.row()
row.prop(pprops, "should_clean_mesh")
row = self.layout.row()
row.prop(pprops, "should_cache")
row = self.layout.row()
row.prop(pprops, "should_load_geometry")
row = self.layout.row()
row.prop(pprops, "should_merge_materials_by_colour")
self.layout.prop(pprops, "load_indexed_maps")
row = self.layout.row()
row.prop(pprops, "geometry_library")
row = self.layout.row()
row.prop(pprops, "deflection_tolerance")
row = self.layout.row()
row.prop(pprops, "angular_tolerance")
row = self.layout.row()
row.prop(pprops, "void_limit")
row = self.layout.row()
row.prop(pprops, "distance_limit")
row = self.layout.row()
row.prop(pprops, "false_origin_mode")
if pprops.false_origin_mode == "MANUAL":
row = self.layout.row()
row.prop(pprops, "false_origin")
row = self.layout.row()
row.prop(pprops, "project_north")
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")
row = self.layout.row()
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="")
row = self.layout.row(align=True)
row.operator("bim.load_project_elements")
def draw_editing_buttons(self, context, row):
2025-02-03 17:40:46 +05:00
pprops = self.props
if IfcStore.get_file():
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="")
def draw_editable_file_info(self, context):
2025-02-03 17:40:46 +05:00
pprops = self.props
if IfcStore.get_file():
row = self.layout.row(align=True)
row.label(text="IFC Schema", icon="FILE_CACHE")
row.label(text=IfcStore.get_file().schema)
if pprops.is_editing:
row = self.layout.row(align=True)
row.prop(pprops, "mvd")
row = self.layout.row(align=True)
row.prop(pprops, "author_name")
row = self.layout.row(align=True)
row.prop(pprops, "author_email")
row = self.layout.row(align=True)
row.prop(pprops, "organisation_name")
row = self.layout.row(align=True)
row.prop(pprops, "organisation_email")
row = self.layout.row(align=True)
row.prop(pprops, "authorisation")
else:
row = self.layout.row(align=True)
row.label(text="IFC MVD", icon="FILE_HIDDEN")
mvd = "".join(IfcStore.get_file().wrapped_data.header.file_description.description)
if "[" in mvd:
mvd = mvd.split("[")[1][0:-1]
row.label(text=mvd)
else:
row = self.layout.row(align=True)
row.label(text="File Not Loaded", icon="ERROR")
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)
# 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
props = context.scene.BIMProperties
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)
if context.scene.BIMProperties.is_dirty:
row.label(text="Saved*", icon="EXPORT")
else:
row.label(text="Saved", icon="EXPORT")
row.label(text=ProjectData.data["last_saved"])
row = self.layout.row(align=True)
row.prop(props, "ifc_file", text="")
row.operator("bim.select_ifc_file", icon="FILE_FOLDER", text="")
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
props = context.scene.BIMProperties
2025-02-03 17:40:46 +05:00
pprops = tool.Project.get_project_props()
prop_with_search(self.layout, pprops, "export_schema")
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")
prop_with_search(self.layout, pprops, "template_file", text="Template")
row = self.layout.row()
row.operator("bim.create_project")
class BIM_PT_project_library(Panel):
bl_label = "Project Library"
bl_idname = "BIM_PT_project_library"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_tab_project_setup"
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()
row = self.layout.row(align=True)
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)
if IfcStore.library_file:
row.label(
text=os.path.splitext(os.path.basename(IfcStore.library_path))[0]
+ f" - {IfcStore.library_file.schema}",
icon="ASSET_MANAGER",
)
row.operator("bim.append_library_element_by_query", text="", icon="APPEND_BLEND")
row.operator("bim.save_library_file", text="", icon="EXPORT")
self.draw_library_ul()
else:
row.label(text="No Library Loaded", icon="ASSET_MANAGER")
def draw_library_ul(self):
layout = self.layout
2025-01-31 13:19:42 +05:00
props = self.props
library_file = IfcStore.library_file
assert library_file
2025-01-31 13:19:42 +05:00
library_is_selected = props.selected_project_library not in ("*", "-")
row = layout.row(align=True)
row.prop(self.props, "selected_project_library", text="")
2025-01-31 13:19:42 +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")
row.prop(self.props, "filter_by_library", text="", icon="FILTER")
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)
layout.separator()
if not self.props.library_elements:
row = self.layout.row()
row.label(text="No Assets Found", icon="ERROR")
return
row = self.layout.row(align=True)
row.label(text=self.props.active_library_element or "Top Level Assets")
if self.props.active_library_element:
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):
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"
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()
2021-09-13 10:00:54 +10:00
row = self.layout.row(align=True)
row.operator("bim.link_ifc")
if self.props.links:
self.layout.template_list(
"BIM_UL_links",
"",
self.props,
"links",
self.props,
"active_link_index",
)
if LinksData.enable_culling:
row = self.layout.row(align=True)
row.label(text="Object Culling Enabled", icon="MOD_TRIANGULATE")
if not LinksData.linked_data or not self.props.links:
if self.props.links:
row = self.layout.row(align=True)
row.label(text="No Object Queried with Explore Tool", icon="QUESTION")
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="")
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
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,
):
if item:
row = layout.row(align=True)
if not item.ifc_definition_id:
op = row.operator("bim.change_library_element", text="", icon="DISCLOSURE_TRI_RIGHT", emboss=False)
op.element_name = item.name
row.label(text=item.name)
if item.ifc_definition_id and item.is_declarable:
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
if item.ifc_definition_id:
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))
class BIM_UL_filter_categories(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
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):
2024-09-30 11:20:29 +05:00
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
2021-09-13 10:00:54 +10:00
if item:
row = layout.row(align=True)
2021-09-13 10:48:38 +10:00
if item.is_loaded:
row.label(text=item.name)
op = row.operator(
"bim.toggle_link_selectability",
text="",
icon="RESTRICT_SELECT_OFF" if item.is_selectable else "RESTRICT_SELECT_ON",
emboss=False,
)
op.link = item.name
op = row.operator(
"bim.toggle_link_visibility",
text="",
icon="CUBE" if item.is_wireframe else "MESH_CUBE",
emboss=False,
)
op.link = item.name
op.mode = "WIREFRAME"
op = row.operator(
"bim.toggle_link_visibility",
text="",
icon="HIDE_ON" if item.is_hidden else "HIDE_OFF",
emboss=False,
)
op.link = item.name
op.mode = "VISIBLE"
2024-09-30 11:20:29 +05:00
op = row.operator("bim.select_link_handle", text="", icon="OBJECT_DATA")
op.index = index
2021-09-13 10:48:38 +10:00
op = row.operator("bim.unload_link", text="", icon="UNLINKED")
op.filepath = item.name
2023-08-27 18:21:19 +01:00
op = row.operator("bim.reload_link", text="", icon="FILE_REFRESH")
op.filepath = item.name
2021-09-13 10:48:38 +10:00
else:
row.prop(item, "name", text="")
2021-09-13 10:48:38 +10:00
op = row.operator("bim.load_link", text="", icon="LINKED")
op.filepath = item.name
2021-09-13 10:48:38 +10:00
op = row.operator("bim.unlink_ifc", text="", icon="X")
op.filepath = item.name
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):
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"
layout.operator("bim.purge_unused_openings", text="Purge Unused Openings in Selected Objects")
row = layout.row(align=True)
row.label(text="Materials: ")
row.operator("bim.purge_unused_objects", text="Purge Unused").object_type = "MATERIAL"
row.operator("bim.merge_identical_objects", text="Merge Identical").object_type = "MATERIAL"
row = layout.row(align=True)
row.label(text="Styles: ")
row.operator("bim.purge_unused_objects", text="Purge Unused").object_type = "STYLE"
row.operator("bim.merge_identical_objects", text="Merge Identical").object_type = "STYLE"