# BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2022 Dion Moult # # This file is part of BlenderBIM Add-on. # # BlenderBIM Add-on 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. # # BlenderBIM Add-on 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 BlenderBIM Add-on. If not, see . import bpy import ifcopenshell import ifcopenshell.util.schema from ifcopenshell.util.doc import get_entity_doc import blenderbim.tool as tool def refresh(): SystemData.is_loaded = False ZonesData.is_loaded = False ActiveObjectZonesData.is_loaded = False ObjectSystemData.is_loaded = False PortData.is_loaded = False SystemDecorationData.is_loaded = False class SystemData: data = {} is_loaded = False @classmethod def load(cls): cls.data = { "system_class": cls.system_class(), "total_systems": cls.total_systems(), } cls.is_loaded = True @classmethod def system_class(cls): declaration = tool.Ifc.schema().declaration_by_name("IfcSystem") declarations = ifcopenshell.util.schema.get_subtypes(declaration) version = tool.Ifc.get_schema() # We're only interested in systems for services. Not sure why IFC groups these together. return [ (c, c, get_entity_doc(version, c).get("description", "")) for c in sorted([d.name() for d in declarations]) if c not in ("IfcStructuralAnalysisModel") ] @classmethod def total_systems(cls): return len(tool.System.get_systems()) class ObjectSystemData: data = {} is_loaded = False @classmethod def load(cls): cls.data = { "systems": cls.systems(), # AFTER SYSTEMS "connected_elements": cls.connected_elements(), } cls.is_loaded = True @classmethod def systems(cls): results = [] cls.element = tool.Ifc.get_entity(bpy.context.active_object) if not cls.element: return results for system in ifcopenshell.util.system.get_element_systems(cls.element): results.append({"id": system.id(), "name": system.Name or "Unnamed", "ifc_class": system.is_a()}) return results @classmethod def connected_elements(cls): if not cls.element: return set() return tool.System.get_connected_elements(cls.element) class PortData: data = {} is_loaded = False @classmethod def load(cls): element = tool.Ifc.get_entity(bpy.context.active_object) cls.element = element is_port = cls.is_port() cls.data = { "total_ports": cls.total_ports(), "located_ports_data": cls.located_ports_data(), "is_port": is_port, "port_connected_object": cls.port_connected_object() if is_port else None, "port_relating_object": cls.port_relating_object() if is_port else None, } # AFTER located_ports_data cls.data["selected_objects_flow_direction"] = cls.selected_objects_flow_direction() if not is_port else None cls.is_loaded = True @classmethod def total_ports(cls): return len(ifcopenshell.util.system.get_ports(cls.element)) @classmethod def is_port(cls): return cls.element and cls.element.is_a("IfcDistributionPort") @classmethod def port_relating_object(cls): return tool.Ifc.get_object(tool.System.get_port_relating_element(cls.element)) @classmethod def port_connected_object(cls): connected_port = tool.System.get_connected_port(cls.element) if not connected_port: return connected_element = tool.System.get_port_relating_element(connected_port) return tool.Ifc.get_object(connected_element) @classmethod def located_ports_data(cls): ports = ifcopenshell.util.system.get_ports(cls.element) data = [] for port in ports: port_obj = tool.Ifc.get_object(port) connected_port = tool.System.get_connected_port(port) if connected_port: connected_obj = tool.Ifc.get_object(tool.System.get_port_relating_element(connected_port)) else: connected_obj = None data.append((port, port_obj, connected_obj)) return data @classmethod def selected_objects_flow_direction(cls): for port, port_obj, connected_obj in cls.data["located_ports_data"]: if connected_obj in bpy.context.selected_objects: return port.FlowDirection class SystemDecorationData: data = {} elements_ports_positions = {} is_loaded = False @classmethod def load(cls): cls.data = { "decorated_elements": cls.decorated_elements(), } cls.is_loaded = True cls.elements_ports_positions = {} @classmethod def get_element_ports_data(cls, element): """returns element's port data, caches the data until UI update Port data includes: - local port position in SI units - port flow direction """ if element not in cls.elements_ports_positions: ports = tool.System.get_ports(element) si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) ports_data = [] for port in ports: position = tool.Model.get_element_matrix(port, keep_local=True).translation * si_conversion port_data = { "position": position, "flow_direction": port.FlowDirection, } ports_data.append(port_data) cls.elements_ports_positions[element] = ports_data return cls.elements_ports_positions[element] @classmethod def decorated_elements(cls): if not ObjectSystemData.is_loaded: ObjectSystemData.load() # Priority: # 1. currently selected systems # 2. active system # 3. if previous steps didn't worked - decorate connected elements decorated_elements = set() if ObjectSystemData.data["systems"]: for system in ObjectSystemData.data["systems"]: system = tool.Ifc.get().by_id(system["id"]) decorated_elements.update(ifcopenshell.util.system.get_system_elements(system)) elif active_system := tool.System.get_active_system(): decorated_elements = set(ifcopenshell.util.system.get_system_elements(active_system)) if not decorated_elements: decorated_elements.update(ObjectSystemData.data["connected_elements"]) return decorated_elements class ZonesData: data = {} is_loaded = False @classmethod def load(cls): cls.data = {"total_zones": cls.total_zones()} cls.is_loaded = True @classmethod def total_zones(cls): return len(tool.Ifc.get().by_type("IfcZone")) class ActiveObjectZonesData: data = {} is_loaded = False @classmethod def load(cls): cls.data = {"zones": cls.zones()} cls.is_loaded = True @classmethod def zones(cls): systems = ifcopenshell.util.system.get_element_systems(tool.Ifc.get_entity(bpy.context.active_object)) return [s.Name or "Unnamed" for s in systems if s.is_a("IfcZone")]