BlenderBIM Script for Property Set

I'm trying to create a script for blenderBIM, that edits a custom property set. My coding experience is very limited as I'm an architect so please keep in mind I'm new to this!
I want the script to check for each Building Story and enter a custom value into a property set field "Geschosscode" in the Pset "Spezifisch".
The Pset is already existing with a placeholder "--", the script just needs to overwrite the values with the correct codes. The code should be corresponding to the Building Story, for example: The Building Story "Untergeschoss" should recieve the code entry "UG01", but the next Building Story "Erdgeschoss" should recieve "EG00".

The problem here is, that it works to run without error, but it only adds the code "OG04", which is the last entry, to the selected Building Story. It should add the right codes to all Storeys, no matter which one is currently selected.
This is the code I have so far, I think I have a logic issue, any help is highly appreciated:

import bpy
import blenderbim.bim.ifc
ifc = blenderbim.bim.ifc.IfcStore().get_file()
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

## define the globalIDs for each level
level_global_ids = {
    "Untergeschoss": "2LiJvusYaHxODNN62wEaPg",
    "Erdgeschoss": "2UCKF9sYaHxODNN62wEaPg",
    "1_Obergeschoss": "2aK8mdsYaHxODNN62wEaPg",
    "2_Obergeschoss": "2aK8r2sYaHxODNN62wEaPg",
    "3_Obergeschoss": "2mvNausYaHxODNN62wEaPg",
    "4_Obergeschoss": "3FOkb0tj0HxODNN62wEaPg"
}

## define the psetIDs for each level
level_pset_ids = {
    "Untergeschoss": 171,
    "Erdgeschoss": 297,
    "1_Obergeschoss": 374,
    "2_Obergeschoss": 439,
    "3_Obergeschoss": 504,
    "4_Obergeschoss": 616
}

## define the pset values to enter for each psetID
storey_codes_by_pset_id = {
    171: "UG01",
    297: "EG00",
    374: "OG01",
    439: "OG02",
    504: "OG03",
    616: "OG04"
}

## loop over each BuildingStorey object and set the corresponding value / i think the issue is here
for level, pset_id in level_pset_ids.items():
    obj_name = f"IfcBuildingStorey/{level}"
    global_id = level_global_ids[level]
    bpy.ops.bim.enable_pset_editing(pset_id=pset_id, obj=obj_name, obj_type="Object")

    ## Create a new element, code didnt run without this (?)
    element = bpy.context.scene.AddEditProperties.add()
    element.pset_name = "Spezifisch"
    element.property_name = "Geschosscode"
    element.string_value = storey_codes_by_pset_id[pset_id]
    element.primary_measure_type = 'IfcGloballyUniqueId'
    element.template_type = 'IfcPropertySingleValue'

    bpy.ops.bim.add_edit_custom_property()
    bpy.ops.bim.disable_pset_editing(obj=obj_name, obj_type="Object")
bpy.context.scene.AddEditProperties.clear()

