diff --git a/src/ifcsverchok/__init__.py b/src/ifcsverchok/__init__.py index 5749501973..a747a7aa34 100644 --- a/src/ifcsverchok/__init__.py +++ b/src/ifcsverchok/__init__.py @@ -1,4 +1,3 @@ - # IfcSverchok - IFC Sverchok extension # Copyright (C) 2020, 2021 Dion Moult # @@ -36,10 +35,14 @@ import sverchok from sverchok.core import sv_registration_utils, make_node_list from sverchok.utils import auto_gather_node_classes, get_node_class_reference from sverchok.menu import SverchNodeItem, SverchNodeCategory, register_node_panels -from sverchok.utils.extra_categories import register_extra_category_provider, unregister_extra_category_provider +from sverchok.utils.extra_categories import ( + register_extra_category_provider, + unregister_extra_category_provider, +) from sverchok.ui.nodeview_space_menu import make_extra_category_menus from sverchok.utils.logging import info, debug - +import asyncio +import time def nodes_index(): return [ @@ -52,17 +55,26 @@ def nodes_index(): ("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") + ], ) ] @@ -84,7 +96,131 @@ imported_modules = make_node_list() reload_event = False import bpy +import os +from os.path import abspath, splitext +import ifcopenshell +from ifcsverchok.ifcstore import SvIfcStore +from sverchok.data_structure import flatten_data +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) + + def execute(self, context): + print("#"*10, "Update current node tree") + self.file = SvIfcStore.purge() + self.file = SvIfcStore.get_file() + self.file.write("/Users/martina/Documents/GSoC/CodeTests/IfcFileTest_7_11_purged.ifc") + 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 = [] + if not 0 <= 0 < len(file.by_type("IfcBuilding")): + 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.extend(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]) + + elements_in_buildings_after = [] + for building in file.by_type("IfcBuilding"): + elements = ifcopenshell.util.element.get_decomposition(building) + elements_in_buildings_after.extend(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 not 0 <= 0 < len(file.by_type("IfcSite")): + 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) + if not ext: + raise Exception("Bad path. Provide a path to a file.") + 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] def register_nodes(): node_modules = make_node_list() @@ -109,7 +245,10 @@ def make_menu(): nodetype = item[1] rna = get_node_class_reference(nodetype) if not rna: - info("Node `%s' is not available (probably due to missing dependencies).", nodetype) + info( + "Node `%s' is not available (probably due to missing dependencies).", + nodetype, + ) else: node_item = SverchNodeItem.new(nodetype) node_items.append(node_item) @@ -135,13 +274,16 @@ def register(): global our_menu_classes debug("Registering ifcsverchok") - + for klass in CLASSES: + bpy.utils.register_class(klass) register_nodes() extra_nodes = importlib.import_module(".nodes", "ifcsverchok") auto_gather_node_classes(extra_nodes) menu = make_menu() menu_category_provider = SvExCategoryProvider("IFCSVERCHOK", menu) - register_extra_category_provider(menu_category_provider) # if 'IFCSVERCHOK' in nodeitems_utils._node_categories: + register_extra_category_provider( + menu_category_provider + ) # if 'IFCSVERCHOK' in nodeitems_utils._node_categories: nodeitems_utils.register_node_categories("IFCSVERCHOK", menu) our_menu_classes = make_extra_category_menus() @@ -158,3 +300,5 @@ def unregister(): print(e) unregister_extra_category_provider("IFCSVERCHOK") unregister_nodes() + for klass in CLASSES: + bpy.utils.unregister_class(klass) diff --git a/src/ifcsverchok/ifcstore.py b/src/ifcsverchok/ifcstore.py new file mode 100644 index 0000000000..9efff80ec6 --- /dev/null +++ b/src/ifcsverchok/ifcstore.py @@ -0,0 +1,100 @@ +# import os +import bpy +# import uuid +# import hashlib +# import zipfile +# import tempfile +import ifcopenshell +from ifcopenshell import template +# import blenderbim.bim.handler +# from pathlib import Path + +class SvIfcStore: + path = "" + file = None + schema = None + cache = None + cache_path = None + id_map = {} + guid_map = {} + deleted_ids = set() + edited_objs = set() + pset_template_path = "" + pset_template_file = None + library_path = "" + library_file = None + element_listeners = set() + undo_redo_stack_objects = set() + undo_redo_stack_object_names = {} + current_transaction = "" + last_transaction = "" + history = [] + future = [] + schema_identifiers = ["IFC4", "IFC2X3"] + + @staticmethod + def purge(): + SvIfcStore.path = "" + SvIfcStore.file = None + SvIfcStore.schema = None + SvIfcStore.cache = None + SvIfcStore.cache_path = None + SvIfcStore.id_map = {} + SvIfcStore.guid_map = {} + SvIfcStore.deleted_ids = set() + SvIfcStore.edited_objs = set() + SvIfcStore.pset_template_path = "" + SvIfcStore.pset_template_file = None + SvIfcStore.library_path = "" + SvIfcStore.library_file = None + SvIfcStore.last_transaction = "" + SvIfcStore.history = [] + SvIfcStore.future = [] + SvIfcStore.schema_identifiers = ["IFC4", "IFC2X3"] + + + @staticmethod + def create_boilerplate(): + + file = template.create( + filename="IfcSverchokDemoFile", + organization=None, + creator=None, + project_name="IfcSverchokDemoProject", + ) + if bpy.context.scene.unit_settings.system == 'IMPERIAL': + #TODO change units to imperial + pass + # model = ifcopenshell.api.run("context.add_context", file, context_type="Model") + model = ifcopenshell.util.representation.get_context(file, context="Model") + print("model: ", model) + context = ifcopenshell.api.run( + "context.add_context", + file, + context_type="Model", + context_identifier="Body", + target_view="MODEL_VIEW", + parent=model, + ) + # site = ifcopenshell.api.run("root.create_entity", file, ifc_class="IfcSite", name="My Site") + # building = ifcopenshell.api.run( + # "root.create_entity", file, ifc_class="IfcBuilding", name="My Building" + # ) + # building_storey = ifcopenshell.api.run( + # "root.create_entity", file, ifc_class="IfcBuildingStorey", name="My Storey" + # ) + + # ifcopenshell.api.run("aggregate.assign_object", file, product=site, relating_object=file.by_type("IfcProject")[0]) + # ifcopenshell.api.run("aggregate.assign_object", file, product=building, relating_object=site) + # ifcopenshell.api.run("aggregate.assign_object", file, product=building_storey, relating_object=building) + + SvIfcStore.file = file + return SvIfcStore.file + + + + @staticmethod + def get_file(): + if SvIfcStore.file is None: + SvIfcStore.create_boilerplate() + return SvIfcStore.file diff --git a/src/ifcsverchok/nodes/ifc/add_pset.py b/src/ifcsverchok/nodes/ifc/add_pset.py new file mode 100644 index 0000000000..bb56caffd1 --- /dev/null +++ b/src/ifcsverchok/nodes/ifc/add_pset.py @@ -0,0 +1,75 @@ +import bpy + +import ifcopenshell +import ifcsverchok.helper +from ifcsverchok.ifcstore import SvIfcStore + +import bpy +import json +import ifcopenshell + +from bpy.props import StringProperty +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode + + +class SvIfcAddPset(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.helper.SvIfcCore): + bl_idname = "SvIfcAddPset" + bl_label = "IFC Add Pset" + Name: StringProperty(name="Name", update=updateNode, default="My_Pset") + Properties: StringProperty(name="Properties", update=updateNode, default="{\"Foo\":\"Bar\"}") + Elements: StringProperty(name="Elements", update=updateNode) + + def sv_init(self, context): + self.inputs.new("SvStringsSocket", "Name").prop_name = "Name" + self.inputs.new("SvTextSocket", "Properties").prop_name = "Properties" + self.inputs.new("SvStringsSocket", "Elements").prop_name = "Elements" + self.outputs.new("SvStringsSocket", "entity") + self.outputs.new("SvStringsSocket", "file") + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + name = self.inputs["Name"].sv_get()[0][0] + properties = self.inputs["Properties"].sv_get()[0][0] + elements = self.inputs["Elements"].sv_get()[0] + + if SvIfcStore.file is None: + SvIfcStore.file = SvIfcStore.create_boilerplate() + self.file = SvIfcStore.get_file() + + if self.node_id not in SvIfcStore.id_map.values(): + element = self.create(name, properties, elements) + else: + element = self.edit(name, properties, elements) + + self.outputs["entity"].sv_set([[element]]) + self.outputs["file"].sv_set([[self.file]]) + + def create(self, name, properties, elements): + results = [] + for element in elements: + result = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name=name) + ifcopenshell.api.run("pset.edit_pset", self.file, pset=result, properties=json.loads(properties)) + SvIfcStore.id_map[result.id()] = self.node_id + results.append(result) + return result + + def edit(self, name, properties, elements): + result = self.get_existing_element() + ifcopenshell.api.run("pset.edit_pset", self.file, pset=result, name=name, properties=json.loads(properties)) + return result + + def get_existing_element(self): + entity_id = list(SvIfcStore.id_map.keys())[list(SvIfcStore.id_map.values()).index(self.node_id)] + return self.file.by_id(entity_id) + + +def register(): + bpy.utils.register_class(SvIfcAddPset) + + +def unregister(): + bpy.utils.unregister_class(SvIfcAddPset) + SvIfcStore.purge() diff --git a/src/ifcsverchok/nodes/ifc/add_spatial_element.py b/src/ifcsverchok/nodes/ifc/add_spatial_element.py new file mode 100644 index 0000000000..228982facc --- /dev/null +++ b/src/ifcsverchok/nodes/ifc/add_spatial_element.py @@ -0,0 +1,149 @@ +import bpy + +import ifcopenshell +import ifcsverchok.helper +from ifcsverchok.ifcstore import SvIfcStore + +from bpy.props import StringProperty +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode, flatten_data, repeat_last_for_length, ensure_min_nesting + + +class SvIfcAddSpatialElement(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.helper.SvIfcCore): + bl_idname = "SvIfcAddSpatialElement" + bl_label = "IFC Add Spatial Element" + node_dict = {} + + Names: StringProperty(name="Name(s)", update=updateNode) + IfcClass: StringProperty(name="IFC Class", update=updateNode, default="IfcSpace") + Elements: StringProperty(name="Elements", update=updateNode) + + def sv_init(self, context): + self.inputs.new("SvStringsSocket", "Names").prop_name = "Names" + self.inputs.new("SvStringsSocket", "IfcClass").prop_name = "IfcClass" + self.inputs.new("SvStringsSocket", "Elements").prop_name = "Elements" + self.outputs.new("SvStringsSocket", "Entities") + self.node_dict[hash(self)] = {} + + def draw_buttons(self, context, layout): + layout.operator("node.sv_ifc_tooltip", text="", icon="QUESTION", emboss=False).tooltip = "Ifc entity by type." + + def process(self): + self.sv_input_names = [i.name for i in self.inputs] + if hash(self) not in self.node_dict: + self.node_dict[hash(self)] = {} #happens if node is already on canvas when blender loads + if not self.node_dict[hash(self)]: + self.node_dict[hash(self)].update(dict.fromkeys(self.sv_input_names, 0)) + + if not any(socket.is_linked for socket in self.outputs): + return + edit = False + edit_elements = False + for i in range(len(self.inputs)): + input = self.inputs[self.sv_input_names[i]].sv_get(deepcopy=True, default =[]) + if isinstance(self.node_dict[hash(self)][self.inputs[i].name], list) and input != self.node_dict[hash(self)][self.inputs[i].name]: + edit = True + if self.inputs[i].name == "Elements": + edit_elements = True + self.node_dict[hash(self)][self.inputs[i].name] = input.copy() + + self.names = flatten_data(self.inputs["Names"].sv_get(), target_level=1) + self.ifc_class = flatten_data(self.inputs["IfcClass"].sv_get(), target_level=1)[0] + self.elements = ensure_min_nesting(self.inputs["Elements"].sv_get(), 2) + self.elements = flatten_data(self.elements, target_level=2) + if not self.elements[0][0]: + raise Exception('Mandatory input "Element(s)" is missing.') + return + self.file = SvIfcStore.get_file() + self.elements = [[self.file.by_id(step_id) for step_id in element] for element in self.elements] + + if "len" not in self.node_dict[hash(self)]: + self.node_dict[hash(self)]["len"] = 0 + self.names = self.repeat_input_unique(self.names, len(self.elements)) + + if (self.node_id not in SvIfcStore.id_map) or (len(self.elements) != self.node_dict[hash(self)]["len"]): + self.remove() + elements = self.create() + self.node_dict[hash(self)]["len"] = len(self.elements) + else: + if edit is True: + elements = self.edit(edit_elements) + else: + elements = SvIfcStore.id_map[self.node_id] + self.outputs["Entities"].sv_set(elements) + + def create(self, index=None): + spatial_ids = [] + iterator = range(len(self.elements)) + if index is not None: + iterator = [index] + for i in iterator: + result = ifcopenshell.api.run("root.create_entity", self.file, name=self.names[i], ifc_class=self.ifc_class) + for items in self.elements[i]: + if items.is_a("IfcSpatialElement") or items.is_a("IfcSpatialStructureElement"): + ifcopenshell.api.run("aggregate.assign_object", self.file, product=items, relating_object=result) + else: + ifcopenshell.api.run("spatial.assign_container", self.file, product=items, relating_structure=result) + SvIfcStore.id_map.setdefault(self.node_id, []).append(result.id()) + spatial_ids.append(result.id()) + return spatial_ids + + def edit(self, edit_elements): + spatial_ids = [] + id_map_copy = SvIfcStore.id_map[self.node_id].copy() + for i, element in enumerate(self.elements): + try: + result_id = id_map_copy[i] + except IndexError: + id = self.create(index=i) + spatial_ids.append(id[0]) + continue + result = self.file.by_id(result_id) + result.Name = self.names[i] + if edit_elements: + subelements = ifcopenshell.util.element.get_decomposition(result) + subelements = set(subelements) + for element in self.elements[i]: + element_set = set([element]) + for removed_element in subelements - element_set: + if removed_element.is_a("IfcSpatialElement") or removed_element.is_a("IfcSpatialStructureElement"): + ifcopenshell.api.run("aggregate.unassign_object", self.file, product=removed_element, relating_object=result) + else: + ifcopenshell.api.run("spatial.unassign_container", self.file, product=removed_element, relating_object=result) + for added_element in element_set - subelements: + if added_element.is_a("IfcSpatialElement") or added_element.is_a("IfcSpatialStructureElement"): + ifcopenshell.api.run("aggregate.assign_object", self.file, product=added_element, relating_object=result) + else: + ifcopenshell.api.run("spatial.assign_container", self.file, product=added_element, relating_structure=result) + spatial_ids.append(result.id()) + SvIfcStore.id_map[self.node_id] = spatial_ids + return spatial_ids + + def remove(self): + if self.node_id in SvIfcStore.id_map: + for element_id in SvIfcStore.id_map[self.node_id]: + element = self.file.by_id(element_id) + ifcopenshell.api.run("root.remove_product", self.file, product=element) + del SvIfcStore.id_map[self.node_id] + + def repeat_input_unique(self, input, count): + input = repeat_last_for_length(input, count, deepcopy=False) + if input[0]: + input = [a if not (s:=sum(j == a for j in input[:i])) else f'{a}-{s+1}' for i, a in enumerate(input)] # add number to duplicates + return input + + def sv_free(self): + try: + del SvIfcStore.id_map[self.node_id] + del self.node_dict[hash(self)] + print('Node was deleted') + except KeyError or AttributeError: + pass + +def register(): + bpy.utils.register_class(SvIfcAddSpatialElement) + + +def unregister(): + bpy.utils.unregister_class(SvIfcAddSpatialElement) + SvIfcStore.purge() diff --git a/src/ifcsverchok/nodes/ifc/api.py b/src/ifcsverchok/nodes/ifc/api.py index f81e1a9948..7dbcc77acf 100644 --- a/src/ifcsverchok/nodes/ifc/api.py +++ b/src/ifcsverchok/nodes/ifc/api.py @@ -27,6 +27,7 @@ from sverchok.data_structure import updateNode def update_usecase(self, context): + print("API - running update usecase!") module_usecase = self.get_module_usecase() if module_usecase: self.generate_node(*module_usecase) @@ -104,4 +105,4 @@ def register(): def unregister(): bpy.utils.unregister_class(SvIfcApi) - bpy.utils.unregister_class(SvIfcTooltip) + bpy.utils.unregister_class(SvIfcTooltip) \ No newline at end of file diff --git a/src/ifcsverchok/nodes/ifc/api_WIP.py b/src/ifcsverchok/nodes/ifc/api_WIP.py new file mode 100644 index 0000000000..c546d07a64 --- /dev/null +++ b/src/ifcsverchok/nodes/ifc/api_WIP.py @@ -0,0 +1,147 @@ + +# IfcSverchok - IFC Sverchok extension +# Copyright (C) 2020, 2021 Dion Moult +# +# 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 . + +import bpy +import ifcopenshell +import ifcopenshell.api +import ifcsverchok.helper +from bpy.props import StringProperty, EnumProperty +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode +import importlib + + + +class SvIfcTooltip(bpy.types.Operator): + bl_idname = "node.sv_ifc_tooltip" + bl_label = "IFC Info" + tooltip: bpy.props.StringProperty() + + @classmethod + def description(cls, context, properties): + return properties.tooltip + + def execute(self, context): + return {"FINISHED"} + + +class SvIfcApiWIP(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.helper.SvIfcCore): + + # def update_usecase(self, context): + # # update_usecase is not getting run rn +# module_usecase = self.get_module_usecase() +# if module_usecase: +# self.generate_node(*module_usecase) + + bl_idname = "SvIfcApiWIP" + bl_label = "IFC API WIP" + tooltip: StringProperty(name="Tooltip") + usecase: StringProperty(name="usecase", update=updateNode) + current_usecase: StringProperty(name="current_usecase") + + def sv_init(self, context): + self.inputs.new("SvStringsSocket", "usecase").prop_name = "usecase" + #input_socket.tooltip = "ifcopenshell.api usecase, written like 'module.usecase' \n E.g.: 'project.create_file'" + self.outputs.new("SvVerticesSocket", "file") + + def draw_buttons(self, context, layout): + op = layout.operator("node.sv_ifc_tooltip", text="", icon="QUESTION", emboss=False).tooltip = "ifcopenshell.api usecase, written like 'module.usecase' \n E.g.: 'project.create_file" + # op.tooltip = self.tooltip + + def process(self): + self.sv_input_names = ["usecase"] + module_usecase = self.inputs["usecase"].sv_get()[0][0] + if module_usecase: + try: + module_usecase = self.get_module_usecase() + except: + raise Exception( + f"Couldn't run generate_node(). Module usecase: {module_usecase}" + ) + if '.'.join(module_usecase) != self.current_usecase: + self.generate_node(*module_usecase) + try: + for i in range(0, len(self.inputs)): + input = self.inputs[i].sv_get() + except: + # This occurs when a blender save file is reloaded + self.generate_node(*module_usecase) + + self.sv_input_names = [i.name for i in self.inputs] + + super().process() # super().process() uses zip_long_repeat() which doesn't take objects like bmesh + + + def get_module_usecase(self): + usecase = self.inputs["usecase"].sv_get()[0][0] + if usecase: + return usecase.split(".") + + def generate_node(self, module, usecase): + importlib.import_module(f"ifcopenshell.api.{module}.{usecase}") + local_module = getattr(getattr(ifcopenshell.api, module), usecase) + + node_inputs = {} + if hasattr(local_module.Usecase(local_module), "file"): + node_inputs["file"] = None + node_inputs.update(getattr(local_module.Usecase(local_module), "settings", {})) + # print("node inputs: ", node_inputs) + + while len(self.inputs) > 1: + self.inputs.remove(self.inputs[-1]) + + if node_inputs: + self.tooltip = "" + for name, data in node_inputs.items(): + setattr(SvIfcApiWIP, name, StringProperty(name=name)) + self.inputs.new("SvStringsSocket", name).prop_name = name + if data is not None: + self.tooltip = f"{name} ({data}): {data}\n" + else: + self.tooltip = f"{name}: None\n" + self.tooltip = self.tooltip.strip() + self.current_usecase = '.'.join([module, usecase]) + + + def process_ifc(self, usecase, *setting_values): + + if usecase and setting_values: + settings = dict(zip(self.sv_input_names[1:], setting_values)) + settings = {k: v for k, v in settings.items() if v != ""} + try: + if "file" in settings: + file = settings["file"] + settings.pop("file") + self.outputs["file"].sv_set([ifcopenshell.api.run(usecase, file, **settings)]) + else: + self.outputs["file"].sv_set([ifcopenshell.api.run(usecase, **settings)]) + except: + raise Exception(f"Couldn't run usecase.") + + + + +def register(): + bpy.utils.register_class(SvIfcTooltip) + bpy.utils.register_class(SvIfcApiWIP) + + +def unregister(): + bpy.utils.unregister_class(SvIfcApiWIP) + bpy.utils.unregister_class(SvIfcTooltip) \ No newline at end of file diff --git a/src/ifcsverchok/nodes/ifc/bmesh_to_ifc.py b/src/ifcsverchok/nodes/ifc/bmesh_to_ifc.py new file mode 100644 index 0000000000..426804d5d4 --- /dev/null +++ b/src/ifcsverchok/nodes/ifc/bmesh_to_ifc.py @@ -0,0 +1,219 @@ +# IfcSverchok - IFC Sverchok extension +# Copyright (C) 2020, 2021 Dion Moult +# +# 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 . + +from copy import deepcopy +from decimal import Context +from email.policy import default +import bpy +import ifcopenshell +import ifcsverchok.helper +import ifcopenshell.api +from ifcsverchok.ifcstore import SvIfcStore +import blenderbim.tool as tool +import blenderbim.core.geometry as core +from bpy.props import StringProperty, EnumProperty, IntProperty, BoolProperty, PointerProperty +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import (updateNode, flatten_data, fixed_iter, flat_iter) +from blenderbim.bim.module.root.prop import get_contexts +from sverchok.data_structure import zip_long_repeat, node_id +from sverchok.core.socket_data import sv_get_socket + +from itertools import chain, cycle + +class SvIfcBMeshToIfcRepr(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.helper.SvIfcCore): + """ + Triggers: BMesh to Ifc Repr + Tooltip: Blender mesh to Ifc Shape Representation + """ + bl_idname = "SvIfcBMeshToIfcRepr" + bl_label = "IFC Blender Mesh to IFC Repr" + node_dict = {} + + is_scene_dependent = True # if True and is_interactive then the node will be updated upon scene changes + + def refresh_node(self, context): + if self.refresh_local: + updateNode(self, context) + self.refresh_local = False + + refresh_local: BoolProperty(name="Update Node", description="Update Node", update=refresh_node) + + context_types = [ + ('Model', 'Model', 'Context type: Model', 0), + ('Plan', 'Plan', 'Context type: Plan', 1), + ] + + context_identifiers = [ + ('Body', 'Body', 'Context identifier: Body', 0), + ('Annotation', 'Annotation', 'Context identifier: Annotation', 1), + ('Box', 'Box', 'Context identifier: Box', 2), + ('Axis', 'Axis', 'Context identifier: Axis', 3), + ] + target_views = [ + ('MODEL_VIEW', 'MODEL_VIEW', 'Target View: MODEL_VIEW', 0), + ('PLAN_VIEW', 'PLAN_VIEW', 'Target View: PLAN_VIEW', 1), + ('GRAPH_VIEW', 'GRAPH_VIEW', 'Target View: GRAPH_VIEW', 2), + ('SKETCH_VIEW', 'SKETCH_VIEW', 'Target View: SKETCH_VIEW', 3), + ] + blender_objects: PointerProperty(name="Blender Mesh(es)", description="Blender Mesh Object(s)",update=updateNode, type=bpy.types.Object) + context_type: EnumProperty(name="Context Type", description="Default: Model", default="Model",items=context_types,update=updateNode) + context_identifier: EnumProperty(name="Context Identifier", description="Default: Body", default="Body", items=context_identifiers, update=updateNode) + target_view: EnumProperty(name="Target View", description="Default: MODEL VIEW", default="MODEL_VIEW",items=target_views, update=updateNode) + tooltip: StringProperty(name="Tooltip") + + + def sv_init(self, context): + self.inputs.new("SvStringsSocket", "context_type").prop_name = "context_type" + self.inputs.new("SvStringsSocket", "context_identifier").prop_name = "context_identifier" + self.inputs.new("SvStringsSocket", "target_view").prop_name = "target_view" + self.inputs.new("SvObjectSocket", "blender_objects").prop_name = "blender_objects" #no prop for now + self.outputs.new("SvVerticesSocket", "Representations") + self.outputs.new("SvMatrixSocket", "Locations") + + def draw_buttons(self, context, layout): + layout.operator("node.sv_ifc_tooltip", text="", icon="QUESTION", emboss=False).tooltip = "Blender mesh to Ifc Shape Representation. \nTakes one or multiple geometries." + + row = layout.row(align=True) + row.prop(self, 'is_interactive', icon='SCENE_DATA', icon_only=True) + row.prop(self, 'refresh_local', icon='FILE_REFRESH') + + def process(self): + self.sv_input_names = [i.name for i in self.inputs] + + if hash(self) not in self.node_dict: + self.node_dict[hash(self)] = {} #happens if node is already on canvas when blender loads + if not self.node_dict[hash(self)]: + self.node_dict[hash(self)].update(dict.fromkeys(self.sv_input_names, 0)) + + if not self.inputs["blender_objects"].sv_get()[0]: + return + edit = False + for i in range(len(self.inputs)): + input = self.inputs[i].sv_get(deepcopy=True) + if isinstance(self.node_dict[hash(self)][self.inputs[i].name], list) and input != self.node_dict[hash(self)][self.inputs[i].name]: + edit = True + self.node_dict[hash(self)][self.inputs[i].name] = input.copy() + + blender_objects = self.inputs["blender_objects"].sv_get() + self.file = SvIfcStore.get_file() + + if self.refresh_local: + edit = True + if self.node_id not in SvIfcStore.id_map: + representations, locations = self.create(blender_objects) + else: + if edit is True: + self.edit() + representations, locations = self.create(blender_objects) + else: + representations = SvIfcStore.id_map[self.node_id]["Representations"] + locations = SvIfcStore.id_map[self.node_id]["Locations"] + + print("representations: ", representations) + print("locations: ", locations) + + self.outputs["Representations"].sv_set(representations) + self.outputs["Locations"].sv_set(locations) + + def create(self, blender_objects): + representations_ids = [] + locations = [] + for blender_object in blender_objects: + if blender_object.type == 'MESH': + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + blender_object.select_set(True) + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.separate(type='LOOSE') + for obj in bpy.context.selected_objects: + representation = ifcopenshell.api.run("geometry.add_representation", self.file, should_run_listeners=False,blender_object = obj, geometry=obj.data, context = self.get_context()) + if not representation: + raise Exception("Couldn't create representation. Possibly wrong context.") + representations_ids.append(representation.id()) + SvIfcStore.id_map.setdefault(self.node_id, {}).setdefault("Representations", []).append(representation.id()) + locations.append(blender_object.matrix_world) + SvIfcStore.id_map.setdefault(self.node_id, {}).setdefault("Locations", []).append(blender_object.matrix_world) + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + return representations_ids, locations + + def edit(self): + if "Representations" not in SvIfcStore.id_map[self.node_id]: + return + for step_id in SvIfcStore.id_map[self.node_id]["Representations"]: + ifcopenshell.api.run("geometry.remove_representation", self.file, representation=self.file.by_id(step_id)) + del SvIfcStore.id_map[self.node_id]["Representations"] + del SvIfcStore.id_map[self.node_id]["Locations"] + return + + def get_existing_element(self): + results = [] + for step_id in SvIfcStore.id_map[self.node_id]["Representations"]: + results.append(self.file.by_id(step_id)) + return results + + def get_context(self): + context = ifcopenshell.util.representation.get_context(self.file, self.context_type, self.context_identifier, self.target_view) + if not context: + parent = ifcopenshell.util.representation.get_context(self.file, self.context_type) + if not parent: + parent = ifcopenshell.api.run("context.add_context", self.file, context_type=self.context_type) + context = ifcopenshell.api.run( + "context.add_context", + self.file, + context_type=self.context_type, + context_identifier=self.context_identifier, + target_view=self.target_view, + parent=parent, + ) + SvIfcStore.id_map.setdefault(self.node_id, {}).setdefault("Contexts", []).append(context.id()) + return context + + + def sv_free(self): + try: + self.file = SvIfcStore.get_file() + if "Representations" in SvIfcStore.id_map[self.node_id]: + for step_id in SvIfcStore.id_map[self.node_id]["Representations"]: + ifcopenshell.api.run("geometry.remove_representation", self.file, representation=self.file.by_id(step_id)) + + if "Contexts" in SvIfcStore.id_map[self.node_id]: + for context_id in SvIfcStore.id_map[self.node_id]["Contexts"]: + context = self.file.by_id(context_id) + if not self.file.get_inverse(context): + if self.file.by_id(context_id).ParentContext: + parent = self.file.by_id(context_id).ParentContext + ifcopenshell.api.run("context.remove_context", self.file, context=context) + if parent: + if not self.file.get_inverse(parent): + ifcopenshell.api.run("context.remove_context", self.file, context=parent) + print("Removed context with step ID: ", context_id) + SvIfcStore.id_map[self.node_id]["Contexts"].remove(context_id) + del SvIfcStore.id_map[self.node_id] + del self.node_dict[hash(self)] + print('Node was deleted') + except KeyError or AttributeError: + pass + + +def register(): + bpy.utils.register_class(SvIfcBMeshToIfcRepr) + + +def unregister(): + bpy.utils.unregister_class(SvIfcBMeshToIfcRepr) diff --git a/src/ifcsverchok/nodes/ifc/by_id.py b/src/ifcsverchok/nodes/ifc/by_id.py index a2888e8475..c67ac956a9 100644 --- a/src/ifcsverchok/nodes/ifc/by_id.py +++ b/src/ifcsverchok/nodes/ifc/by_id.py @@ -20,29 +20,32 @@ import bpy import ifcopenshell import ifcsverchok.helper +from ifcsverchok.ifcstore import SvIfcStore from bpy.props import StringProperty from sverchok.node_tree import SverchCustomTreeNode -from sverchok.data_structure import updateNode +from sverchok.data_structure import updateNode, flatten_data class SvIfcById(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.helper.SvIfcCore): bl_idname = "SvIfcById" bl_label = "IFC By Id" - file: StringProperty(name="file", update=updateNode) - id: StringProperty(name="id", update=updateNode) + id: StringProperty(name="Id(s)", update=updateNode, ) def sv_init(self, context): - self.inputs.new("SvStringsSocket", "file").prop_name = "file" self.inputs.new("SvStringsSocket", "id").prop_name = "id" - self.outputs.new("SvStringsSocket", "entity") + self.outputs.new("SvStringsSocket", "Entities") + + def draw_buttons(self, context, layout): + layout.operator("node.sv_ifc_tooltip", text="", icon="QUESTION", emboss=False).tooltip = "Get IFC element by step id. Takes one or multiple step ids." def process(self): - self.sv_input_names = ["file", "id"] - super().process() - - def process_ifc(self, file, id): - self.outputs["entity"].sv_set([[file.by_id(int(id))]]) - + self.ids = flatten_data(self.inputs["id"].sv_get(), target_level=1) + print(self.ids) + if not self.ids[0]: + return + self.file = SvIfcStore.get_file() + self.entities = [self.file.by_id(int(step_id)) for step_id in self.ids] + self.outputs["Entities"].sv_set(self.entities) def register(): bpy.utils.register_class(SvIfcById) diff --git a/src/ifcsverchok/nodes/ifc/by_type.py b/src/ifcsverchok/nodes/ifc/by_type.py index ae91242e1a..b6ed6bc7b1 100644 --- a/src/ifcsverchok/nodes/ifc/by_type.py +++ b/src/ifcsverchok/nodes/ifc/by_type.py @@ -21,13 +21,14 @@ import bpy import ifcopenshell import ifcsverchok.helper from bpy.props import StringProperty, EnumProperty +from ifcsverchok.ifcstore import SvIfcStore from sverchok.node_tree import SverchCustomTreeNode -from sverchok.data_structure import updateNode - +from sverchok.data_structure import updateNode, flatten_data +from sverchok.utils.handle_blender_data import keep_enum_reference def get_ifc_products(self, context): ifc_products = getattr(self, "ifc_products", []) - file = self.inputs["file"].sv_get()[0][0] + file = SvIfcStore.get_file() if not file: return [] if ifc_products and file: @@ -56,12 +57,11 @@ def update_ifc_products(self, context): if hasattr(self, "ifc_classes"): self.ifc_classes.clear() - def get_ifc_classes(self, context): ifc_classes = getattr(self, "ifc_classes", []) if ifc_classes: return self.ifc_classes - file = self.inputs["file"].sv_get()[0][0] + file = SvIfcStore.get_file() if not file: return [] schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(file.schema) @@ -83,31 +83,35 @@ def get_ifc_classes(self, context): class SvIfcByType(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.helper.SvIfcCore): bl_idname = "SvIfcByType" bl_label = "IFC By Type" - file: StringProperty(name="file", update=updateNode) - ifc_product: EnumProperty(items=get_ifc_products, name="Products", update=update_ifc_products) - ifc_class: EnumProperty(items=get_ifc_classes, name="Class", update=updateNode) - custom_ifc_class: StringProperty(name="Custom Ifc Class", update=updateNode) + ifc_product: EnumProperty(items=get_ifc_products, name="IfcProduct", description="Pick an IfcProduct from drop-down.", update=update_ifc_products) + ifc_class: EnumProperty(items=get_ifc_classes, name="IfcClass", description="Pick an IfcClass from drop-down.", update=updateNode) + custom_ifc_class: StringProperty(name="Custom IfcClass", description="Give the name of your custom IfcClass.", update=updateNode) def sv_init(self, context): - self.inputs.new("SvStringsSocket", "file").prop_name = "file" self.inputs.new("SvStringsSocket", "ifc_product").prop_name = "ifc_product" self.inputs.new("SvStringsSocket", "ifc_class").prop_name = "ifc_class" self.inputs.new("SvStringsSocket", "custom_ifc_class").prop_name = "custom_ifc_class" - self.outputs.new("SvStringsSocket", "entity") + self.outputs.new("SvStringsSocket", "Entity") + self.width = 200 + + def draw_buttons(self, context, layout): + layout.operator("node.sv_ifc_tooltip", text="", icon="QUESTION", emboss=False).tooltip = "Get IFC element(s) in file by type. \nPick an IfcProduct and an IfcClass or give a custom IfcClass." def process(self): - file = self.inputs["file"].sv_get()[0][0] - if file: + self.file = SvIfcStore.get_file() + if self.file: self.ifc_products = get_ifc_products(self, bpy.context) self.ifc_classes = get_ifc_classes(self, bpy.context) - self.sv_input_names = ["file", "ifc_product", "ifc_class", "custom_ifc_class"] + self.sv_input_names = ["ifc_product", "ifc_class", "custom_ifc_class"] super().process() - def process_ifc(self, file, ifc_product, ifc_class, custom_ifc_class): + def process_ifc(self, ifc_product, ifc_class, custom_ifc_class): if custom_ifc_class: - self.outputs["entity"].sv_set([file.by_type(custom_ifc_class)]) + self.outputs["Entity"].sv_set([self.file.by_type(custom_ifc_class)]) + elif ifc_class: + self.outputs["Entity"].sv_set([self.file.by_type(ifc_class)]) else: - self.outputs["entity"].sv_set([file.by_type(ifc_class)]) + self.outputs["Entity"].sv_set([]) def register(): @@ -115,4 +119,4 @@ def register(): def unregister(): - bpy.utils.unregister_class(SvIfcByType) + bpy.utils.unregister_class(SvIfcByType) \ No newline at end of file diff --git a/src/ifcsverchok/nodes/ifc/create_entity.py b/src/ifcsverchok/nodes/ifc/create_entity.py index 43bd9160f0..0889fe1198 100644 --- a/src/ifcsverchok/nodes/ifc/create_entity.py +++ b/src/ifcsverchok/nodes/ifc/create_entity.py @@ -1,111 +1,185 @@ +import bpy +from mathutils import Matrix, Vector +import ifcopenshell +import ifcsverchok.helper +from ifcsverchok.ifcstore import SvIfcStore -# IfcSverchok - IFC Sverchok extension -# Copyright (C) 2020, 2021 Dion Moult -# -# 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 . +# import ifcsverchok.ifc_store +# from bpy.props import StringProperty +# from sverchok.node_tree import SverchCustomTreeNode +# from sverchok.data_structure import updateNode import bpy import ifcopenshell -import ifcsverchok.helper -from bpy.props import StringProperty + +from bpy.props import StringProperty, EnumProperty, IntProperty, BoolProperty from sverchok.node_tree import SverchCustomTreeNode -from sverchok.data_structure import updateNode +from sverchok.data_structure import updateNode, flatten_data, repeat_last_for_length class SvIfcCreateEntity(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.helper.SvIfcCore): bl_idname = "SvIfcCreateEntity" bl_label = "IFC Create Entity" - file: StringProperty(name="file", update=updateNode) - ifc_class: StringProperty(name="ifc_class", update=updateNode) - current_ifc_class: StringProperty(name="current_ifc_class") + node_dict = {} + + is_scene_dependent = True # if True and is_interactive then the node will be updated upon scene changes + + def refresh_node(self, context): + if self.refresh_local: + self.process() + self.refresh_local = False + + refresh_local: BoolProperty(name="Update Node", description="Update Node", update=refresh_node) + + Names: StringProperty(name="Names", default="", update=updateNode) + Descriptions: StringProperty(name="Descriptions", default="", update=updateNode) + IfcClass: StringProperty(name="IfcClass", update=updateNode) + Representations: StringProperty(name="Representations", default="", update=updateNode) + # Locations: FloatVectorProperty(name="Locations", default="", update=updateNode) + Properties: StringProperty(name="properties", update=updateNode) def sv_init(self, context): - self.inputs.new("SvStringsSocket", "file").prop_name = "file" - self.inputs.new("SvStringsSocket", "ifc_class").prop_name = "ifc_class" - self.outputs.new("SvStringsSocket", "file") - self.outputs.new("SvStringsSocket", "entity") + self.inputs.new("SvStringsSocket", "Names").prop_name = "Names" + self.inputs.new("SvStringsSocket", "Descriptions").prop_name = "Descriptions" + self.inputs.new("SvStringsSocket", "IfcClass").prop_name = "IfcClass" + self.inputs.new("SvStringsSocket", "Representations").prop_name = "Representations" + self.inputs.new("SvMatrixSocket", "Locations").is_mandatory=False + self.inputs.new("SvStringsSocket", "Properties").prop_name = "Properties" + self.outputs.new("SvStringsSocket", "Entities") + self.node_dict[hash(self)] = {} + + def draw_buttons(self, context, layout): + layout.operator("node.sv_ifc_tooltip", text="", icon="QUESTION", emboss=False).tooltip = "Create IFC Entity. Takes one or multiple inputs. \nIf 'Representation(s)' is given, that determines number of output entities. Otherwise, 'Names' is used." + + row = layout.row(align=True) + row.prop(self, 'is_interactive', icon='SCENE_DATA', icon_only=True) + row.prop(self, 'refresh_local', icon='FILE_REFRESH') def process(self): - self.sv_input_names = ["file", "ifc_class"] - ifc_class = self.inputs["ifc_class"].sv_get()[0][0] - if ifc_class: - file = self.inputs["file"].sv_get()[0][0] - if file: - schema_name = file.wrapped_data.schema + + self.names = flatten_data(self.inputs["Names"].sv_get(), target_level=1) + self.descriptions = flatten_data(self.inputs["Descriptions"].sv_get(), target_level=1) + self.ifc_class = flatten_data(self.inputs["IfcClass"].sv_get(), target_level=1)[0] + self.representations = flatten_data(self.inputs["Representations"].sv_get(), target_level=1) + self.locations = flatten_data(self.inputs["Locations"].sv_get(default=[]), target_level=1) + self.properties = self.inputs["Properties"].sv_get() + + + self.sv_input_names = [i.name for i in self.inputs] + + if hash(self) not in self.node_dict: + self.node_dict[hash(self)] = {} #happens if node is already on canvas when blender loads + if not self.node_dict[hash(self)]: + self.node_dict[hash(self)].update(dict.fromkeys(self.sv_input_names, 0)) + if not self.inputs["IfcClass"].sv_get()[0][0]: + raise Exception('Mandatory input "IfcClass" is missing.') + + edit = False + for i in range(len(self.inputs)): + input = self.inputs[self.sv_input_names[i]].sv_get(deepcopy=True, default =[]) + if isinstance(self.node_dict[hash(self)][self.inputs[i].name], list) and input != self.node_dict[hash(self)][self.inputs[i].name]: + edit = True + self.node_dict[hash(self)][self.inputs[i].name] = input.copy() + + if self.refresh_local: + edit = True + + self.file = SvIfcStore.get_file() + if self.representations[0]: + try: + self.representations = [self.file.by_id(step_id) for step_id in self.representations] + except Exception as e: + raise + self.names = self.repeat_input_unique(self.names, len(self.representations)) + self.descriptions = self.repeat_input_unique(self.descriptions, len(self.representations)) + elif not self.representations[0]: + self.descriptions = self.repeat_input_unique(self.descriptions, len(self.names)) + + if self.node_id not in SvIfcStore.id_map: + entities = self.create() + else: + if edit is True: + entities = self.edit() else: - schema_name = "IFC4" - self.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name) - self.entity_schema = self.schema.declaration_by_name(ifc_class) + entities = SvIfcStore.id_map[self.node_id] + + print("Entities: ", entities) - if ifc_class != self.current_ifc_class: - self.generate_inputs(ifc_class) + self.outputs["Entities"].sv_set(entities) + + def create(self, index=None): + entities_ids = [] + iterator = range(len(self.names)) + if index is not None: + iterator = [index] + for i in iterator: try: - for i in range(0, len(self.inputs)): - self.inputs[i].sv_get() - except: - # This occurs when a blender save file is reloaded - self.generate_inputs(ifc_class) + entity = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=self.ifc_class, name=self.names[i], description=self.descriptions[i]) + try: + if self.representations[i]: + ifcopenshell.api.run("geometry.assign_representation", self.file, product=entity, representation=self.representations[i]) + except IndexError: + pass + try: + if isinstance(self.locations[i], Matrix): + ifcopenshell.api.run("geometry.edit_object_placement", self.file, product=entity, matrix=self.locations[i]) + except IndexError: + pass + entities_ids.append(entity.id()) + SvIfcStore.id_map.setdefault(self.node_id, []).append(entity.id()) + except Exception as e: + raise Exception("Something went wrong. Cannot create entity.", e) + return entities_ids - for i in range(0, self.entity_schema.attribute_count()): - self.sv_input_names.append(self.entity_schema.attribute_by_index(i).name()) - super().process() - - def generate_inputs(self, ifc_class): - while len(self.inputs) > 2: - self.inputs.remove(self.inputs[-1]) - for i in range(0, self.entity_schema.attribute_count()): - name = self.entity_schema.attribute_by_index(i).name() - setattr(SvIfcCreateEntity, name, StringProperty(name=name)) - self.inputs.new("SvStringsSocket", name).prop_name = name - self.current_ifc_class = ifc_class - - def process_ifc(self, file, ifc_class, *attributes): - entity = file.create_entity(ifc_class) - for i in range(0, len(entity)): + def edit(self): + entities_ids = [] + id_map_copy = SvIfcStore.id_map[self.node_id].copy() + for i, _ in enumerate(self.names): try: - value = attributes[i] - if isinstance(value, str) and value == "": - value = None - except: - value = None - if value is None and entity.is_a("IfcRoot") and i == 0: - value = ifcopenshell.guid.new() - if value is not None: - value = self.cast_value(self.entity_schema.attribute_by_index(i), value) + step_id = id_map_copy[i] + except IndexError: + id = self.create(index=i) + entities_ids.append(id[0]) + continue + entity = self.file.by_id(step_id) + entity.Name = self.names[i] + entity.Description = self.descriptions[i] + try: - entity[i] = value - except: - raise Exception( - "The IFC class {} requires a valid value for {} - you provided {}".format( - ifc_class, entity.attribute_name(i), value - ) - ) - self.outputs["file"].sv_set([[file]]) - self.outputs["entity"].sv_set([[entity]]) + if self.representations[i] and self.representations[i].is_a('IfcProductDefinitionShape'): + entity.Representation = self.representations[i] + elif self.representations[i]: + ifcopenshell.api.run("geometry.assign_representation", self.file, product=entity, representation=self.representations[i]) + except IndexError: + pass + try: + if isinstance(self.locations[i], Matrix): + ifcopenshell.api.run("geometry.edit_object_placement", self.file, product=entity, matrix=self.locations[i]) + except IndexError: + pass + if entity.is_a() != self.ifc_class: + SvIfcStore.id_map[self.node_id].remove(step_id) + entity = ifcopenshell.util.schema.reassign_class(self.file, entity, self.ifc_class) + SvIfcStore.id_map.setdefault(self.node_id, []).append(entity.id()) + entities_ids.append(entity.id()) - def cast_value(self, attribute, value): - data_type = self.get_attribute_data_type(attribute.type_of_attribute()) - if data_type == "integer": - value = int(value) - return value + if id_map_copy>entities_ids: + SvIfcStore.id_map[self.node_id] = entities_ids + return entities_ids - def get_attribute_data_type(self, data_type): - if hasattr(data_type, "declared_type"): - return self.get_attribute_data_type(data_type.declared_type()) - return data_type + def repeat_input_unique(self, input, count): + input = repeat_last_for_length(input, count, deepcopy=False) + if input[0]: + input = [a if not (s:=sum(j == a for j in input[:i])) else f'{a}-{s+1}' for i, a in enumerate(input)] # add number to duplicates + return input + + def sv_free(self): + try: + del SvIfcStore.id_map[self.node_id] + del self.node_dict[hash(self)] + print('Node was deleted') + except KeyError or AttributeError: + pass def register(): diff --git a/src/ifcsverchok/nodes/ifc/create_file.py b/src/ifcsverchok/nodes/ifc/create_file.py index 1e8b1869ff..4005190af7 100644 --- a/src/ifcsverchok/nodes/ifc/create_file.py +++ b/src/ifcsverchok/nodes/ifc/create_file.py @@ -1,4 +1,3 @@ - # IfcSverchok - IFC Sverchok extension # Copyright (C) 2020, 2021 Dion Moult # diff --git a/src/ifcsverchok/nodes/ifc/create_project.py b/src/ifcsverchok/nodes/ifc/create_project.py new file mode 100644 index 0000000000..e61ac0697a --- /dev/null +++ b/src/ifcsverchok/nodes/ifc/create_project.py @@ -0,0 +1,79 @@ +# IfcSverchok - IFC Sverchok extension +# Copyright (C) 2020, 2021 Dion Moult +# +# 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 . + +import bpy +import ifcopenshell +import ifcsverchok.helper +from bpy.props import StringProperty +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode +# from ifcopenshell import template + +class SvIfcCreateProject(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.helper.SvIfcCore): + bl_idname = "SvIfcCreateProject" + bl_label = "IFC Create Project" + + def sv_init(self, context): + input_socket = self.inputs.new("SvStringsSocket", "file") + input_socket.tooltip = "ifc file to add the project to" + input_socket = self.inputs.new("SvStringsSocket", "project_name") + input_socket.tooltip = "Project name" + self.outputs.new("SvVerticesSocket", "file") + + def draw_buttons(self, context, layout): + op = layout.operator("node.sv_ifc_tooltip", text="", icon="QUESTION", emboss=False).tooltip = "Adds project, unit and context to IFC file" + #op.tooltip = self.tooltip + + def process(self): + #file + file = self.inputs["file"].sv_get()[0][0] + if file: + schema_name = file.wrapped_data.schema + else: + schema_name = "IFC4" + + #project name + project_name = self.inputs["project_name"].sv_get()[0][0] + self.process_ifc(file, project_name) + + + def process_ifc(self, file, project_name): + + # create project + project = ifcopenshell.api.run("root.create_entity", file, ifc_class="IfcProject", name=str(project_name)) + lengthunit = ifcopenshell.api.run("unit.add_si_unit", file, unit_type="LENGTHUNIT", name="METRE") + ifcopenshell.api.run("unit.assign_unit", file, units=[lengthunit]) + model = ifcopenshell.api.run("context.add_context", file, context_type="Model") + context = ifcopenshell.api.run( + "context.add_context", + file, + context_type="Model", + context_identifier="Body", + target_view="MODEL_VIEW", + parent=model, + ) + + self.outputs["file"].sv_set([[file]]) + + +def register(): + bpy.utils.register_class(SvIfcCreateProject) + + +def unregister(): + bpy.utils.unregister_class(SvIfcCreateProject) diff --git a/src/ifcsverchok/nodes/ifc/create_shape.py b/src/ifcsverchok/nodes/ifc/create_shape.py index 1f5cad07b1..220cc2d6e4 100644 --- a/src/ifcsverchok/nodes/ifc/create_shape.py +++ b/src/ifcsverchok/nodes/ifc/create_shape.py @@ -21,65 +21,100 @@ import bpy import logging import ifcopenshell import ifcsverchok.helper +from ifcsverchok.ifcstore import SvIfcStore import blenderbim.bim.import_ifc -from bpy.props import StringProperty +from bpy.props import StringProperty, PointerProperty, BoolProperty from sverchok.node_tree import SverchCustomTreeNode -from sverchok.data_structure import updateNode +from sverchok.data_structure import updateNode, flatten_data -class SvIfcCreateShapeRefresh(bpy.types.Operator): - bl_idname = "node.sv_ifc_create_shape_refresh" - bl_label = "IFC Create Shape Refresh" - bl_options = {"UNDO"} +# class SvIfcCreateShapeRefresh(bpy.types.Operator): +# bl_idname = "node.sv_ifc_create_shape_refresh" +# bl_label = "IFC Create Shape Refresh" +# bl_options = {"UNDO"} - tree_name: StringProperty(default="") - node_name: StringProperty(default="") - def execute(self, context): - node = bpy.data.node_groups[self.tree_name].nodes[self.node_name] - node.process() - return {"FINISHED"} +# tree_name: StringProperty(default="") +# node_name: StringProperty(default="") + +# def execute(self, context): +# node = bpy.data.node_groups[self.tree_name].nodes[self.node_name] +# node.process() +# return {"FINISHED"} class SvIfcCreateShape(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.helper.SvIfcCore): + """ + Triggers: Ifc create shape by entity + Tooltip: Create Blender shape by Ifc Entity + """ + is_scene_dependent = True + is_interactive = False + node_dict = {} + def refresh_node(self, context): + if self.refresh_local: + self.process() + self.refresh_local = False + + refresh_local: BoolProperty(name="Create shape(s)", description="Update Node", update=refresh_node) + bl_idname = "SvIfcCreateShape" - bl_label = "IFC Create Shape" - file: StringProperty(name="file", update=updateNode) - entity: StringProperty(name="entity", update=updateNode) + bl_label = "IFC Create Blender Shape" + entity: StringProperty(name="Entities", update=updateNode) + def sv_init(self, context): - self.inputs.new("SvStringsSocket", "file").prop_name = "file" - self.inputs.new("SvStringsSocket", "entity").prop_name = "entity" + self.inputs.new("SvStringsSocket", "Entities").prop_name = "entity" + self.outputs.new('SvStringsSocket', "Object(s)") def draw_buttons(self, context, layout): - self.wrapper_tracked_ui_draw_op(layout, "node.sv_ifc_create_shape_refresh", icon="FILE_REFRESH", text="Refresh") + row = layout.row(align=True) + row.operator("node.sv_ifc_tooltip", text="", icon="QUESTION", emboss=False).tooltip = "Create Blender shape from IfcEntity ID. Takes one or multiple IfcEntity IDs." + row.prop(self, 'refresh_local', icon='FILE_REFRESH') def process(self): - self.sv_input_names = ["file", "entity"] - super().process() - - def process_ifc(self, file, entity): - try: - if not entity.is_a("IfcProduct"): - return - logger = logging.getLogger("ImportIFC") - self.ifc_import_settings = blenderbim.bim.import_ifc.IfcImportSettings.factory(bpy.context, "", logger) - settings = ifcopenshell.geom.settings() - shape = ifcopenshell.geom.create_shape(settings, entity) - ifc_importer = blenderbim.bim.import_ifc.IfcImporter(self.ifc_import_settings) - ifc_importer.file = file - mesh = ifc_importer.create_mesh(entity, shape) - obj = bpy.data.objects.new("IFC Element", mesh) - bpy.context.scene.collection.objects.link(obj) - except: - print("Entity could not be converted into a shape", entity) + self.entities = flatten_data(self.inputs["Entities"].sv_get(), target_level = 1) + if not self.entities[0]: + return + + if self.refresh_local or hash(self) not in self.node_dict: + self.file = SvIfcStore.get_file() + try: + self.entities = [self.file.by_id(step_id) for step_id in self.entities] + except Exception as e: + raise + blender_objects = self.create() + self.node_dict[hash(self)] = blender_objects + else: + blender_objects = self.node_dict[hash(self)] + + self.outputs["Object(s)"].sv_set(blender_objects) + def create(self): + blender_objects = [] + for entity in self.entities: + try: + if not entity.is_a("IfcProduct"): + return + logger = logging.getLogger("ImportIFC") + self.ifc_import_settings = blenderbim.bim.import_ifc.IfcImportSettings.factory(bpy.context, "", logger) + settings = ifcopenshell.geom.settings() + shape = ifcopenshell.geom.create_shape(settings, entity) + ifc_importer = blenderbim.bim.import_ifc.IfcImporter(self.ifc_import_settings) + ifc_importer.file = self.file + mesh = ifc_importer.create_mesh(entity, shape) + obj = bpy.data.objects.new("IFC Element", mesh) + blender_objects.append(obj) + bpy.context.scene.collection.objects.link(obj) + except: + raise Exception("Entity could not be converted into a shape. Entity: {}".format(entity)) + return blender_objects def register(): - bpy.utils.register_class(SvIfcCreateShapeRefresh) + # bpy.utils.register_class(SvIfcCreateShapeRefresh) bpy.utils.register_class(SvIfcCreateShape) def unregister(): bpy.utils.unregister_class(SvIfcCreateShape) - bpy.utils.unregister_class(SvIfcCreateShapeRefresh) + # bpy.utils.unregister_class(SvIfcCreateShapeRefresh) diff --git a/src/ifcsverchok/nodes/ifc/pick_ifc_class.py b/src/ifcsverchok/nodes/ifc/pick_ifc_class.py new file mode 100644 index 0000000000..3b292d86c3 --- /dev/null +++ b/src/ifcsverchok/nodes/ifc/pick_ifc_class.py @@ -0,0 +1,108 @@ + +# IfcSverchok - IFC Sverchok extension +# Copyright (C) 2020, 2021 Dion Moult +# +# 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 . + +import bpy +import ifcopenshell +import ifcsverchok.helper +from bpy.props import StringProperty, EnumProperty +from ifcsverchok.ifcstore import SvIfcStore +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode + +def get_ifc_products(self, context): + ifc_products = getattr(self, "ifc_products", []) + file = SvIfcStore.get_file() + if not file: + return [] + if ifc_products and file: + return ifc_products + ifc_products = [] + ifc_products.extend( + [ + (e, e, "") + for e in [ + "IfcElement", + "IfcElementType", + "IfcSpatialElement", + "IfcGroup", + "IfcStructuralItem", + "IfcContext", + "IfcAnnotation", + ] + ] + ) + if file.schema == "IFC2X3": + ifc_products[2] = ("IfcSpatialStructureElement", "IfcSpatialStructureElement", "") + return ifc_products + + +def update_ifc_products(self, context): + if hasattr(self, "ifc_classes"): + self.ifc_classes.clear() + + +def get_ifc_classes(self, context): + ifc_classes = getattr(self, "ifc_classes", []) + if ifc_classes: + return self.ifc_classes + file = SvIfcStore.get_file() + if not file: + return [] + schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(file.schema) + declaration = schema.declaration_by_name(self.ifc_product) + + def get_classes(declaration): + results = [] + if not declaration.is_abstract(): + results.append(declaration.name()) + for subtype in declaration.subtypes(): + results.extend(get_classes(subtype)) + return results + + classes = get_classes(declaration) + ifc_classes.extend([(c, c, "") for c in sorted(classes)]) + return ifc_classes + + +class SvIfcPickIfcClass(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.helper.SvIfcCore): + bl_idname = "SvIfcPickIfcClass" + bl_label = "IFC Class Picker" + ifc_product: EnumProperty(items=get_ifc_products, name="IfcProduct", description="Pick an IfcProduct from drop-down.", update=update_ifc_products) + ifc_class: EnumProperty(items=get_ifc_classes, name="IfcClass", description="Pick an IfcClass from drop-down.", update=updateNode) + + def sv_init(self, context): + self.inputs.new("SvStringsSocket", "ifc_product").prop_name = "ifc_product" + self.inputs.new("SvStringsSocket", "ifc_class").prop_name = "ifc_class" + self.outputs.new("SvStringsSocket", "IfcClass") + self.width = 200 + + def draw_buttons(self, context, layout): + layout.operator("node.sv_ifc_tooltip", text="", icon="QUESTION", emboss=False).tooltip = "Ifc Class Picker" + + def process(self): + ifc_class = self.inputs["ifc_class"].sv_get()[0][0] + self.outputs["IfcClass"].sv_set([ifc_class]) + + +def register(): + bpy.utils.register_class(SvIfcPickIfcClass) + + +def unregister(): + bpy.utils.unregister_class(SvIfcPickIfcClass) diff --git a/src/ifcsverchok/nodes/ifc/quick_project_setup.py b/src/ifcsverchok/nodes/ifc/quick_project_setup.py new file mode 100644 index 0000000000..fe3bb07450 --- /dev/null +++ b/src/ifcsverchok/nodes/ifc/quick_project_setup.py @@ -0,0 +1,93 @@ +# IfcSverchok - IFC Sverchok extension +# Copyright (C) 2020, 2021 Dion Moult +# +# 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 . + +from email.mime import application +import bpy +import ifcopenshell +import ifcsverchok.helper +from bpy.props import StringProperty +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode +from ifcopenshell import template + +class SvIfcQuickProjectSetup(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.helper.SvIfcCore): + bl_idname = "SvIfcQuickProjectSetup" + bl_label = "IFC Quick Project Setup" + schema_identifier: StringProperty(name="schema_identifier", update=updateNode, default="IFC4") + timestring: StringProperty(name="timestring", update=updateNode) + application: StringProperty(name="application", update=updateNode) + application_version: StringProperty(name="application_version", update=updateNode) + timestamp: StringProperty(name="timestamp", update=updateNode) + + + def sv_init(self, context): + input_socket = self.inputs.new("SvStringsSocket", "filename") + input_socket.tooltip = "Ifc file name" + input_socket = self.inputs.new("SvStringsSocket", "timestring") + input_socket.tooltip = "Timestring, default = current time" + input_socket = self.inputs.new("SvStringsSocket", "organization") + input_socket.tooltip = "Organization" + input_socket = self.inputs.new("SvStringsSocket", "creator") + input_socket.tooltip = "creator" + input_socket = self.inputs.new("SvStringsSocket", "schema_identifier") + input_socket.tooltip = "Schema, default = 'IFC4'" + input_socket = self.inputs.new("SvStringsSocket", "application_version") + input_socket.tooltip = "Application version" + input_socket = self.inputs.new("SvStringsSocket", "timestamp") + input_socket.tooltip = "Timestamp, default = current time" + input_socket = self.inputs.new("SvStringsSocket", "application") + input_socket.tooltip = "Application, default = 'IfcOpenShell'" + input_socket = self.inputs.new("SvStringsSocket", "project_globalid") + input_socket.tooltip = "Project GlobalId" + input_socket = self.inputs.new("SvStringsSocket", "project_name") + input_socket.tooltip = "Project name" + self.outputs.new("SvVerticesSocket", "file") + + def draw_buttons(self, context, layout): + op = layout.operator("node.sv_ifc_tooltip", text="", icon="QUESTION", emboss=False).tooltip = "Quick Project Setup: creates Ifc file and sets up a basic project" + #op.tooltip = self.tooltip + + def process(self): + self.sv_input_names = [i.name for i in self.inputs] + super().process() + + def process_ifc(self, *setting_values): + settings = dict(zip(self.sv_input_names, setting_values)) + settings = {k: v for k, v in settings.items() if v != ""} + file = template.create( + filename=settings['filename'], + timestring=settings['timestring'], + organization=settings['organization'], + creator=settings['creator'], + schema_identifier=settings['schema_identifier'], + application_version=settings['application_version'], + timestamp=settings['timestamp'], + application=settings['application'], + project_globalid=settings['project_globalid'], + project_name=settings['project_name'], + ) + + self.outputs["file"].sv_set([[file]]) + + +def register(): + bpy.utils.register_class(SvIfcQuickProjectSetup) + + +def unregister(): + bpy.utils.unregister_class(SvIfcQuickProjectSetup) diff --git a/src/ifcsverchok/nodes/ifc/sverchok_to_ifc.py b/src/ifcsverchok/nodes/ifc/sverchok_to_ifc.py new file mode 100644 index 0000000000..925ce9ee41 --- /dev/null +++ b/src/ifcsverchok/nodes/ifc/sverchok_to_ifc.py @@ -0,0 +1,197 @@ +# IfcSverchok - IFC Sverchok extension +# Copyright (C) 2022 Dion Moult +# +# 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 . + +from copy import deepcopy +import bpy +import ifcopenshell +import ifcsverchok.helper +import ifcopenshell.api +from ifcsverchok.ifcstore import SvIfcStore +import blenderbim.tool as tool +import blenderbim.core.geometry as core +from bpy.props import StringProperty, EnumProperty, IntProperty, FloatVectorProperty +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode + + +class SvIfcSverchokToIfcRepr(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.helper.SvIfcCore): + """ + Triggers: Sv to Ifc Repr + Tooltip: Sverchok geometry to Ifc Shape Representation + """ + bl_idname = "SvIfcSverchokToIfcRepr" + bl_label = "IFC Sverchok to IFC Repr" + node_dict = {} + + n_id: StringProperty() + context_types = [ + ('Model', 'Model', 'Context type: Model', 0), + ('Plan', 'Plan', 'Context type: Plan', 1), + ] + + context_identifiers = [ + ('Body', 'Body', 'Context identifier: Body', 0), + ('Annotation', 'Annotation', 'Context identifier: Annotation', 1), + ('Box', 'Box', 'Context identifier: Box', 2), + ('Axis', 'Axis', 'Context identifier: Axis', 3), + ] + target_views = [ + ('MODEL_VIEW', 'MODEL_VIEW', 'Target View: MODEL_VIEW', 0), + ('PLAN_VIEW', 'PLAN_VIEW', 'Target View: PLAN_VIEW', 1), + ('GRAPH_VIEW', 'GRAPH_VIEW', 'Target View: GRAPH_VIEW', 2), + ('SKETCH_VIEW', 'SKETCH_VIEW', 'Target View: SKETCH_VIEW', 3), + ] + + context_type: EnumProperty(name="Context Type", description="Default: Model", default="Model",items=context_types,update=updateNode) + context_identifier: EnumProperty(name="Context Identifier", description="Default: Body", default="Body", items=context_identifiers, update=updateNode) + target_view: EnumProperty(name="Target View", description="Default: MODEL VIEW", default="MODEL_VIEW",items=target_views, update=updateNode) + + def sv_init(self, context): + self.inputs.new("SvStringsSocket", "context_type").prop_name = "context_type" + self.inputs.new("SvStringsSocket", "context_identifier").prop_name = "context_identifier" + self.inputs.new("SvStringsSocket", "target_view").prop_name = "target_view" + self.inputs.new("SvVerticesSocket", "Vertices") + self.inputs.new("SvStringsSocket", "Edges") + self.inputs.new("SvStringsSocket", "Faces") + self.outputs.new("SvVerticesSocket", "Representation(s)") + self.width = 210 + self.node_dict[hash(self)] = {} + + def draw_buttons(self, context, layout): + op = layout.operator( + "node.sv_ifc_tooltip", text="", icon="QUESTION", emboss=False + ).tooltip = "Sverchok geometry to Ifc Shape Representation. \nTakes one or multiple geometries." + + def process(self): + if not any(socket.is_linked for socket in self.inputs): + return + + self.file = SvIfcStore.get_file() + self.sv_input_names = [i.name for i in self.inputs] + + if hash(self) not in self.node_dict: + self.node_dict[hash(self)] = {} #happens if node is already on canvas when blender loads + if not self.node_dict[hash(self)]: + self.node_dict[hash(self)].update(dict.fromkeys(self.sv_input_names, 0)) + + edit = False + for i in range(len(self.inputs)): + input = self.inputs[i].sv_get(deepcopy=False) + if isinstance(self.node_dict[hash(self)][self.inputs[i].name], list) and input != self.node_dict[hash(self)][self.inputs[i].name]: + edit = True + self.node_dict[hash(self)][self.inputs[i].name] = input + + self.vertices = self.inputs["Vertices"].sv_get(deepcopy=False) + self.edges = self.inputs["Edges"].sv_get(deepcopy=False) + self.faces = self.inputs["Faces"].sv_get(deepcopy=False) + geo_data = list(zip(self.vertices, self.edges, self.faces)) + + if self.node_id not in SvIfcStore.id_map: + + representations = self.create(geo_data) + else: + if edit is True: + self.edit() + representations = self.create(geo_data) + else: + # representations = self.get_existing_element() + representations = SvIfcStore.id_map[self.node_id]["Representations"] + + self.outputs["Representation(s)"].sv_set(representations) + + def create(self, geo_data): + representations_ids = [] + self.context = self.get_context() + for item in geo_data: + representation = ifcopenshell.api.run( + "geometry.add_sverchok_representation", + self.file, + should_run_listeners=False, + context=self.context, + vertices=[item[0]], + edges=[item[1]], + faces=[item[2]], + ) + if not representation: + raise Exception("Couldn't create representation. Possibly wrong context.") + representations_ids.append(representation.id()) + SvIfcStore.id_map.setdefault(self.node_id, {}).setdefault("Representations", []).append(representation.id()) + return representations_ids + + def edit(self): + # results = self.get_existing_element() + if "Representations" not in SvIfcStore.id_map[self.node_id]: + return + for step_id in SvIfcStore.id_map[self.node_id]["Representations"]: + ifcopenshell.api.run( + "geometry.remove_representation", self.file, representation=self.file.by_id(step_id) + ) + del SvIfcStore.id_map[self.node_id]["Representations"] + return + + def get_context(self): + context = ifcopenshell.util.representation.get_context( + self.file, self.context_type, self.context_identifier, self.target_view + ) + if not context: + parent = ifcopenshell.util.representation.get_context(self.file, self.context_type) + if not parent: + parent = ifcopenshell.api.run("context.add_context", self.file, context_type=self.context_type) + context = ifcopenshell.api.run( + "context.add_context", + self.file, + context_type=self.context_type, + context_identifier=self.context_identifier, + target_view=self.target_view, + parent=parent, + ) + SvIfcStore.id_map.setdefault(self.node_id, {}).setdefault("Contexts", []).append(context.id()) + return context + + def sv_free(self): + try: + self.file = SvIfcStore.get_file() + if "Representations" in SvIfcStore.id_map[self.node_id]: + for step_id in SvIfcStore.id_map[self.node_id]["Representations"]: + ifcopenshell.api.run("geometry.remove_representation", self.file, representation=self.file.by_id(step_id)) + + if "Contexts" in SvIfcStore.id_map[self.node_id]: + for context_id in SvIfcStore.id_map[self.node_id]["Contexts"]: + context = self.file.by_id(context_id) + if not self.file.get_inverse(context): + if self.file.by_id(context_id).ParentContext: + parent = self.file.by_id(context_id).ParentContext + ifcopenshell.api.run("context.remove_context", self.file, context=context) + if parent: + if not self.file.get_inverse(parent): + ifcopenshell.api.run("context.remove_context", self.file, context=parent) + print("Removed context with step ID: ", context_id) + SvIfcStore.id_map[self.node_id]["Contexts"].remove(context_id) + del SvIfcStore.id_map[self.node_id] + del self.node_dict[hash(self)] + print('Node was deleted') + except KeyError or AttributeError: + pass + + +def register(): + bpy.utils.register_class(SvIfcSverchokToIfcRepr) + + +def unregister(): + bpy.utils.unregister_class(SvIfcSverchokToIfcRepr) \ No newline at end of file diff --git a/src/ifcsverchok/nodes/ifc/write_file.py b/src/ifcsverchok/nodes/ifc/write_file.py index 59df9fc84f..554ae811be 100644 --- a/src/ifcsverchok/nodes/ifc/write_file.py +++ b/src/ifcsverchok/nodes/ifc/write_file.py @@ -17,30 +17,97 @@ # You should have received a copy of the GNU General Public License # along with IfcSverchok. If not, see . +from os.path import abspath, splitext import bpy import ifcopenshell import ifcsverchok.helper -from bpy.props import StringProperty +from ifcsverchok.ifcstore import SvIfcStore +from bpy.props import StringProperty, BoolProperty from sverchok.node_tree import SverchCustomTreeNode -from sverchok.data_structure import updateNode +from sverchok.data_structure import updateNode, flatten_data class SvIfcWriteFile(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.helper.SvIfcCore): + """ + Triggers: Ifc write to file + Tooltip: Write active Sverchok Ifc file to path + """ + def refresh_node_local(self, context): + if self.refresh_local: + self.process() + self.refresh_local = False + + # out = "" + refresh_local: BoolProperty(name="Write", description="Write to file", update=refresh_node_local) + bl_idname = "SvIfcWriteFile" bl_label = "IFC Write File" - file: StringProperty(name="file", update=updateNode) - path: StringProperty(name="path", update=updateNode) + path: StringProperty(name="path", description="File path to write to. Can be relative.", update=updateNode) def sv_init(self, context): - self.inputs.new("SvStringsSocket", "file").prop_name = "file" self.inputs.new("SvStringsSocket", "path").prop_name = "path" + self.outputs.new("SvStringsSocket", "output") + + def draw_buttons(self, context, layout): + row = layout.row(align=True) + row.operator("node.sv_ifc_tooltip", text="", icon="QUESTION", emboss=False).tooltip = "Writes active Ifc file to path.\n It will overwrite an existing file.\n N.B.! It's recommended to create a fresh IFC File using the 're-run all nodes' button in IfcSverchok panel before saving." + row.prop(self, 'refresh_local', icon='FILE_REFRESH') def process(self): - self.sv_input_names = ["file", "path"] - super().process() + path = flatten_data(self.inputs["path"].sv_get(), target_level = 1)[0] + if not path: + return + path = abspath(path) + file = SvIfcStore.get_file() + _, ext = splitext(path) + if not ext: + raise Exception("Bad path. Provide a path to a file.") + if self.refresh_local and ext: + self.ensure_hirarchy(file) + file.write(path) + self.outputs["output"].sv_set(f"File written successfully to: {path}.") + + def ensure_hirarchy(self, file): + elements_in_buildings = [] + if not 0 <= 0 < len(file.by_type("IfcBuilding")): + 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.extend(elements) - def process_ifc(self, file, path): - file.write(path) + 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]) + + elements_in_buildings_after = [] + for building in file.by_type("IfcBuilding"): + elements = ifcopenshell.util.element.get_decomposition(building) + elements_in_buildings_after.extend(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 not 0 <= 0 < len(file.by_type("IfcSite")): + 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 register():