OSArch Community

[Blender] Create your first Blender add-on

  1. G
  2. C

    Thanks, this was suprisingly easy, however I always get lost in the seas of documentation with Blender

    What I did was this:

    I edited these settings:

    each time I save in VS code the add-on is reloaded

    result

  3. G

    Awesome !! I really hate registering / unregistering classes. That's why I pretty much always use this cool module by Jacques Lucke to auto load / unload blender classes. https://gist.github.com/JacquesLucke/11fecc6ea86ef36ea72f76ca547e795b

    Copy / paste the file in your addon root folder, boom. Everything is automatically registered. One thing you do absolutely need to do, if you're using subfolders, is add a file called __init__.py in every subfolder otherwise the utility won't be able to scan it and the classes won't register. It doesn't matter if the file is blank. It also automatically runs any function in your files named register at startup and unregister when you disable the addon, so you still can define custom behaviour on register / unregister.

    Take a look at one of my addons if you want to see how it works. https://github.com/Gorgious56/asset_browser_utilities

    Here for instance I don't register my menus, I only append them to the asset browser header. The rest is taken care of by the utility. https://github.com/Gorgious56/asset_browser_utilities/blob/master/core/ui/menu/main.py#L64-L69

  4. C

    @Gorgious

    How do you acces the values in operator.py if you have defined them in prop.py?

  5. G

    If they're in the same directory, you can do from . prop import my_prop

  6. C

    I think I am doing something wrong, I have the following in prop.py

    
    import bpy
    
    from bpy.types import Scene
    
    
    from bpy.props import BoolProperty
    
    
    def register():
    
        Scene.property_ifcproduct = BoolProperty(name="IfcProduct",description="Export IfcProduct",default=True)
    
    def unregister():
    
        del Scene.property_ifcproduct
    

    then in ui.py I have:

    
    import bpy
    
    from bpy.types import Panel
    
    
    class BlenderBIMSpreadSheetPanel(Panel):
    
        bl_label = "BlenderBIM Spreadsheet"
    
        bl_idname = "OBJECT_PT_blenderbimxlsxpanel"
    
        bl_space_type = "VIEW_3D"
    
        bl_region_type = "UI"
    
        bl_category = "Tools"
    
    
        def draw(self, context):
    
            row = self.layout.row()
    
            row.prop(context.scene, 'property_ifcproduct')
    
    
    def register():
    
        bpy.utils.register_class(BlenderBIMSpreadSheetPanel)
    
    
    def unregister():
    
        bpy.utils.unregister_class(BlenderBIMSpreadSheetPanel)
    
    

    Now I want to acces this in operator.py

    
    import bpy
    
    
    from . prop import property_ifcproduct
    

    The python files are all in the same folder.

    When I try from . prop import property_ifcproduct the add-on fails to load from VS code, it says:

    
    RuntimeError: Error: Traceback (most recent call last):
    
      File "c:\Program Files\Blender Foundation\Blender 3.4\3.4\scripts\modules\addon_utils.py", line 333, in enable
    
        mod = __import__(module_name)
    
      File "C:\Users\cclaus\AppData\Roaming\Blender Foundation\Blender\3.4\scripts\addons\blenderbim_spreadsheet\__init__.py", line 20, in <module>
    
        from . import ui, prop, operator
    
      File "C:\Users\cclaus\AppData\Roaming\Blender Foundation\Blender\3.4\scripts\addons\blenderbim_spreadsheet\operator.py", line 4, in <module>
    
        print (bpy.props.property_ifcproduct)
    
    AttributeError: module 'bpy.props' has no attribute 'property_ifcproduct'
    
  7. G

    Well you can't do that in Python, could you elaborate on what you're trying to do ? You can access the property in the operator from the context passed in the execute method. context.scene.property_ifcproduct

  8. C

    Thanks for answering:

    This I what I want to do:

    I want to create a checkbox in the ui, but I want to use the name of the checkbox in operator.py rather then the true or false value. My aim is to create only the 'hardcoded' variables in the ui.py.

    I made this littel UI:

    in ui.py:

    
    import bpy
    
    from bpy.types import Panel
    
    
    from . import  prop, operators
    
    
    class BlenderBIMSpreadSheetPanel(Panel):
    
        bl_idname = "OBJECT_PT_BlenderBIMSpreadSheet_panel"
    
        bl_label = "BlenderBIM Spreadsheet"
    
        bl_space_type = "VIEW_3D"
    
        bl_region_type = "UI"
    
        bl_category = "BlenderBIM | Spreadsheet"
    
    
        def draw(self, context):
    
    
            ifc_properties = context.scene.ifc_properties
    
            layout = self.layout
    
            layout.label(text="General")
    
            box = layout.box()
    
            row = box.row()
    
            row.prop(ifc_properties, "my_ifcproduct")
    
            row = box.row()
    
            row.prop(ifc_properties, "my_ifcbuildingstorey")
    
            layout.operator("export.tospreadsheet")
    
    
    def register():
    
        bpy.utils.register_class(BlenderBIMSpreadSheetPanel)
    
    
    def unregister():
    
        bpy.utils.unregister_class(BlenderBIMSpreadSheetPanel)
    

    in prop.py:

    
    import bpy
    
    from bpy.types import Scene
    
    from bpy.props import BoolProperty, StringProperty
    
    
    class IFCProperties(bpy.types.PropertyGroup):
    
        prop_ifc_product: StringProperty(name="My String Property")
    
    
        my_ifcproduct: bpy.props.BoolProperty(name="IfcProduct",description="Export IfcProduct",default=True)
    
        my_ifcbuildingstorey: bpy.props.BoolProperty(name="IfcBuildingStorey",description="Export IfcBuildingStorey",default = True)
    
    
    
    
    def register():
    
        bpy.utils.register_class(IFCProperties)
    
        bpy.types.Scene.ifc_properties = bpy.props.PointerProperty(type=IFCProperties)
    
    
    def unregister():
    
        bpy.utils.unregister_class(IFCProperties)
    
        del bpy.types.Scene.ifc_properties
    

    in operator.py

    
    import bpy
    
    
    from . import prop
    
    
    class ExportToSpreadSheet(bpy.types.Operator):
    
        bl_idname = "export.tospreadsheet"
    
        bl_label = "Export to Spreadsheet"
    
    
        def execute(self, context):
    
            #print("Hello, world!")
    
            #print (context.scene)
    
            #ifc_properties = context.scene
    
            my_bool_name = context.scene.ifc_properties.my_ifcproduct #.prop_ifc_product)
    
    
            # = bpy.types.Scene.bl_rna.properties["my_ifcproduct"].name
    
    
     )
    
            return {'FINISHED'}
    
    
    def register():
    
        bpy.utils.register_class(ExportToSpreadSheet)
    
    
    def unregister():
    
        bpy.utils.unregister_class(ExportToSpreadSheet)
    

    I would like the variable my_bool_name just to return IfcProduct as a dumb string in operator.py

  9. G

    Oh, okay, I understand. You may be going a little too fast here, but it's possible. FWIW I don't think it's that bad to hardcode values, as long as you can replace them easily with a "Search & Replace" across all your files.

    You need to access a barely documented construct through bl_rna.

    I didn't test this but you should be able to access it with my_bool_name = context.scene.bl_rna.properties["my_ifcproduct"].name. You can explore dir(context.scene.bl_rna.properties["my_ifcproduct"]), it lets you fetch some information about the property definition.

  10. C

    @Gorgious

    Thanks, I think I've exhaused every option, read the documentation thoroughfully but to no avail. I eventually came up with this workaround.

    The idea now is just to hardcode the variables only in prop.py

    
    import bpy
    
    from bpy.types import Scene
    
    from bpy.props import BoolProperty, StringProperty
    
    
    prop_ifcproduct = 'IfcProduct'
    
    
    class IFCProperties(bpy.types.PropertyGroup):
    
        my_ifcproduct: bpy.props.BoolProperty(name=prop_ifcproduct,description="Export IfcProduct",default=True)
    
        my_ifcbuildingstorey: bpy.props.BoolProperty(name="IfcBuildingStorey",description="Export IfcBuildingStorey",default = True)
    
    
    def register():
    
        bpy.utils.register_class(IFCProperties)
    
        bpy.types.Scene.ifc_properties = bpy.props.PointerProperty(type=IFCProperties)
    
    
    def unregister():
    
        bpy.utils.unregister_class(IFCProperties)
    
        del bpy.types.Scene.ifc_properties
    

    in operator.py

    
    import bpy
    
    from . import prop
    
    
    class ExportToSpreadSheet(bpy.types.Operator):
    
        bl_idname = "export.tospreadsheet"
    
        bl_label = "Export to Spreadsheet"
    
    
        def execute(self, context):
    
    
            print (prop.prop_ifcproduct)
    
    
            return {'FINISHED'}
    
    
    def register():
    
        bpy.utils.register_class(ExportToSpreadSheet)
    
    
    def unregister():
    
        bpy.utils.unregister_class(ExportToSpreadSheet)
    

    Might not be good practice and a bit risky maybe, but it works and I only have one place where I define variables

  11. C

    What would be best practice for filling the UI in blender with data coming from an IFC file?

    For example, I want to create a dropdown classification menu, so end users can select which classification they would like to export. Because there can be more than 1 classificatoin system:

  12. G

    You can fetch the relevant data from ifc and dynamically update the enum items with a callback. For example here's where the types are populated and here's where the dynamic enums are used to fetch the data. Remember to always have a permanent handle to the dynamic enum items or else you'll start seeing weird bugs in how they're displayed. See https://blender.stackexchange.com/questions/216230/is-there-a-workaround-for-the-known-bug-in-dynamic-enumproperty or the warning snippet in the docs for more information

  13. C

    @Gorgious

    Thanks, very insightful answer! :-)

    Another question:

    What would be best practice for saving the settings in the Blender UI?

    For example, the end-user would like to save this configuration in the UI:

    My idea was to store the settings in a text file somewhere. Kind of similar to Navisworks Search Sets. But after struggling for a while I have realized it might not be such a good idea. Is there no native Blender functionality which is capable of doing this?

  14. G

    There are two ways to store these settings natively in Blender : inside a single file, or across all blend files. If you don't mind each file having its own set of settings, you can add them to the Scene object type as a CollectionProperty and do your thing. I would advise against it, since it will only be available in this specific file and pretty much impossible to export to another file.

    It is possible to store these settings natively across blender files using the addon preferences. here's an example how to do it. Depending on how you defined the properties you want to save, it might be as easy as using this PropertyGroup as a PointerProperty or CollectionProperty in your addons prefs and adding a custom UI to save / load it. Note : you do not have to expose the addon preferences custom properties to the user. Use a custom draw method and show only what you want.

    I've done something like that in one of my addons. I self-imposed the constraint of not having external files to store this because I don't think it would be a good experience for the user. It does make the code more complicated than it needs to.

    I think in your case it would be more straightforward to use a txt file, that way users can reliably save their preferences, and share it with other users easily. You can add all sorts of custom information in your files. You should however explore json formatting which is a reliable way to serialize / deserialize custom data. That way you don't have to design a parser from scratch. One aded benefit is that you can theoretically use these files in other platforms, you won't be dependant on Blender's implementation. You could even use a format that is universally recognized for doing such tasks, etc.

  15. C

    I think in your case it would be more straightforward to use a txt file, that way users can reliably save their preferences, and share it with other users easily. You can add all sorts of custom information in your files. You should however explore json formatting which is a reliable way to serialize / deserialize custom data. That way you don't have to design a parser from scratch. One aded benefit is that you can theoretically use these files in other platforms, you won't be dependant on Blender's implementation. You could even use a format that is universally recognized for doing such tasks, etc.

    Thanks, this is a really useful tip

  16. C

    I managed to create a json file from all the Properties in my PropertyGroup class.

    
    {
    
        "my_selectionload": "C:\\Algemeen\\07_ifcopenshell\\00_ifc\\02_ifc_library\\IFC4 demo_selectionset.json",
    
        "my_ifcproduct": false,
    
        "my_ifcproductname": false,
    
        "my_ifcproducttypename": false,
    
        "my_ifcbuildingstorey": false,
    
        "my_ifcclassification": false,
    
        "my_ifcmaterial": false,
    
        "my_ifcpsetcommon": true,
    
        "my_isexternal": true,
    
        "my_loadbearing": true,
    
        "my_firerating": false,
    
        "my_acousticrating": false,
    
        "my_length": false,
    
        "my_width": true,
    
        "my_height": true,
    
        "my_area": false,
    
        "my_netarea": false,
    
        "my_netsidearea": false,
    
        "my_grossarea": false,
    
        "my_grosssidearea": false,
    
        "my_volume": true,
    
        "my_netvolume": true,
    
        "my_grossvolume": true,
    
        "my_spreadsheetfile": "C:\\Algemeen\\07_ifcopenshell\\00_ifc\\02_ifc_library\\IFC Schependomlaan_blenderbim.xlsx",
    
        "ods_or_xlsx": "ODS",
    
        "key1": "1",
    
        "key2": "2"
    
    }
    

    How would I set all the properties accordingly to this json file in the Blender UI? I am struggling with the logic.

    So far I came up with this:

    
            ifc_properties = context.scene.ifc_properties
    
            selection_file = open(ifc_properties.my_selectionload)
    
            selection_configuration = json.load(selection_file)
    
    
            for my_ifcproperty in ifc_properties.__annotations__.keys():
    
                my_ifcpropertyvalue = getattr(ifc_properties, my_ifcproperty)
    
    
    
                for property_name_from_json, property_value_from_json in selection_configuration.items():
    
                    if my_ifcproperty==property_name_from_json:
    
    
    
                        if property_value_from_json == True:
    
                            ifc_properties.my_ifcproductname = True
    
    
    
    
                        if property_value_from_json == False:
    
                            ifc_properties.my_ifcproductname = False
    

    But sometimes it seems to work and other times not, I am getting a bit confused.

  17. G

    I will try to answer on a practical standpoint for your last few lines, they can be considered an anti-pattern. First and foremost, in python you don't compare to boolean values with the equality symbol. You use if a is True, not if a == True. I won't go into technical details but you can look it up if you want. It can be reduced to ifc_properties.my_ifcproductname = bool(property_value_from_json), but I would use another method.

    I also don't know where ifc_properties.my_ifcproductname comes from ?

    I think you can reduce this whole for loop with just :

    
    for property_name_from_json, property_value_from_json in selection_configuration.items():
    
        if not hasattr(ifc_properties, property_name_from_json):
    
            continue  # don't bother if we can't reliably copy the property
    
        setattr(ifc_properties, property_name_from_json, property_value_from_json)
    

    You may need to cast the values if you don't want blender to complain, since json is formatting everything to a string format (except boolean values I guess ?).

    
    my_property_type = type(getattr(ifc_properties, property_name_from_json))
    
    setattr(ifc_properties, property_name_from_json, my_property_type (property_value_from_json))
    
  18. M

    Just dropping in to say this thread is a goldmine of knowledge and a huge thanks to @Gorgious for being so generous with his knowledge!

  19. G

    Hehe thank you it means a lot. I feel like I'm just doing my part in giving back a part of what was given to me by others :)

  20. C

    @Gorgious

    Thank you so much! I did not know about the setattr function.

    Found here a good post when to use is and when to use ==

  21. C

    @Gorgious said:

    You can fetch the relevant data from ifc and dynamically update the enum items with a callback. For example here's where the types are populated and here's where the dynamic enums are used to fetch the data. Remember to always have a permanent handle to the dynamic enum items or else you'll start seeing weird bugs in how they're displayed. See https://blender.stackexchange.com/questions/216230/is-there-a-workaround-for-the-known-bug-in-dynamic-enumproperty or the warning snippet in the docs for more information

    I'm really struggling with this, I want to update the EnumProperty from a Json file.

    So far I have this:

    
    class CustomCollectionActions(bpy.types.Operator):
    
        bl_idname = "custom.collection_actions"
    
        bl_label = "Execute"
    
        action: bpy.props.EnumProperty(
    
            items=(
    
                ("add",) * 3,
    
                ("remove",) * 3,
    
            ),
    
        )
    
        def execute(self, context):
    
    
            custom_collection = context.scene.custom_collection
    
            if self.action == "add":           
    
                item = custom_collection.items.add()  
    
            if self.action == "remove":
    
                custom_collection.items.remove(len(custom_collection.items) - 1 )
    
            return {"FINISHED"}  
    
    
        def set_configuration(context, property_set, property_name):
    
    
    
            print ('set configruaton method',property_set, property_name) 
    
    
            return {"FINISHED"}  
    

    The set configuration method is being called in another class ConfirmSelection like so:

    
    for property_name_from_json, property_value_from_json in selection_configuration.items():
    
                if property_name_from_json.startswith('my_ifccustomproperty'):
    
                    set_configuration(context, property_set=property_name_from_json, property_name=property_value_from_json)
    

    A section of the json file looks like this:

    
    "my_ifccustomproperty1": "1",
    
        "my_ifccustomproperty2": "3",
    
        "my_ifccustomproperty3": "3",
    
        "my_ifccustomproperty4": "4",
    
        "my_ifccustomproperty5": "5"
    

    I think I need to update action: bpy.props.EnumProperty. but before I can update it with a value I need to add an empty item?

  22. C

    @Coen said:

    @Gorgious said:

    You can fetch the relevant data from ifc and dynamically update the enum items with a callback. For example here's where the types are populated and here's where the dynamic enums are used to fetch the data. Remember to always have a permanent handle to the dynamic enum items or else you'll start seeing weird bugs in how they're displayed. See https://blender.stackexchange.com/questions/216230/is-there-a-workaround-for-the-known-bug-in-dynamic-enumproperty or the warning snippet in the docs for more information

    I'm really struggling with this, I want to update the EnumProperty from a Json file.

    So far I have this:

    class CustomCollectionActions(bpy.types.Operator):

    bl_idname = "custom.collection_actions"
    bl_label = "Execute"
    action: bpy.props.EnumProperty(
        items=(
            ("add",) * 3,
            ("remove",) * 3,
        ),
    )
    def execute(self, context):
        custom_collection = context.scene.custom_collection
        if self.action == "add":           
            item = custom_collection.items.add()  
        if self.action == "remove":
            custom_collection.items.remove(len(custom_collection.items) - 1 )
        return {"FINISHED"}  
    def set_configuration(context, property_set, property_name):
        print ('set configruaton method',property_set, property_name) 
        return {"FINISHED"}  

    The set configuration method is being called in another class ConfirmSelection like so:

    for property_name_from_json, property_value_from_json in selection_configuration.items():

            if property_name_from_json.startswith('my_ifccustomproperty'):
                set_configuration(context, property_set=property_name_from_json, property_name=property_value_from_json)

    A section of the json file looks like this:

    "my_ifccustomproperty1": "1",

    "my_ifccustomproperty2": "3",
    "my_ifccustomproperty3": "3",
    "my_ifccustomproperty4": "4",
    "my_ifccustomproperty5": "5"

    I think I need to update action: bpy.props.EnumProperty. but before I can update it with a value I need to add an empty item?

    I was overthinking this, it was suprisingly easy:

    
        def set_configuration(context, property_set, property_name):
    
            custom_collection = context.scene.custom_collection
    
            custom_collection.items.add().name = property_name
    
    
            return {"FINISHED"}   
    
  23. C

    How would I clear an enumproperty?

    Found this

    custom_collection.items.clear but I don't see it doing anything.

  24. C

    @Coen said:

    How would I clear an enumproperty?

    Found this

    custom_collection.items.clear but I don't see it doing anything.

    Disregard, I spoke too soon, the method is going through a loop so custom_collection.items.remove(1) was sufficient. Thank you for reading.

  25. C

    The result, you can now load a json file and will store the settings:

  1. Page 1
  2. 2
  3. 3
  4. 4
  5. 5

Login or Register to reply.