From ecc82a52f5b5884e86e4d0749e9d824e39416b86 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Wed, 4 Mar 2026 17:57:34 +0500 Subject: [PATCH] ExtractPropertiesToSQLite - add typing for created columns --- .../bonsai/bim/module/project/operator.py | 21 ++++-- .../recipes/ExtractPropertiesToSQLite.py | 74 +++++++++++++------ 2 files changed, 65 insertions(+), 30 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/project/operator.py b/src/bonsai/bonsai/bim/module/project/operator.py index 2bd731a93d..d5dc0f0a5b 100644 --- a/src/bonsai/bonsai/bim/module/project/operator.py +++ b/src/bonsai/bonsai/bim/module/project/operator.py @@ -2287,6 +2287,11 @@ class QueryLinkedElement(bpy.types.Operator): region_2d_to_origin_3d, region_2d_to_vector_3d, ) + from ifcpatch.recipes.ExtractPropertiesToSQLite import ( + ElementRow, + PropertyRow, + RelationshipRow, + ) LinksData.linked_data = {} props = tool.Project.get_project_props() @@ -2352,7 +2357,7 @@ class QueryLinkedElement(bpy.types.Operator): self.c = self.db.cursor() self.c.execute(f"SELECT * FROM elements WHERE global_id = '{guid}' LIMIT 1") - element = self.c.fetchone() + element = ElementRow(*self.c.fetchone()) attributes: dict[str, Any] = {} for i, attr in enumerate(["GlobalId", "IFC Class", "Predefined Type", "Name", "Description"]): @@ -2360,14 +2365,14 @@ class QueryLinkedElement(bpy.types.Operator): attributes[attr] = element[i + 1] self.c.execute("SELECT * FROM properties WHERE element_id = ?", (element[0],)) - rows = self.c.fetchall() + rows = [PropertyRow(*row) for row in self.c.fetchall()] - properties = {} + properties: defaultdict[str, dict[str, str]] = defaultdict(dict) for row in rows: - properties.setdefault(row[1], {})[row[2]] = row[3] + properties[row.pset_name][row.name] = row.value self.c.execute("SELECT * FROM relationships WHERE from_id = ?", (element[0],)) - relationships = self.c.fetchall() + relationships = [RelationshipRow(*row) for row in self.c.fetchall()] relating_type_id = None @@ -2375,12 +2380,12 @@ class QueryLinkedElement(bpy.types.Operator): if relationship[1] == "IfcRelDefinesByType": relating_type_id = relationship[2] - type_properties = {} + type_properties: defaultdict[str, dict[str, str]] = defaultdict(dict) if relating_type_id is not None: self.c.execute("SELECT * FROM properties WHERE element_id = ?", (relating_type_id,)) - rows = self.c.fetchall() + rows = [PropertyRow(*row) for row in self.c.fetchall()] for row in rows: - type_properties.setdefault(row[1], {})[row[2]] = row[3] + type_properties[row.pset_name][row.name] = row.value LinksData.linked_data = { "attributes": attributes, diff --git a/src/ifcpatch/ifcpatch/recipes/ExtractPropertiesToSQLite.py b/src/ifcpatch/ifcpatch/recipes/ExtractPropertiesToSQLite.py index c7ca60610b..c91467036f 100644 --- a/src/ifcpatch/ifcpatch/recipes/ExtractPropertiesToSQLite.py +++ b/src/ifcpatch/ifcpatch/recipes/ExtractPropertiesToSQLite.py @@ -19,17 +19,40 @@ import logging import tempfile +from typing import NamedTuple import ifcopenshell.util.element import ifcpatch try: - import sqlite3 + import sqlite3 # noqa: F401 except: print("No SQLite support") +class ElementRow(NamedTuple): + element_id: int + guid: str + class_: str + predefined_type: str | None + name: str | None + description: str | None + + +class PropertyRow(NamedTuple): + element_id: int + pset_name: str + name: str + value: str + + +class RelationshipRow(NamedTuple): + element_id: int + rel_ifc_class: str + to_id: int + + class Patcher(ifcpatch.BasePatcher): def __init__( self, @@ -94,20 +117,21 @@ class Patcher(ifcpatch.BasePatcher): elements = self.file.by_type("IfcObjectDefinition") - rows = [] - properties = [] - relationships = [] + rows: list[ElementRow] = [] + properties: list[PropertyRow] = [] + relationships: list[RelationshipRow] = [] id_map = {e.id(): i for i, e in enumerate(elements)} + for i, element in enumerate(elements): rows.append( - [ + ElementRow( i, element[0], # IfcRoot.GlobalId element.is_a(), ifcopenshell.util.element.get_predefined_type(element), element[2], # IfcRoot.Name element[3], # IfcRoot.Description - ] + ) ) psets = ifcopenshell.util.element.get_psets(element, should_inherit=False) for pset_name, pset_data in psets.items(): @@ -118,49 +142,55 @@ class Patcher(ifcpatch.BasePatcher): value = "True" if value else "False" elif not isinstance(value, str): value = str(value) - properties.append([i, pset_name, prop_name, value]) + properties.append(PropertyRow(i, pset_name, prop_name, value)) material = ifcopenshell.util.element.get_material(element, should_skip_usage=True) if material: name = getattr(material, "Name", getattr(material, "LayerSetName", None)) or "Unnamed" - properties.append([i, "IFC Material", "Name", name]) - properties.append([i, "IFC Material", "Class", material.is_a()]) + properties.append(PropertyRow(i, "IFC Material", "Name", name)) + properties.append(PropertyRow(i, "IFC Material", "Class", material.is_a())) if material.is_a("IfcMaterial"): materials = [] elif material.is_a("IfcMaterialLayerSet"): for idx, item in enumerate(material.MaterialLayers or []): material = item.Material - properties.append([i, "IFC Material", f"Layer {idx + 1} Name", getattr(item, "Name", None)]) - properties.append([i, "IFC Material", f"Layer {idx + 1} Material", material.Name]) + properties.append( + PropertyRow(i, "IFC Material", f"Layer {idx + 1} Name", getattr(item, "Name", None)) + ) + properties.append(PropertyRow(i, "IFC Material", f"Layer {idx + 1} Material", material.Name)) if category := getattr(material, "Category", None): - properties.append([i, "IFC Material", f"Layer {idx + 1} Category", category]) + properties.append(PropertyRow(i, "IFC Material", f"Layer {idx + 1} Category", category)) elif material.is_a("IfcMaterialProfileSet"): for idx, item in enumerate(material.MaterialProfiles or []): material = item.Material - properties.append([i, "IFC Material", f"Profile {idx + 1} Name", item.Name]) - properties.append([i, "IFC Material", f"Profile {idx + 1} Material", material.Name]) + properties.append(PropertyRow(i, "IFC Material", f"Profile {idx + 1} Name", item.Name)) + properties.append(PropertyRow(i, "IFC Material", f"Profile {idx + 1} Material", material.Name)) if category := getattr(material, "Category", None): - properties.append([i, "IFC Material", f"Profile {idx + 1} Category", category]) + properties.append(PropertyRow(i, "IFC Material", f"Profile {idx + 1} Category", category)) elif material.is_a("IfcMaterialConstituentSet"): for idx, item in enumerate(material.MaterialConstituents or []): material = item.Material - properties.append([i, "IFC Material", f"Constituent {idx + 1} Name", item.Name]) - properties.append([i, "IFC Material", f"Constituent {idx + 1} Material", material.Name]) + properties.append(PropertyRow(i, "IFC Material", f"Constituent {idx + 1} Name", item.Name)) + properties.append( + PropertyRow(i, "IFC Material", f"Constituent {idx + 1} Material", material.Name) + ) if category := getattr(material, "Category", None): - properties.append([i, "IFC Material", f"Constituent {idx + 1} Category", category]) + properties.append( + PropertyRow(i, "IFC Material", f"Constituent {idx + 1} Category", category) + ) elif material.is_a("IfcMaterialList"): for idx, material in enumerate(material.Materials): - properties.append([i, "IFC Material", f"Material {idx + 1} Name", material.Name]) + properties.append(PropertyRow(i, "IFC Material", f"Material {idx + 1} Name", material.Name)) if category := getattr(material, "Category", None): - properties.append([i, "IFC Material", f"Material {idx + 1} Category", category]) + properties.append(PropertyRow(i, "IFC Material", f"Material {idx + 1} Category", category)) layers = ifcopenshell.util.element.get_layers(self.file, element) for idx, layer in enumerate(layers): - properties.append([i, "IFC Presentation Layer Assignment", f"Layer {idx + 1}", layer.Name]) + properties.append(PropertyRow(i, "IFC Presentation Layer Assignment", f"Layer {idx + 1}", layer.Name)) relating_type = ifcopenshell.util.element.get_type(element) if relating_type and relating_type != element: - relationships.append([i, "IfcRelDefinesByType", id_map[relating_type.id()]]) + relationships.append(RelationshipRow(i, "IfcRelDefinesByType", id_map[relating_type.id()])) self.c.executemany("INSERT INTO elements VALUES (?, ?, ?, ?, ?, ?);", rows) self.c.executemany("INSERT INTO properties VALUES (?, ?, ?, ?);", properties)