2020-07-31 16:12:14 +10:00
#!/usr/bin/env python3
2021-08-05 13:21:55 +10:00
# IfcCSV - A utility to interact with IFC data through CSV.
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcCSV.
#
# IfcCSV is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcCSV 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcCSV. If not, see <http://www.gnu.org/licenses/>.
2020-03-16 13:19:35 +11:00
# This can be packaged with `pyinstaller --onefile --clean --icon=icon.ico ifccsv.py`
import ifcopenshell
2020-08-08 17:58:45 +10:00
import ifcopenshell . util . selector
2020-09-15 14:58:17 +10:00
import ifcopenshell . util . element
2021-01-04 13:07:29 +11:00
import ifcopenshell . util . schema
2020-03-16 13:19:35 +11:00
import csv
import argparse
2020-11-01 19:23:50 +07:00
2021-01-22 15:52:48 +11:00
class IfcAttributeSetter :
2020-03-16 13:19:35 +11:00
@staticmethod
2020-09-15 14:58:17 +10:00
def set_element_key ( ifc_file , element , key , value ) :
if key == " type " and element . is_a ( ) != value :
2021-01-04 13:07:29 +11:00
return ifcopenshell . util . schema . reassign_class ( ifc_file , element , value )
2020-04-26 14:40:33 +10:00
if hasattr ( element , key ) :
2020-09-15 14:58:17 +10:00
setattr ( element , key , value )
return element
2020-04-26 14:40:33 +10:00
if " . " not in key :
2020-09-15 14:58:17 +10:00
return element
2020-04-26 14:40:33 +10:00
if key [ 0 : 3 ] == " Qto " :
2021-03-14 23:20:51 +02:00
qto_name , prop = key . split ( " . " , 1 )
2021-01-22 15:52:48 +11:00
qto = IfcAttributeSetter . get_element_qto ( element , qto_name )
2020-03-16 13:19:35 +11:00
if qto :
2021-01-22 15:52:48 +11:00
IfcAttributeSetter . set_qto_property ( qto , prop , value )
2020-09-15 14:58:17 +10:00
return element
2020-07-18 17:29:32 +10:00
pset_name , prop = key . split ( " . " , 1 )
2021-01-22 15:52:48 +11:00
pset = IfcAttributeSetter . get_element_pset ( element , pset_name )
2020-04-26 14:40:33 +10:00
if pset :
2021-10-13 12:17:20 +11:00
IfcAttributeSetter . set_pset_property ( ifc_file , pset , prop , value )
2020-09-15 14:58:17 +10:00
return element
return element
@staticmethod
2020-03-16 13:19:35 +11:00
def get_element_qto ( element , name ) :
for relationship in element . IsDefinedBy :
if (
relationship . is_a ( " IfcRelDefinesByProperties " )
and relationship . RelatingPropertyDefinition . is_a ( " IfcElementQuantity " )
and relationship . RelatingPropertyDefinition . Name == name
) :
return relationship . RelatingPropertyDefinition
@staticmethod
2020-05-17 19:32:04 +10:00
def set_qto_property ( qto , name , value ) :
2020-03-16 13:19:35 +11:00
for prop in qto . Quantities :
if prop . Name != name :
continue
setattr ( prop , prop . is_a ( ) [ len ( " IfcQuantity " ) : ] + " Value " , value )
@staticmethod
def get_element_pset ( element , name ) :
if element . is_a ( " IfcTypeObject " ) :
if element . HasPropertySets :
for pset in element . HasPropertySets :
if pset . is_a ( " IfcPropertySet " ) and pset . Name == name :
return pset
else :
for relationship in element . IsDefinedBy :
if (
relationship . is_a ( " IfcRelDefinesByProperties " )
and relationship . RelatingPropertyDefinition . is_a ( " IfcPropertySet " )
and relationship . RelatingPropertyDefinition . Name == name
) :
return relationship . RelatingPropertyDefinition
@staticmethod
2021-10-13 12:17:20 +11:00
def set_pset_property ( ifc_file , pset , name , value ) :
2020-03-16 13:19:35 +11:00
for property in pset . HasProperties :
2021-10-13 12:17:20 +11:00
if property . Name != name :
continue
2021-10-13 12:18:29 +11:00
if value . lower ( ) in [ " null " , " none " ] :
property . NominalValue = None
continue
2021-10-13 12:17:20 +11:00
# In lieu of loading a map for data casting, we only have four
# options, which this ugly method will determine.
if property . NominalValue is None :
if value . isnumeric ( ) :
property . NominalValue = ifc_file . createIfcInteger ( int ( value ) )
elif value . lower ( ) in [ " 1 " , " true " , " yes " , " uh-huh " ] :
property . NominalValue = ifc_file . createIfcBoolean ( True )
elif value . lower ( ) in [ " 0 " , " false " , " no " , " nope " ] :
property . NominalValue = ifc_file . createIfcBoolean ( False )
else :
try :
value = float ( value )
property . NominalValue = ifc_file . createIfcReal ( value )
except :
property . NominalValue = ifc_file . createIfcLabel ( str ( value ) )
else :
2020-07-28 11:47:22 +10:00
try :
property . NominalValue . wrappedValue = str ( value )
except :
try :
property . NominalValue . wrappedValue = float ( value )
except :
try :
property . NominalValue . wrappedValue = int ( value )
except :
property . NominalValue . wrappedValue = (
2021-10-13 12:17:20 +11:00
True if value . lower ( ) in [ " 1 " , " true " , " yes " , " uh-huh " ] else False
2020-11-01 19:23:50 +07:00
)
2020-03-16 13:19:35 +11:00
class IfcCsv :
def __init__ ( self ) :
self . results = [ ]
self . attributes = [ ]
self . output = " "
2022-01-24 17:52:32 +11:00
self . selector = ifcopenshell . util . selector . Selector ( )
2021-03-22 15:35:17 +11:00
self . delimiter = " , "
2020-03-16 13:19:35 +11:00
2020-07-18 17:29:32 +10:00
def export ( self , ifc_file , elements ) :
self . ifc_file = ifc_file
2020-03-16 13:19:35 +11:00
for element in elements :
result = [ ]
if hasattr ( element , " GlobalId " ) :
result . append ( element . GlobalId )
else :
result . append ( None )
2020-07-18 17:29:32 +10:00
for index , attribute in enumerate ( self . attributes ) :
if " * " in attribute :
self . attributes . extend ( self . get_wildcard_attributes ( attribute ) )
del self . attributes [ index ]
2020-03-16 13:19:35 +11:00
for attribute in self . attributes :
2020-08-08 17:58:45 +10:00
result . append ( self . selector . get_element_value ( element , attribute ) )
2020-03-16 13:19:35 +11:00
self . results . append ( result )
with open ( self . output , " w " , newline = " " , encoding = " utf-8 " ) as f :
2021-03-22 15:35:17 +11:00
writer = csv . writer ( f , delimiter = self . delimiter )
2020-03-16 13:19:35 +11:00
header = [ " GlobalId " ]
header . extend ( self . attributes )
writer . writerow ( header )
for row in self . results :
writer . writerow ( row )
2020-07-18 17:29:32 +10:00
def get_wildcard_attributes ( self , attribute ) :
results = set ( )
pset_qto_name = attribute . split ( " . " , 1 ) [ 0 ]
for element in self . ifc_file . by_type ( " IfcPropertySet " ) + self . ifc_file . by_type ( " IfcElementQuantity " ) :
if element . Name != pset_qto_name :
continue
if element . is_a ( " IfcPropertySet " ) :
results . update ( [ p . Name for p in element . HasProperties ] )
else :
results . update ( [ p . Name for p in element . Quantities ] )
return [ " {} . {} " . format ( pset_qto_name , n ) for n in results ]
2021-03-22 16:19:29 +11:00
def Import ( self , ifc_file ) :
2020-03-16 13:19:35 +11:00
with open ( self . output , newline = " " , encoding = " utf-8 " ) as f :
2020-12-28 10:18:55 +07:00
reader = csv . reader ( f , delimiter = self . delimiter )
2020-03-16 13:19:35 +11:00
headers = [ ]
for row in reader :
if not headers :
headers = row
continue
2020-11-14 10:11:36 +11:00
try :
element = ifc_file . by_guid ( row [ 0 ] )
except :
print ( " The element with GUID {} was not found " . format ( row [ 0 ] ) )
2020-03-16 13:19:35 +11:00
continue
for i , value in enumerate ( row ) :
if i == 0 :
continue # Skip GlobalId
2021-01-22 15:52:48 +11:00
element = IfcAttributeSetter . set_element_key ( ifc_file , element , headers [ i ] , value )
2020-03-16 13:19:35 +11:00
2020-11-01 19:23:50 +07:00
2020-04-26 14:21:11 +10:00
if __name__ == " __main__ " :
parser = argparse . ArgumentParser ( description = " Exports IFC data to and from CSV " )
parser . add_argument ( " -i " , " --ifc " , type = str , required = True , help = " The IFC file " )
parser . add_argument ( " -c " , " --csv " , type = str , default = " data.csv " , help = " The CSV file to import from or export to " )
parser . add_argument ( " -q " , " --query " , type = str , default = " " , help = ' Specify a IFC query selector, such as " .IfcWall " ' )
2020-12-02 08:50:15 +11:00
parser . add_argument ( " -a " , " --arguments " , nargs = " + " , help = " Specify attributes that are part of the extract, using the IfcQuery syntax such as ' type ' , ' Name ' or ' Pset_Foo.Bar ' " )
2020-04-26 14:21:11 +10:00
parser . add_argument ( " --export " , action = " store_true " , help = " Export from IFC to CSV " )
parser . add_argument ( " --import " , action = " store_true " , help = " Import from CSV to IFC " )
args = parser . parse_args ( )
2020-03-16 13:19:35 +11:00
2020-04-26 14:21:11 +10:00
if args . export :
2020-08-08 17:58:45 +10:00
ifc_file = ifcopenshell . open ( args . ifc )
selector = ifcopenshell . util . selector . Selector ( )
results = selector . parse ( ifc_file , args . query )
2020-04-26 14:21:11 +10:00
ifc_csv = IfcCsv ( )
ifc_csv . output = args . csv
ifc_csv . attributes = args . arguments if args . arguments else [ ]
2020-08-08 17:58:45 +10:00
ifc_csv . selector = selector
ifc_csv . export ( ifc_file , results )
2020-04-26 14:21:11 +10:00
elif getattr ( args , " import " ) :
ifc_csv = IfcCsv ( )
ifc_csv . output = args . csv
2021-03-22 16:19:29 +11:00
ifc_file = ifcopenshell . open ( args . ifc )
ifc_csv . Import ( ifc_file )
ifc_file . write ( args . ifc )