mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-16 10:34:31 +00:00
245 lines
7.7 KiB
Python
245 lines
7.7 KiB
Python
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
|
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
|
#
|
|
# 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 <http://www.gnu.org/licenses/>.
|
|
|
|
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")]
|