Files
IfcOpenShell/src/bonsai/bonsai/bim/__init__.py
T
2025-12-19 08:51:06 +01:00

362 lines
12 KiB
Python

# Bonsai - OpenBIM Blender Add-on
# Copyright (C) 2020, 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
import bpy
import bpy.utils.previews
import importlib
from bpy_extras.io_utils import ImportHelper, ExportHelper
from . import handler, ui, prop, operator
from typing import Union
from collections.abc import Callable
try:
from bonsai.translations import translations_dict
except ImportError:
translations_dict = {}
cwd = os.path.dirname(os.path.realpath(__file__))
modules = {
"project": None,
"search": None,
"bcf": None,
"bsdd": None,
"root": None,
"unit": None,
"model": None,
"cad": None,
"georeference": None,
"context": None,
"drawing": None,
"misc": None,
"attribute": None,
"type": None,
"spatial": None,
"void": None,
"aggregate": None,
"nest": None,
"geometry": None,
"fm": None,
"resource": None,
"cost": None,
"sequence": None,
"group": None,
"system": None,
"brick": None,
"structural": None,
"boundary": None,
"profile": None,
"material": None,
"style": None,
"layer": None,
"owner": None,
"pset": None,
"qto": None,
"classification": None,
"library": None,
"constraint": None,
"document": None,
"pset_template": None,
"clash": None,
"csv": None,
"tester": None,
"diff": None,
"patch": None,
"gis": None,
"covetool": None,
"augin": None,
"debug": None,
"ifcgit": None,
"covering": None,
"web": None,
"light": None,
"alignment": None,
# Uncomment this line to enable loading of the demo module. Happy hacking!
# The name "demo" must correlate to a folder name in `bim/module/`.
# "demo": None,
}
for name in modules.keys():
modules[name] = importlib.import_module(f"bonsai.bim.module.{name}")
classes = [
operator.BIM_OT_add_section_plane,
operator.BIM_OT_delete_object,
operator.BIM_OT_remove_section_plane,
operator.BIM_OT_select_entity,
operator.BIM_OT_select_entity_by_guid,
operator.BIM_OT_select_object,
operator.BIM_OT_show_description,
operator.BIM_OT_multiple_file_selector,
operator.BIM_OT_attribute_add_subitem,
operator.BIM_OT_attribute_remove_subitem,
operator.ClippingPlaneCutWithCappings,
operator.CloseBlendWarning,
operator.CloseError,
operator.CreateMacBonsaiApp,
operator.CopyTextToClipboard,
operator.EditBlenderCollection,
operator.FileAssociate,
operator.FileUnassociate,
operator.OpenPath,
operator.OpenUpstream,
operator.OpenUri,
operator.ReloadIfcFile,
operator.RevertClippingPlaneCut,
operator.SelectDir,
operator.SelectIfcFile,
operator.SelectURIAttribute,
operator.SetTab,
operator.SwitchTab,
operator.ShowSystemInfo,
prop.StrProperty,
operator.BIM_OT_enum_property_search, # /!\ Register AFTER prop.StrProperty
operator.BIM_OT_attribute_search_values,
prop.ObjProperty,
prop.MultipleFileSelect,
prop.Attribute,
prop.ISODuration,
prop.BIMAreaProperties,
prop.BIMTabProperties,
prop.BIMProperties,
prop.IfcParameter,
prop.PsetQto,
prop.BIMObjectProperties,
prop.BIMCollectionProperties,
prop.BIMMeshProperties,
prop.BIMFacet,
prop.BIMFilterGroup,
prop.BIMSnapProperties,
prop.BIMSnapGroups,
ui.BIM_UL_clipping_plane,
ui.BIM_UL_generic,
ui.DocPreferences,
ui.GizmoPreferencesDoor, # Register before GizmoPreferences
ui.GizmoPreferencesWindow, # Register before GizmoPreferences
ui.GizmoPreferencesStair, # Register before GizmoPreferences
ui.GizmoPreferences,
# ui.DefaultParameters and ui.BIM_ADDON_preferences are registered separately after modules (see late_classes below)
# Tabs panel
ui.BIM_PT_tabs,
# Project overview
ui.BIM_PT_tab_new_project_wizard,
ui.BIM_PT_tab_project_info,
ui.BIM_PT_tab_spatial,
ui.BIM_PT_tab_grouping_and_filtering,
ui.BIM_PT_tab_project_setup,
ui.BIM_PT_tab_geometry,
ui.BIM_PT_tab_stakeholders,
# Object information
ui.BIM_PT_tab_object_metadata,
ui.BIM_PT_tab_misc,
# Geometry and materials
ui.BIM_PT_tab_placement,
ui.BIM_PT_tab_representations,
ui.BIM_PT_tab_geometric_relationships,
ui.BIM_PT_tab_parametric_geometry,
ui.BIM_PT_tab_object_materials,
ui.BIM_PT_tab_materials,
ui.BIM_PT_tab_styles,
ui.BIM_PT_tab_profiles,
# Drawings and documents
ui.BIM_PT_tab_sheets,
ui.BIM_PT_tab_drawings,
ui.BIM_PT_tab_schedules,
ui.BIM_PT_tab_references,
# Services and systems
ui.BIM_PT_tab_services,
ui.BIM_PT_tab_zones,
ui.BIM_PT_tab_solar_analysis,
ui.BIM_PT_tab_lighting,
# Structural analysis
ui.BIM_PT_tab_structural,
# Construction scheduling
ui.BIM_PT_tab_status,
ui.BIM_PT_tab_qto,
ui.BIM_PT_tab_resources,
ui.BIM_PT_tab_cost,
ui.BIM_PT_tab_sequence,
# Facility management
ui.BIM_PT_tab_handover,
ui.BIM_PT_tab_operations,
# Quality and coordination
ui.BIM_PT_tab_quality_control,
ui.BIM_PT_tab_clash_detection,
ui.BIM_PT_tab_collaboration,
ui.BIM_PT_tab_sandbox,
# TODO: move this somewhere else and clean it up
ui.BIM_PT_section_plane,
ui.BIM_PT_section_with_cappings,
ui.BIM_PT_decorators_overlay,
ui.BIM_PT_snappping,
operator.BIM_UL_tab_panels,
operator.BIM_OT_toggle_panel_visibility,
operator.BIM_OT_bookmark_panel,
operator.BIM_OT_manage_tab_panels,
]
for mod in modules.values():
classes.extend(mod.classes)
addon_keymaps = []
icons = None
is_registering = False
original_scene_panels_polls: dict[bpy.types.Panel, Union[Callable, None]] = dict()
def on_register(scene):
global is_registering
if is_registering:
return
is_registering = True
handler.load_post(scene)
if not bpy.app.background:
bpy.app.handlers.depsgraph_update_post.remove(on_register)
is_registering = False
# Classes that need to be registered after modules (due to cross-module dependencies)
late_classes = (
ui.DefaultParameters, # Register before BIM_ADDON_preferences
ui.BIM_ADDON_preferences,
)
def register_classes(classes_to_register):
for cls in classes_to_register:
# Prevent crashes in Blender 4.4.0, see #6420.
if issubclass(cls, (ImportHelper, ExportHelper)):
assert getattr(cls, "bl_description", "") or cls.__doc__, cls
bpy.utils.register_class(cls)
def unregister_classes(classes_to_unregister):
for cls in reversed(classes_to_unregister):
if getattr(cls, "is_registered", None) is None:
bpy.utils.unregister_class(cls)
elif cls.is_registered:
bpy.utils.unregister_class(cls)
def register():
register_classes(classes)
bpy.app.handlers.depsgraph_update_post.append(on_register)
bpy.app.handlers.undo_post.append(handler.undo_post)
bpy.app.handlers.redo_post.append(handler.redo_post)
bpy.app.handlers.load_post.append(handler.load_post)
bpy.app.handlers.load_post.append(handler.loadIfcStore)
bpy.types.Scene.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties)
bpy.types.Scene.BIMSnapProperties = bpy.props.PointerProperty(type=prop.BIMSnapProperties)
bpy.types.Scene.BIMSnapGroups = bpy.props.PointerProperty(type=prop.BIMSnapGroups)
bpy.types.Screen.BIMAreaProperties = bpy.props.CollectionProperty(type=prop.BIMAreaProperties)
bpy.types.Screen.BIMTabProperties = bpy.props.PointerProperty(type=prop.BIMTabProperties)
bpy.types.Collection.BIMCollectionProperties = bpy.props.PointerProperty(type=prop.BIMCollectionProperties)
bpy.types.Object.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
bpy.types.Mesh.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
bpy.types.Curve.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
bpy.types.Camera.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
bpy.types.PointLight.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
if hasattr(bpy.types, "UI_MT_button_context_menu"):
bpy.types.UI_MT_button_context_menu.append(ui.draw_custom_context_menu)
bpy.types.STATUSBAR_HT_header.append(ui.draw_statusbar)
for mod in modules.values():
mod.register()
# Delay registering classes that depend on module classes
register_classes(late_classes)
wm = bpy.context.window_manager
if wm.keyconfigs.addon:
km = wm.keyconfigs.addon.keymaps.new(name="Window", space_type="EMPTY")
kmi = km.keymap_items.new("bim.switch_tab", "TAB", "PRESS", ctrl=True)
addon_keymaps.append((km, kmi))
global icons
icons_dir = os.path.join(cwd, "data", "icons")
icon_preview = bpy.utils.previews.new()
for filename in os.listdir(icons_dir):
if filename.endswith(".png"):
icon_name = os.path.splitext(filename)[0]
icon_path = os.path.join(icons_dir, filename)
icon_preview.load(icon_name, icon_path, "IMAGE")
icons = icon_preview
bpy.app.translations.register("bonsai", translations_dict)
import bonsai.tool as tool
tool.Blender.ensure_bin_in_path()
# RestrictedContext doesn't allow accessing scene attribute, postpone it for a bit.
bpy.app.timers.register(tool.Blender.setup_user_data_dir, first_interval=0.1)
bpy.types.Scene.active_tab_name = bpy.props.StringProperty()
bpy.types.Scene.tab_panels = bpy.props.CollectionProperty(type=bpy.types.PropertyGroup)
bpy.types.Scene.active_tab_panel_index = bpy.props.IntProperty()
def unregister():
global icons
bpy.utils.previews.remove(icons)
unregister_classes(classes)
bpy.app.handlers.load_post.remove(handler.load_post)
bpy.app.handlers.load_post.remove(handler.loadIfcStore)
del bpy.types.Scene.BIMProperties
del bpy.types.Collection.BIMCollectionProperties
del bpy.types.Object.BIMObjectProperties
del bpy.types.Mesh.BIMMeshProperties
del bpy.types.Curve.BIMMeshProperties
del bpy.types.Camera.BIMMeshProperties
del bpy.types.PointLight.BIMMeshProperties
if hasattr(bpy.types, "UI_MT_button_context_menu"):
bpy.types.UI_MT_button_context_menu.remove(ui.draw_custom_context_menu)
bpy.types.STATUSBAR_HT_header.remove(ui.draw_statusbar)
# Unregister late classes before modules
unregister_classes(late_classes)
for mod in reversed(list(modules.values())):
mod.unregister()
wm = bpy.context.window_manager
kc = wm.keyconfigs.addon
if kc:
for km, kmi in addon_keymaps:
km.keymap_items.remove(kmi)
addon_keymaps.clear()
import bonsai.tool as tool
# use tuple() as method will be removing keys from dict
for panel in tuple(original_scene_panels_polls.keys()):
tool.Blender.remove_scene_panel_override(panel)
bpy.app.translations.unregister("bonsai")
del bpy.types.Scene.active_tab_name
del bpy.types.Scene.tab_panels
del bpy.types.Scene.active_tab_panel_index