2023-06-16 16:16:22 +10:00
try :
import re
import json
2023-06-16 20:32:40 +02:00
2023-06-16 16:16:22 +10:00
import ifcopenshell . util . schema
from . file import file
from . import ifcopenshell_wrapper
from . entity_instance import entity_instance
2023-06-16 20:32:40 +02:00
except ImportError as e :
print ( f " No SQL support: { e } " )
2023-06-16 16:16:22 +10:00
class sqlite ( file ) :
def __init__ ( self , filepath ) :
2023-06-16 20:32:40 +02:00
import sqlite3
2023-06-16 16:16:22 +10:00
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
2023-06-16 20:32:40 +02:00
# import mysql.connector
2023-06-16 16:16:22 +10:00
# 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 = { }
2023-06-27 21:02:50 +10:00
self . entity_cache = { }
2023-06-16 16:16:22 +10:00
for row in self . cursor . fetchall ( ) :
self . id_map [ row [ 0 ] ] = row [ 1 ]
self . class_map . setdefault ( row [ 1 ] , [ ] ) . append ( row [ 0 ] )
2023-06-27 21:02:50 +10:00
self . preprocess_schema ( )
def preprocess_schema ( self ) :
2023-06-16 16:16:22 +10:00
self . ifc_class_subtypes = { }
self . ifc_class_attributes = { }
self . ifc_class_inverse_attributes = { }
self . ifc_class_references = { }
self . ifc_class_inverses = { }
2023-07-07 18:12:52 +10:00
for declaration in self . ifc_schema . entities ( ) :
2023-06-16 16:16:22 +10:00
# print('Dealing with declaration', declaration.name())
self . ifc_class_subtypes [ declaration . name ( ) ] = ifcopenshell . util . schema . get_subtypes ( declaration )
self . ifc_class_attributes [ declaration . name ( ) ] = { a . name ( ) : a for a in declaration . all_attributes ( ) }
self . ifc_class_inverse_attributes [ declaration . name ( ) ] = {
a . name ( ) : a for a in declaration . all_inverse_attributes ( )
}
entity = [ ]
entity_list = [ ]
for attribute in declaration . all_attributes ( ) :
primitive = ifcopenshell . util . attribute . get_primitive_type ( attribute )
if primitive == " entity " :
entity . append ( attribute . name ( ) )
attribute_entity = attribute . type_of_attribute ( ) . declared_type ( )
for subtype in ifcopenshell . util . schema . get_subtypes ( attribute_entity ) :
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 ( ) )
elif self . is_entity_list ( attribute ) :
# print('is an entity list', attribute.name())
entity_list . append ( attribute . name ( ) )
for entity_name in re . findall ( " <entity (.*?)> " , 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 }
2023-06-26 16:55:14 +10:00
def clear_cache ( self ) :
self . entity_cache = { }
2023-06-16 16:16:22 +10:00
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 = [ ]
2023-06-27 21:02:50 +10:00
subtypes = self . ifc_class_subtypes [ type ] if include_subtypes else self . ifc_class_subtypes [ type ] [ 0 : 1 ]
for subtype in subtypes :
2023-06-16 16:16:22 +10:00
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 ) :
results = [ inst ]
queue = [ inst ]
while queue :
2023-06-27 21:02:50 +10:00
if max_levels is not None :
max_levels - = 1
2023-06-16 16:16:22 +10:00
cur = queue . pop ( )
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 , [ ] )
2023-06-27 21:02:50 +10:00
if not result :
continue
elif isinstance ( result , tuple ) :
2023-06-16 16:16:22 +10:00
results . extend ( result )
2023-06-27 21:02:50 +10:00
if max_levels is None or max_levels :
2023-06-16 16:16:22 +10:00
queue . extend ( result )
else :
results . append ( result )
2023-06-27 21:02:50 +10:00
if max_levels is None or max_levels :
2023-06-16 16:16:22 +10:00
queue . append ( result )
# print('traverse results', results)
return results
def get_inverse ( self , inst , allow_duplicate = False , with_attribute_indices = False ) :
2023-06-26 16:55:14 +10:00
query = f " SELECT inverses FROM { inst . sqlite_wrapper . ifc_class } WHERE `ifc_id` = { inst . sqlite_wrapper . id } LIMIT 1 "
self . cursor . execute ( query )
row = self . cursor . fetchone ( )
if not row or not row [ 0 ] :
return set ( )
return { self . by_id ( e ) for e in json . loads ( row [ 0 ] ) }
2023-06-16 16:16:22 +10:00
def is_entity_list ( self , attribute ) :
attribute = str ( attribute . type_of_attribute ( ) )
if ( attribute . startswith ( " <list " ) or attribute . startswith ( " <set " ) ) and " <entity " in attribute :
for data_type in re . findall ( " <(.*?) .*?> " , attribute ) :
if data_type not in ( " list " , " set " , " select " , " entity " ) :
return False
return True
return False
2024-03-18 17:25:09 +05:00
def get_geometry ( self , ids : list [ int ] ) - > dict [ str , dict ] :
2023-06-16 20:32:40 +02:00
import numpy as np
2023-06-16 16:16:22 +10:00
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 ( )
2023-06-26 16:55:14 +10:00
self . sqlite_wrapper . attribute_cache = { }
2023-06-16 16:16:22 +10:00
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")
2023-06-26 16:55:14 +10:00
query = f " SELECT * FROM { self . sqlite_wrapper . ifc_class } WHERE `ifc_id` = { self . sqlite_wrapper . id } LIMIT 1 "
2023-06-16 16:16:22 +10:00
self . sqlite_wrapper . file . cursor . execute ( query )
2023-06-26 16:55:14 +10:00
row = self . sqlite_wrapper . file . cursor . fetchone ( )
2023-06-16 16:16:22 +10:00
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 )
2023-06-26 16:55:14 +10:00
if not row or row [ aname ] is None :
self . sqlite_wrapper . attribute_cache [ aname ] = None
elif primitive == " entity " :
self . sqlite_wrapper . attribute_cache [ aname ] = self . sqlite_wrapper . file . by_id ( row [ aname ] )
elif isinstance ( primitive , tuple ) :
if isinstance ( row [ aname ] , int ) :
self . sqlite_wrapper . attribute_cache [ aname ] = self . sqlite_wrapper . file . by_id ( row [ aname ] )
else :
self . sqlite_wrapper . attribute_cache [ aname ] = self . unserialise_value ( json . loads ( row [ aname ] ) )
else :
self . sqlite_wrapper . attribute_cache [ aname ] = row [ aname ]
if isinstance ( self . sqlite_wrapper . attribute_cache [ aname ] , list ) :
self . sqlite_wrapper . attribute_cache [ aname ] = tuple ( self . sqlite_wrapper . attribute_cache [ aname ] )
2023-06-16 16:16:22 +10:00
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
results = [ ]
2023-06-26 16:55:14 +10:00
query = f " SELECT inverses FROM { self . sqlite_wrapper . ifc_class } WHERE `ifc_id` = { self . sqlite_wrapper . id } LIMIT 1 "
2023-06-16 16:16:22 +10:00
self . sqlite_wrapper . file . cursor . execute ( query )
2023-06-26 16:55:14 +10:00
row = self . sqlite_wrapper . file . cursor . fetchone ( )
if not row or not row [ 0 ] :
self . sqlite_wrapper . inverse_attribute_cache [ name ] = tuple ( )
return self . sqlite_wrapper . inverse_attribute_cache [ name ]
attribute = self . sqlite_wrapper . inverse_attributes [ name ]
entity_class = attribute . entity_reference ( ) . name ( )
declaration = self . sqlite_wrapper . file . ifc_schema . declaration_by_name ( entity_class )
forward_name = attribute . attribute_reference ( ) . name ( )
subtypes = [ st . name ( ) for st in ifcopenshell . util . schema . get_subtypes ( declaration ) ]
element_ids = json . loads ( row [ 0 ] )
for element_id in element_ids :
ifc_class = self . sqlite_wrapper . file . id_map [ element_id ]
if ifc_class in subtypes :
potential_result = self . sqlite_wrapper . file . by_id ( element_id )
forward_value = getattr ( potential_result , forward_name , None )
if not forward_value :
pass
elif isinstance ( forward_value , tuple ) :
if self . sqlite_wrapper . id in [ e . id ( ) for e in forward_value ] :
results . append ( potential_result )
elif forward_value . id ( ) == self . sqlite_wrapper . id :
results . append ( potential_result )
self . sqlite_wrapper . inverse_attribute_cache [ name ] = tuple ( results )
return self . sqlite_wrapper . inverse_attribute_cache [ name ]
2023-06-16 16:16:22 +10:00
raise AttributeError (
" entity instance of type ' %s ' has no attribute ' %s ' " % ( self . wrapped_data . is_a ( True ) , name )
)
2023-06-26 16:55:14 +10:00
def unserialise_value ( self , value ) :
if isinstance ( value , ( tuple , list ) ) :
for i , value2 in enumerate ( value ) :
value [ i ] = self . unserialise_value ( value2 )
return value
elif isinstance ( value , int ) :
2023-06-16 16:16:22 +10:00
return self . sqlite_wrapper . file . by_id ( value )
2023-06-26 16:55:14 +10:00
elif isinstance ( value , dict ) :
value2 = ifcopenshell . create_entity ( value [ " type " ] )
value2 [ 0 ] = value [ " value " ]
return value2
return value
2023-06-16 16:16:22 +10:00
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 }
2023-06-26 16:55:14 +10:00
if not self . sqlite_wrapper . attribute_cache :
self . __getitem__ ( 0 ) # This will get all attributes
info . update ( self . sqlite_wrapper . attribute_cache )
2023-06-16 16:16:22 +10:00
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 "