try: import re import json import sqlite3 import mysql.connector import numpy as np import ifcopenshell.util.schema from .file import file from . import ifcopenshell_wrapper from .entity_instance import entity_instance except: pass # No SQL support class sqlite(file): def __init__(self, filepath): self.wrapped_data = None self.history_size = 64 self.history = [] self.future = [] self.transaction = None self.filepath = filepath self.db = sqlite3.connect(self.filepath) self.db.row_factory = sqlite3.Row # self.db = mysql.connector.connect( # host="localhost", # user="root", # password="root", # database="test" # ) self.cursor = self.db.cursor() try: self.cursor.execute("SELECT preprocessor, schema, mvd FROM metadata LIMIT 1") row = self.cursor.fetchone() if row[0] != "IfcOpenShell-1.0.0": assert False, "SQLite schema not supported." except: assert False, "SQLite schema not supported." self.schema = row[1] self.ifc_schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(self.schema) self.cursor.execute("SELECT ifc_id, ifc_class FROM id_map") self.id_map = {} self.class_map = {} for row in self.cursor.fetchall(): self.id_map[row[0]] = row[1] self.class_map.setdefault(row[1], []).append(row[0]) self.ifc_class_subtypes = {} self.ifc_class_attributes = {} self.ifc_class_inverse_attributes = {} self.ifc_class_references = {} self.ifc_class_inverses = {} self.entity_cache = {} for declaration in self.ifc_schema.declarations(): if not str(declaration).startswith("", str(attribute)): attribute_entity = self.ifc_schema.declaration_by_name(entity_name) for subtype in ifcopenshell.util.schema.get_subtypes(attribute_entity): # self.ifc_class_inverses.setdefault(subtype.name(), set()).add(declaration.name()) self.ifc_class_inverses.setdefault(subtype.name(), {}) self.ifc_class_inverses[subtype.name()].setdefault(declaration.name(), []) self.ifc_class_inverses[subtype.name()][declaration.name()].append(attribute.name()) self.ifc_class_references[declaration.name()] = {"entity": entity, "entity_list": entity_list} def create_entity(self, type, *args, **kawrgs): assert False def by_id(self, id): entity = self.entity_cache.get(id, None) if entity: return entity ifc_class = self.id_map.get(id, None) if ifc_class: entity = sqlite_entity(id, ifc_class, self) self.entity_cache[id] = entity return entity self.cursor.execute("SELECT ifc_id, ifc_class FROM id_map LIMIT 1") row = self.cursor.fetchone() if row: self.id_map[row[0]] = row[1] entity = sqlite_entity(id, ifc_class, self) self.entity_cache[id] = entity return entity def by_type(self, type, include_subtypes=True): if self.class_map: results = [] for subtype in self.ifc_class_subtypes[type]: results.extend([self.by_id(i) for i in self.class_map.get(subtype.name(), [])]) return results if include_subtypes: declaration = self.ifc_schema.declaration_by_name(type) subtypes = ",".join([f"'{st.name()}'" for st in ifcopenshell.util.schema.get_subtypes(declaration)]) self.cursor.execute(f"SELECT ifc_id, ifc_class FROM id_map WHERE ifc_class IN ({subtypes})") rows = self.cursor.fetchall() return [self.by_id(r[0]) for r in rows] self.cursor.execute(f"SELECT ifc_id FROM id_map WHERE ifc_class='{type}'") rows = self.cursor.fetchall() return [self.by_id(r[0]) for r in rows] def traverse(self, inst, max_levels=None, breadth_first=False): print("traversing", inst) if max_levels is None: max_levels = 1 results = [inst] queue = [inst] while queue: max_levels -= 1 cur = queue.pop() level_results = set() reference_attributes = self.ifc_class_references[cur.sqlite_wrapper.ifc_class] attributes = reference_attributes["entity"] + reference_attributes["entity_list"] if not attributes: continue for attribute in attributes: result = getattr(cur, attribute, []) if isinstance(result, tuple): results.extend(result) if max_levels: queue.extend(result) else: results.append(result) if max_levels: queue.append(result) # print('traverse results', results) return results def get_inverse(self, inst, allow_duplicate=False, with_attribute_indices=False): results = [] print("getting inverse of", inst.sqlite_wrapper.id, inst.sqlite_wrapper.ifc_class) for inverse_class, inverse_attrs in self.ifc_class_inverses.get(inst.sqlite_wrapper.ifc_class, {}).items(): where = " OR ".join([f"`{attr}`={inst.sqlite_wrapper.id}" for attr in inverse_attrs]) query = f"SELECT DISTINCT ifc_id FROM {inverse_class} WHERE {where}" self.cursor.execute(query) rows = self.cursor.fetchall() for row in rows: results.append(self.by_id(row[0])) # print('we got', results) if allow_duplicate: return results return set(results) def is_entity_list_old(self, primitive, is_first_call=True): if not isinstance(primitive, tuple): return False elif is_first_call and primitive[0] == "select": return False elif primitive[1] == "entity": return True elif isinstance(primitive[1], tuple): return self.is_entity_list(primitive[1], is_first_call=False) return False def is_entity_list(self, attribute): attribute = str(attribute.type_of_attribute()) if (attribute.startswith("", attribute): if data_type not in ("list", "set", "select", "entity"): return False return True return False def get_geometry(self, ids): ids_csv = ",".join(map(str, ids)) query = f"SELECT ifc_id, x, y, z, matrix, geometry, verts, edges, faces, material_ids, materials FROM shape LEFT JOIN geometry ON shape.geometry = geometry.id WHERE `ifc_id` IN ({ids_csv})" self.cursor.execute(query) rows = self.cursor.fetchall() shapes = {} geometry = {} for row in rows: if row["geometry"] and row["geometry"] not in geometry: geometry[row["geometry"]] = { "verts": np.frombuffer(row["verts"]).tolist() if row["verts"] else [], "edges": np.frombuffer(row["edges"], dtype=np.int64).tolist() if row["edges"] else [], "faces": np.frombuffer(row["faces"], dtype=np.int64).tolist() if row["faces"] else [], "material_ids": np.frombuffer(row["material_ids"], dtype=np.int64).tolist() if row["material_ids"] else [], "materials": json.loads(row["materials"]) if row["materials"] else [], } shapes[row["ifc_id"]] = { "co": [row["x"], row["y"], row["z"]], "matrix": np.copy(np.frombuffer(row["matrix"]).reshape((4, 4))), "geometry": row["geometry"], } ids_without_geometry = set(ids) - set(shapes.keys()) for id in ids_without_geometry: shapes[id] = { "co": [0.0, 0.0, 0.0], "matrix": np.eye(4), "geometry": None, } return {"shapes": shapes, "geometry": geometry} class sqlite_entity(entity_instance): def __init__(self, id, ifc_class, file=None): if not ifc_class: print(id, ifc_class, file) assert False e = ifcopenshell_wrapper.new_IfcBaseClass(file.schema, ifc_class) s = sqlite_wrapper(id, ifc_class, file) super(entity_instance, self).__setattr__("wrapped_data", e) super(entity_instance, self).__setattr__("sqlite_wrapper", s) def id(self): return self.sqlite_wrapper.id def __del__(self): pass def __getitem__(self, key): return self.__getattr__(list(self.sqlite_wrapper.attributes.keys())[key]) def __setattr__(self, key, value): # query = f"UPDATE `{self.sqlite_wrapper.ifc_class}` SET `{key}`='' WHERE `ifc_id` = {self.sqlite_wrapper.id}" query = f"UPDATE `{self.sqlite_wrapper.ifc_class}` SET `{key}` = ? WHERE ifc_id = {self.sqlite_wrapper.id}" self.sqlite_wrapper.file.cursor.execute(query, (value,)) self.sqlite_wrapper.file.db.commit() def __getattr__(self, name): # print("*" * 100) # print("GETATTR", self.sqlite_wrapper.id, self.sqlite_wrapper.ifc_class, name) INVALID, FORWARD, INVERSE = range(3) attr_cat = self.wrapped_data.get_attribute_category(name) if attr_cat == FORWARD: if self.sqlite_wrapper.attribute_cache: # print(self.sqlite_wrapper.ifc_class) # print(self.sqlite_wrapper.attribute_cache) return self.sqlite_wrapper.attribute_cache[name] # print('first time for', self.sqlite_wrapper.ifc_class) # print("IT IS A FORWARD") query = f"SELECT * FROM {self.sqlite_wrapper.ifc_class} WHERE `ifc_id` = {self.sqlite_wrapper.id}" self.sqlite_wrapper.file.cursor.execute(query) rows = self.sqlite_wrapper.file.cursor.fetchall() for attribute in self.sqlite_wrapper.attributes.values(): # attribute = self.sqlite_wrapper.attributes[name] aname = attribute.name() primitive = ifcopenshell.util.attribute.get_primitive_type(attribute) is_entity_list = self.sqlite_wrapper.file.is_entity_list(attribute) # print("IS IT AN ENTITY LIST", is_entity_list, primitive) # if is_entity_list: # query = ( # f"SELECT `{name}` FROM {self.sqlite_wrapper.ifc_class} WHERE `ifc_id` = {self.sqlite_wrapper.id}" # ) # else: # query = f"SELECT * FROM {self.sqlite_wrapper.ifc_class} WHERE `ifc_id` = {self.sqlite_wrapper.id} LIMIT 1" # print("query is", query) # self.sqlite_wrapper.file.cursor.execute(query) if is_entity_list: # rows = self.sqlite_wrapper.file.cursor.fetchall() # print("forward rows are", rows) # print("returning", tuple((self.sqlite_wrapper.file.by_id(r[0]) for r in rows if r[0]))) result = tuple((self.sqlite_wrapper.file.by_id(r[aname]) for r in rows if r[aname])) self.sqlite_wrapper.attribute_cache[aname] = result # return result else: # row = self.sqlite_wrapper.file.cursor.fetchone() row = rows[0] if not row or row[aname] is None: self.sqlite_wrapper.attribute_cache[aname] = None # return None # print("primitive is", primitive) elif primitive == "entity": # print("returning entity", row[0]) entity = self.sqlite_wrapper.file.by_id(row[aname]) self.sqlite_wrapper.attribute_cache[aname] = entity # return entity elif isinstance(primitive, tuple) and primitive[0] == "select": result = self.get_select_value(primitive, row[aname]) self.sqlite_wrapper.attribute_cache[aname] = result # return result elif isinstance(primitive, tuple) and primitive[0] in ("list", "set"): if isinstance(row[aname], int): result = (self.sqlite_wrapper.file.by_id(row[aname]),) self.sqlite_wrapper.attribute_cache[aname] = result # return result else: result = json.loads(row[aname]) self.sqlite_wrapper.attribute_cache[aname] = result # return result else: # print("returning", row[0]) result = row[aname] self.sqlite_wrapper.attribute_cache[aname] = result # return result return self.sqlite_wrapper.attribute_cache[name] elif attr_cat == INVERSE: if self.sqlite_wrapper.inverse_attribute_cache: results = self.sqlite_wrapper.inverse_attribute_cache.get(name, None) if results is not None: return results # print("IT IS AN INVERSE") attribute = self.sqlite_wrapper.inverse_attributes[name] inverse_name = attribute.attribute_reference().name() declaration = self.sqlite_wrapper.file.ifc_schema.declaration_by_name(attribute.entity_reference().name()) results = [] # Union is slightly faster it seems. Not much though. subtypes = ifcopenshell.util.schema.get_subtypes(declaration) query = " UNION ".join( f"SELECT DISTINCT `ifc_id` FROM {subtype.name()} WHERE `{inverse_name}` = {self.sqlite_wrapper.id}" for subtype in subtypes ) self.sqlite_wrapper.file.cursor.execute(query) rows = self.sqlite_wrapper.file.cursor.fetchall() results.extend([self.sqlite_wrapper.file.by_id(r[0]) for r in rows]) results = tuple(results) self.sqlite_wrapper.inverse_attribute_cache[name] = results # Loop variant # for subtype in ifcopenshell.util.schema.get_subtypes(declaration): # query = f"SELECT DISTINCT `ifc_id` FROM {subtype.name()} WHERE `{name}` = {self.sqlite_wrapper.id}" # self.sqlite_wrapper.file.cursor.execute(query) # rows = self.sqlite_wrapper.file.cursor.fetchall() # results.extend([self.sqlite_wrapper.file.by_id(r[0]) for r in rows]) # print("query is", query) # print("inverse rows are", rows) # print("returning", tuple((self.sqlite_wrapper.file.by_id(r[0]) for r in rows))) return results raise AttributeError( "entity instance of type '%s' has no attribute '%s'" % (self.wrapped_data.is_a(True), name) ) def get_select_value(self, primitive, value): if "entity" in primitive[1] and isinstance(value, int): return self.sqlite_wrapper.file.by_id(value) data = json.loads(value) ifc_primitive = ifcopenshell.create_entity(data["type"]) ifc_primitive[0] = data["value"] return ifc_primitive def __eq__(self, other): if not isinstance(self, type(other)): return False elif None in (self.sqlite_wrapper.file, other.sqlite_wrapper.file): assert False # not implemented if self.sqlite_wrapper.id: return self.sqlite_wrapper.id == other.sqlite_wrapper.id assert False # not implemented def __hash__(self): if self.sqlite_wrapper.id: return hash((self.sqlite_wrapper.id, self.sqlite_wrapper.file.filepath)) def get_info(self, include_identifier=True, recursive=False, return_type=dict, ignore=(), scalar_only=False): info = {"id": self.sqlite_wrapper.id, "type": self.sqlite_wrapper.ifc_class} if self.sqlite_wrapper.attribute_cache: info.update(self.sqlite_wrapper.attribute_cache) return info query = f"SELECT * FROM {self.sqlite_wrapper.ifc_class} WHERE `ifc_id` = {self.sqlite_wrapper.id}" # print('GET INFO QUERY', query) self.sqlite_wrapper.file.cursor.execute(query) rows = self.sqlite_wrapper.file.cursor.fetchall() row = rows[0] entity_list_indices = [] entity_list_names = [] attribute_names = list(self.sqlite_wrapper.attributes.keys()) for i, value in enumerate(row[1:]): # print('enumerating row', i, value) attribute_name = attribute_names[i] # print('attr name is', attribute_name) attribute = self.sqlite_wrapper.attributes[attribute_name] # print('attribute is', attribute) primitive = ifcopenshell.util.attribute.get_primitive_type(attribute) # print('primitive is', primitive) is_entity_list = self.sqlite_wrapper.file.is_entity_list(attribute) if is_entity_list: entity_list_indices.append(i) entity_list_names.append(attribute_name) info[attribute_name] = [] elif value is None: info[attribute_name] = None elif primitive == "entity": info[attribute_name] = self.sqlite_wrapper.file.by_id(value) elif isinstance(primitive, tuple) and primitive[0] == "select": self.get_select_value(primitive, value) elif isinstance(primitive, tuple) and primitive[0] in ("list", "set"): info[attribute_name] = json.loads(value) else: info[attribute_name] = value for row in rows: row = row[1:] for i, entity_list_index in enumerate(entity_list_indices): value = row[entity_list_index] info[entity_list_names[i]].append(self.sqlite_wrapper.file.by_id(value)) for name in entity_list_names: info[name] = tuple(info[name]) return info class sqlite_wrapper: def __init__(self, id, ifc_class, file): self.id = id self.ifc_class = ifc_class self.file = file self.attributes = self.file.ifc_class_attributes[self.ifc_class] self.inverse_attributes = self.file.ifc_class_inverse_attributes[self.ifc_class] self.attribute_cache = {} self.inverse_attribute_cache = {} def __repr__(self): return "todo"