# IfcSverchok - IFC Sverchok extension # Copyright (C) 2022, 2023 Martina Jakubowska # # This file is part of IfcSverchok. # # IfcSverchok 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. # # IfcSverchok 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 IfcSverchok. If not, see . bl_info = { "name": "IFC for Sverchok", "author": "Martina Jakubowska, Dion Moult", "version": (0, 0, 999999), "blender": (2, 90, 0), "location": "Node Editor", "category": "Node", "description": "An extension to Sverchok to work with IFC data", "tracker_url": "https://github.com/IfcOpenShell/IfcOpenShell/issues", "warning": "", } import importlib import logging logger = logging.getLogger('sverchok.ifc') from sverchok.ui.nodeview_space_menu import add_node_menu def nodes_index(): return [ ( "IFC", [ ("ifc.create_file", "SvIfcCreateFile"), ("ifc.read_file", "SvIfcReadFile"), ("ifc.write_file", "SvIfcWriteFile"), ("ifc.create_entity", "SvIfcCreateEntity"), ("ifc.create_shape", "SvIfcCreateShape"), ("ifc.read_entity", "SvIfcReadEntity"), ("ifc.pick_ifc_class", "SvIfcPickIfcClass"), ("ifc.by_id", "SvIfcById"), ("ifc.by_guid", "SvIfcByGuid"), ("ifc.by_type", "SvIfcByType"), ("ifc.by_query", "SvIfcByQuery"), ("ifc.add", "SvIfcAdd"), ("ifc.add_pset", "SvIfcAddPset"), ("ifc.add_spatial_element", "SvIfcAddSpatialElement"), ("ifc.remove", "SvIfcRemove"), ("ifc.generate_guid", "SvIfcGenerateGuid"), ("ifc.get_property", "SvIfcGetProperty"), ("ifc.get_attribute", "SvIfcGetAttribute"), ("ifc.select_blender_objects", "SvIfcSelectBlenderObjects"), ("ifc.api", "SvIfcApi"), ("ifc.api_WIP", "SvIfcApiWIP"), ("ifc.bmesh_to_ifc", "SvIfcBMeshToIfcRepr"), ("ifc.sverchok_to_ifc", "SvIfcSverchokToIfcRepr"), ("ifc.create_project", "SvIfcCreateProject"), ("ifc.quick_project_setup", "SvIfcQuickProjectSetup"), ], ) ] node_categories = [ { "IFC": [ "SvIfcCreateFile", "SvIfcReadFile", "SvIfcWriteFile", "SvIfcCreateEntity", "SvIfcCreateShape", "SvIfcReadEntity", "SvIfcPickIfcClass", "SvIfcById", "SvIfcByGuid", "SvIfcByType", "SvIfcByQuery", "SvIfcAdd", "SvIfcAddPset", "SvIfcAddSpatialElement", "SvIfcRemove", "SvIfcGenerateGuid", "SvIfcGetProperty", "SvIfcGetAttribute", "SvIfcSelectBlenderObjects", "SvIfcApi", "SvIfcApiWIP", "SvIfcBMeshToIfcRepr", "SvIfcSverchokToIfcRepr", "SvIfcCreateProject", "SvIfcQuickProjectSetup", ] } ] def make_node_list(): modules = [] base_name = "ifcsverchok.nodes" index = nodes_index() for category, items in index: for module_name, node_name in items: module = importlib.import_module(f".{module_name}", base_name) modules.append(module) return modules imported_modules = make_node_list() reload_event = False import bpy from os.path import splitext import ifcopenshell from ifcsverchok.ifcstore import SvIfcStore class IFC_Sv_UpdateCurrent(bpy.types.Operator): """Update current Sverchok node tree""" bl_idname = "ifc.sverchok_update_current" bl_label = "Update current node tree" bl_options = {"REGISTER", "UNDO", "INTERNAL"} node_group: bpy.props.StringProperty(default="") force_mode: bpy.props.BoolProperty(default=False) # FIXME For now it's fine to center around buildings. # But for the future it'd be good to also allow other # infra-related spatial structure elements, such as IfcBridge. # https://github.com/IfcOpenShell/IfcOpenShell/pull/2576#discussion_r1016261407 def execute(self, context): self.file = SvIfcStore.purge() node_tree = context.space_data.node_tree if node_tree: if self.force_mode or node_tree.sv_process: try: bpy.context.window.cursor_set("WAIT") node_tree.force_update() finally: bpy.context.window.cursor_set("DEFAULT") self.report({"INFO"}, "Node tree updated.") return {"FINISHED"} class IFC_Sv_write_file(bpy.types.Operator): bl_idname = "ifc.write_file_panel" bl_label = "Write File" bl_options = {"REGISTER", "UNDO"} bl_description = "File path to write to." filepath: bpy.props.StringProperty(subtype="FILE_PATH") node_group: bpy.props.StringProperty(default="") force_mode: bpy.props.BoolProperty(default=False) @classmethod def poll(cls, context): return any("IFC" in n for n in context.space_data.edit_tree.nodes.keys()) def ensure_hirarchy(self, file): elements_in_buildings = set() if len(file.by_type("IfcBuilding")) == 0: my_building = ifcopenshell.api.run("root.create_entity", file, ifc_class="IfcBuilding", name="My Building") elements = ifcopenshell.util.element.get_decomposition(my_building) else: for building in file.by_type("IfcBuilding"): elements = ifcopenshell.util.element.get_decomposition(building) elements_in_buildings.update(elements) for spatial in file.by_type("IfcSpatialElement") or file.by_type("IfcSpatialStructureElement"): if not (spatial.is_a("IfcSite") or spatial.is_a("IfcBuilding")) and (spatial not in elements_in_buildings): elements = ifcopenshell.util.element.get_decomposition(spatial) ifcopenshell.api.run( "aggregate.assign_object", file, product=spatial, relating_object=file.by_type("IfcBuilding")[0] ) for building in file.by_type("IfcBuilding"): elements = ifcopenshell.util.element.get_decomposition(building) elements_in_buildings.update(elements) elements = file.by_type("IfcElement") for element in elements: if element not in elements_in_buildings: ifcopenshell.api.run( "spatial.assign_container", file, product=element, relating_structure=file.by_type("IfcBuilding")[0] ) for building in file.by_type("IfcBuilding"): elements = ifcopenshell.util.element.get_decomposition(building) if not building.Decomposes: if len(file.by_type("IfcSite")) == 0: ifcopenshell.api.run("root.create_entity", file, ifc_class="IfcSite", name="My Site") ifcopenshell.api.run( "aggregate.assign_object", file, product=building, relating_object=file.by_type("IfcSite")[0] ) try: if file.by_type("IfcSite")[0].Decomposes[0].RelatingObject.is_a("IfcProject"): continue except IndexError: pass ifcopenshell.api.run( "aggregate.assign_object", file, product=file.by_type("IfcSite")[0], relating_object=file.by_type("IfcProject")[0], ) self.file = file return def execute(self, context): self.file = SvIfcStore.file if not self.file: raise Exception("No IFC file in SvIfcStore.") _, ext = splitext(self.filepath) # FIXME checking for extension not necessary if not ext: raise Exception("Bad path. Provide a path to a file ending with a file extension.") else: self.ensure_hirarchy(self.file) self.file.write(self.filepath) self.report({"INFO"}, f"File written to: {self.filepath}") return {"FINISHED"} def invoke(self, context, event): context.window_manager.fileselect_add(self) return {"RUNNING_MODAL"} class IFC_PT_write_file_panel(bpy.types.Panel): bl_idname = "IFC_PT_write_file_panel" bl_label = "Write IFC to file" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "NODE_EDITOR" bl_region_type = "UI" bl_category = "IfcSverchok" @classmethod def poll(cls, context): if context.space_data.edit_tree and any("IFC" in n for n in context.space_data.edit_tree.nodes.keys()): return True def draw(self, context): ng = context.space_data.node_tree layout = self.layout row = layout.split(factor=0.2, align=True) row = layout.row() row2 = layout.row() row.operator("ifc.sverchok_update_current", text="IFC Re-run all nodes") row2.operator("ifc.write_file_panel") CLASSES = [IFC_Sv_UpdateCurrent, IFC_Sv_write_file, IFC_PT_write_file_panel] class SvExCategoryProvider(object): def __init__(self, identifier, menu): self.identifier = identifier self.menu = menu def get_categories(self): return self.menu our_menu_classes = [] add_node_menu.append_from_config(node_categories) def register(): node_modules = make_node_list() for module in node_modules: module.register() for cls in CLASSES: bpy.utils.register_class(cls) add_node_menu.register() def unregister(): global imported_modules for module in reversed(imported_modules): try: module.unregister() except: logger.error(f"Unable to unregister module: {module}") for cls in reversed(CLASSES): try: bpy.utils.unregister_class(cls) except: logger.error(f"Unable to unregister class: {cls}")