user: How do I use Registration in Blender? Include parameters, examples, warnings and best practices.

assistant: ### Module Registration

Blender modules loaded at startup require `register()` and `unregister()` functions.
These are the *only* functions that Blender calls from your code, which is otherwise a regular Python module.

A simple Blender Python module can look like this:

```
import bpy

class SimpleOperator(bpy.types.Operator):
    """ See example above """

def register():
    bpy.utils.register_class(SimpleOperator)

def unregister():
    bpy.utils.unregister_class(SimpleOperator)

if __name__ == "__main__":
    register()
```

These functions usually appear at the bottom of the script containing class registration sometimes adding menu items.
You can also use them for internal purposes setting up data for your own tools but take care
since register won’t re-run when a new blend-file is loaded.

The register/unregister calls are used so it’s possible to toggle add-ons and reload scripts while Blender runs.
If the register calls were placed in the body of the script, registration would be called on import,
meaning there would be no distinction between importing a module or loading its classes into Blender.
This becomes problematic when a script imports classes from another module
making it difficult to manage which classes are being loaded and when.

The last two lines are only for testing:

```
if __name__ == "__main__":
    register()
```

This allows the script to be run directly in the text editor to test changes.
This `register()` call won’t run when the script is imported as a module
since `__main__` is reserved for direct execution.

### Class Registration

Registering a class with Blender results in the class definition being loaded into Blender,
where it becomes available alongside existing functionality.
Once this class is loaded you can access it from [`bpy.types`](bpy.types.html#module-bpy.types "bpy.types"),
using the `bl_idname` rather than the classes original name.

When loading a class, Blender performs sanity checks making sure all required properties and functions are found,
that properties have the correct type, and that functions have the right number of arguments.

Mostly you will not need concern yourself with this but if there is a problem
with the class definition it will be raised on registering:

Using the function arguments `def execute(self, context, spam)`, will raise an exception:

`ValueError: expected Operator, SimpleOperator class "execute" function to have 2 args, found 3`

Using `bl_idname = 1` will raise:

`TypeError: validating class error: Operator.bl_idname expected a string type, not int`

#### Inter-Class Dependencies

When customizing Blender you may want to group your own settings together,
after all, they will likely have to co-exist with other scripts.
To group these properties classes need to be defined,
for groups within groups or collections within groups
you can’t avoid having to deal with the order of registration/unregistration.

Custom properties groups are themselves classes which need to be registered.

For example, if you want to store material settings for a custom engine:

```
# Create new property:
# bpy.data.materials[0].my_custom_props.my_float
import bpy

class MyMaterialProps(bpy.types.PropertyGroup):
    my_float: bpy.props.FloatProperty()

def register():
    bpy.utils.register_class(MyMaterialProps)
    bpy.types.Material.my_custom_props = bpy.props.PointerProperty(type=MyMaterialProps)

def unregister():
    del bpy.types.Material.my_custom_props
    bpy.utils.unregister_class(MyMaterialProps)

if __name__ == "__main__":
    register()
```

```
# Create new property group with a sub property:
# bpy.data.materials[0].my_custom_props.sub_group.my_float
import bpy

class MyMaterialSubProps(bpy.types.PropertyGroup):
    my_float: bpy.props.FloatProperty()

class MyMaterialGroupProps(bpy.types.PropertyGroup):
    sub_group: bpy.props.PointerProperty(type=MyMaterialSubProps)

def register():
    bpy.utils.register_class(MyMaterialSubProps)
    bpy.utils.register_class(MyMaterialGroupProps)
    bpy.types.Material.my_custom_props = bpy.props.PointerProperty(type=MyMaterialGroupProps)

def unregister():
    del bpy.types.Material.my_custom_props
    bpy.utils.unregister_class(MyMaterialGroupProps)
    bpy.utils.unregister_class(MyMaterialSubProps)

if __name__ == "__main__":
    register()
```

#### Manipulating Classes

Properties can be added and removed as Blender runs,
normally done on register or unregister but for some special cases
it may be useful to modify types as the script runs.

For example:

```
# Add a new property to an existing type.
bpy.types.Object.my_float: bpy.props.FloatProperty()
# Remove it.
del bpy.types.Object.my_float
```

This works just as well for `PropertyGroup` subclasses you define yourself.

```
class MyPropGroup(bpy.types.PropertyGroup):
    pass
MyPropGroup.my_float: bpy.props.FloatProperty()
```

This is equivalent to:

```
class MyPropGroup(bpy.types.PropertyGroup):
    my_float: bpy.props.FloatProperty()
```

#### Dynamic Class Definition (Advanced)

In some cases the specifier for data may not be in Blender, for example a external render engines shader definitions,
and it may be useful to define them as types and remove them on the fly.

```
for i in range(10):
    idname = "object.operator_{:d}".format(i)

    def func(self, context):
        print("Hello World", self.bl_idname)
        return {'FINISHED'}

    op_class = type(
        "DynOp{:d}".format(i),
        (bpy.types.Operator, ),
        {"bl_idname": idname, "bl_label": "Test", "execute": func},
    )
    bpy.utils.register_class(op_class)
```

To call the operators from the previous example:

```
>>> bpy.ops.object.operator_1()
Hello World OBJECT_OT_operator_1
{'FINISHED'}
```

```
>>> bpy.ops.object.operator_2()
Hello World OBJECT_OT_operator_2
{'FINISHED'}
```