Comments

  • edited May 2023

    @LFR
    Do you mean like this?

    I hope you don't mind. I took the liberty to completely refactor your code.

    import bpy
    import ifcopenshell
    import ifcopenshell.api
    from ifcopenshell.api import run
    import blenderbim.bim.ifc
    from blenderbim.bim.ifc import IfcStore
    from collections import OrderedDict
    
    ifc_file = ifcopenshell.open(IfcStore.path)
    products = ifc_file.by_type('IfcProduct')
    
    level_global_ids        =  OrderedDict()
    level_pset_ids          =  OrderedDict()
    storey_codes_by_psetid  =  OrderedDict()
    level_pset_list         =  ['171','297','374','439','504','616']
    storey_codes_list       =  ['UG01','EG00','OG01', 'OG02','OG03','OG04']
    
    for ifc_product in products:
        if ifc_product.is_a('IfcBuildingStorey'):
            level_global_ids[ifc_product.Name] = ifc_product.GlobalId
    
    for index, level_name in enumerate(level_global_ids.keys()):
        level_pset_id = level_pset_list[index]
        level_pset_ids[level_name] = level_pset_id
    
    for index, pset_id in enumerate(level_pset_ids.values()):
        storey_code_id = storey_codes_list[index]
        storey_codes_by_psetid[pset_id] = storey_code_id
    
    for ifc_product in products:
        if ifc_product.is_a('IfcBuildingStorey'):                
            for level, pset_id in level_pset_ids.items():
                if level == ifc_product.Name:
                    storey_code_id = storey_codes_by_psetid[level_pset_ids[level]]
                    property_set  =  run("pset.add_pset", ifc_file, product=ifc_product, name=str(storey_code_id))
                    run("pset.edit_pset", ifc_file, pset=property_set, properties={ "Pset ID": str(pset_id),
                                                                                    "Storey Code ID": str(storey_code_id)})
    
    ifc_file.write(IfcStore.path)
    

    I tried to remove all the hardcoded dictionaries from your script and created new two OrderedDictionaries from two separate lists. The script gets the IfcBuildingStorey instead of hardcoding the GlobalId in a dictionary.
    My code could probably do with some refactoring as well. But as you can see from the .gif it works. Hope this is what you meant?
    I think with some tweaking of this script you could get what you want in detail?

    Gorgious
  • Nice snippet @Coen ! I like these lower-level scripts. Might be a little more efficient to use for storey in ifc_file.by_type('IfcBuildingStorey'): instead of iterating over all products ?

    @LFR I didn't understand the use for the global ids in the OP. Was that for something else ?

  • edited May 2023

    Might be a little more efficient to use for storey in ifc_file.by_type('IfcBuildingStorey'): instead of iterating over all products ?

    Oh yes ofcourse, obvious. Silly mistake. ?, Here is the revised code:

    import bpy
    import ifcopenshell
    import ifcopenshell.api
    from ifcopenshell.api import run
    import blenderbim.bim.ifc
    from blenderbim.bim.ifc import IfcStore
    from collections import OrderedDict
    
    ifc_file = ifcopenshell.open(IfcStore.path)
    building_stories = ifc_file.by_type('IfcBuildingStorey')
    
    level_global_ids        =  OrderedDict()
    level_pset_ids          =  OrderedDict()
    storey_codes_by_psetid  =  OrderedDict()
    level_pset_list         =  ['171','297','374','439','504','616']
    storey_codes_list       =  ['UG01','EG00','OG01', 'OG02','OG03','OG04']
    
    for building_storey in building_stories:
        level_global_ids[building_storey.Name] = building_storey.GlobalId
    
    for index, level_name in enumerate(level_global_ids.keys()):
        level_pset_id = level_pset_list[index]
        level_pset_ids[level_name] = level_pset_id
    
    for index, pset_id in enumerate(level_pset_ids.values()):
        storey_code_id = storey_codes_list[index]
        storey_codes_by_psetid[pset_id] = storey_code_id
    
    for building_storey in building_stories:             
        for level, pset_id in level_pset_ids.items():
            if level == building_storey.Name:
                storey_code_id = storey_codes_by_psetid[level_pset_ids[level]]
                property_set  =  run("pset.add_pset", ifc_file, product=building_storey, name="Spezifisch")
                run("pset.edit_pset", ifc_file, pset=property_set, properties={ "Pset ID": str(pset_id),
                                                                                "Geschosscode": str(storey_code_id)})
    
    ifc_file.write(IfcStore.path)
    

  • I asked chatGPT to explain the code from the comment above:

    This code is used to set specific properties on IfcBuildingStorey objects in an IFC file. An IFC file is a file format used in the architecture, engineering and construction industry to exchange information between different software applications.

    The code first imports necessary modules and libraries such as ifcopenshell and blenderbim to work with IFC files in Python. It then opens the IFC file specified in IfcStore.path and retrieves all IfcBuildingStorey objects in the file.

    The code then creates three OrderedDict objects named level_global_ids, level_pset_ids and storey_codes_by_psetid. These objects are used to keep track of various information as the code runs.

    The level_global_ids dictionary is used to map each IfcBuildingStorey object's name to its GlobalId. Thelevel_pset_ids dictionary is used to map each IfcBuildingStorey object's name to a predefined Pset ID (a type of property set ID). Finally, the storey_codes_by_psetid dictionary is used to map each Pset ID to a storey code.

    The code then populates the level_pset_ids dictionary and storey_codes_by_psetid dictionary using the predefined level_pset_list and storey_codes_list.

    The code then loops over all IfcBuildingStorey objects and for each object, it loops over the level_pset_ids dictionary to find the matching Pset ID for the object. Once a match is found, the code uses the run function from the ifcopenshell.api module to create a new property set named "Spezifisch" on the object and sets the Pset ID and Geschosscode properties of the property set to the corresponding values.

    Finally, the modified IFC file is written back to disk.

    Overall, the code is doing a few things:

    1. Reading an IFC file
    2. Getting specific objects from the IFC file
    3. Creating dictionaries to map certain properties to each other
    4. Looping over the objects and updating them with new properties
    5. Writing the updated IFC file back to disk.
  • Hello @Coen @Gorgious , thanks so much for your input!
    I'm trying to change this entry to be specific:

    This is inside of blender. Which program did you use @Coen ?

    @Gorgious regarding your question, I used the globalIDs in an earlier version of the code, when I had trouble calling the Building Storeys, but you guys are right, best not to hardcode it.

    Thanks for your snippet, its looking much better already, however I cant get it to work for me. My Property Set is not updating inside of blender, maybe I need some kind of refresh of the IfcOpenShell?

  • @LFR

    My Property Set is not updating inside of blender, maybe I need some kind of refresh of the IfcOpenShell?

    Try having BIM Vision with the ifc openend, BIM Vision will prompt you for a reload of the file. Then try reloading it in your BlenderBIM add-on.

    This is inside of blender. Which program did you use @Coen ?

    For what you trying to achieve,Bblender is ok. I also used that for this case. You could also toggle the system console to see the output.

    If you want to get a more serious IDE (Integrated Development Environment). I recommend VS code and the VS Code Blender extension made my Jacques Lucke

  • edited May 2023

    It's not updating because the script is modifying the ifc file directly on disk, not the one that's in memory inside Blender. Change ifc_file = ifcopenshell.open(IfcStore.path) to ifc_file = IfcStore.file and delete the last line ifc_file.write(IfcStore.path) because that will be taken care of by the BlenderBIM addon and you don't want to unknowingly overwrite stuff.

    Note that because the property sets are populated when you click on an object, your new pset won't appear unless you re-select the object.
    Add bpy.context.view_layer.objects.active = bpy.context.active_object at the end to force the refresh.
    The other software is BIMVision if I'm not mistaken.

    BTW I'm not sure it's necessary to use OrderedDict here, since Python 3.7 all regular dicts are guaranteed to be traversed in the order in which elements were added.

    Coen
  • edited May 2023

    @Gorgious

    here, since Python 3.7 all regular dicts are guaranteed to be traversed in the order in which elements were added.

    Wow, I did not know that.

    It's not updating because the script is modifying the ifc file directly on disk, not the one that's in memory inside Blender. Change ifc_file = ifcopenshell.open(IfcStore.path) to ifc_file = IfcStore.file and delete the last line ifc_file.write(IfcStore.path) because that will be taken care of by the BlenderBIM addon and you don't want to unknowingly overwrite stuff

    I did not know that either. (❁´◡`❁)

    Gorgious
  • @Gorgious @Coen
    thanks again, that did the trick! Its working now

    Coen
  • @LFR

    thanks again, that did the trick! Its working now

    Just curious what you came up with eventually, do you mind posting the code here? :-D

  • Just curious what you came up with eventually, do you mind posting the code here? :-D

    Yes sure, I only modified it slightly, added all of my building storeys and removed the ID entry from the Pset:

        import bpy
        import ifcopenshell
        import ifcopenshell.api
        from ifcopenshell.api import run
        import blenderbim.bim.ifc
        from blenderbim.bim.ifc import IfcStore
        from collections import OrderedDict
    
        ifc_file = IfcStore.file
        building_stories = ifc_file.by_type('IfcBuildingStorey')
    
        level_global_ids = OrderedDict()
        level_pset_ids = OrderedDict()
        storey_codes_by_psetid = OrderedDict()
        level_pset_list = ['171', '297', '374', '439', '504', '616', '693', '758', '823', '953', '1018', '1083', '1148', '1213', '1278', '1343', '1408', '1473', '1538', '1603', '1668', '1733', '1798', '1863', '1975']
        storey_codes_list = ['UG01', 'EG00', 'OG01', 'OG02', 'OG03', 'OG04', 'OG05', 'OG06','OG07', 'OG08', 'OG09', 'OG10', 'OG11', 'OG12', 'OG13', 'OG14', 'OG15', 'OG16', 'OG17', 'OG18', 'OG19', 'OG20', 'OG21', 'OG22', 'OG23', 'OG24']
    
        for building_storey in building_stories:
            level_global_ids[building_storey.Name] = building_storey.GlobalId
    
        level_names = list(level_global_ids.keys())
    
        for index, level_name in enumerate(level_names):
            if index < len(level_pset_list):
                level_pset_id = level_pset_list[index]
                level_pset_ids[level_name] = level_pset_id
    
        for index, pset_id in enumerate(level_pset_ids.values()):
            if index < len(storey_codes_list):
                storey_code_id = storey_codes_list[index]
                storey_codes_by_psetid[pset_id] = storey_code_id
    
        for building_storey in building_stories:
            level = building_storey.Name
            if level in level_pset_ids:
                pset_id = level_pset_ids[level]
                if pset_id in storey_codes_by_psetid:
                    storey_code_id = storey_codes_by_psetid[pset_id]
                    property_set = run("pset.add_pset", ifc_file, product=building_storey, name="Spezifisch")
                    run("pset.edit_pset", ifc_file, pset=property_set, properties={"Geschosscode": str(storey_code_id)})
    
        bpy.context.view_layer.objects.active = bpy.context.active_object
    

    It even works if there is no Pset "Spezifisch" yet, it creates it :D

    Coen
  • @LFR
    Nice! Your lists are getting quite long, maybe it would be more practical to but it an external data source like CSV or Excel and read them from there with pyton? :-)

Sign In or Register to comment.