user: Describe Your Second Add-on from the Blender manual or Python API, including usage steps and snippets.

assistant: For our second add-on, we will focus on object instancing – this is – to make linked
copies of an object in a similar way to what you may have seen with the Array modifier.

### Write the Script

As before, first we will start with a script, develop it, then convert it into an add-on.

```
import bpy
from bpy import context

# Get the current scene
scene = context.scene

# Get the 3D cursor location
cursor = scene.cursor.location

# Get the active object (assume we have one)
obj = context.active_object

# Now make a copy of the object
obj_new = obj.copy()

# The new object has to be added to a collection in the scene
scene.collection.objects.link(obj_new)

# Now we can place the object
obj_new.location = cursor
```

Now try copying this script into Blender and run it on the default Cube.
Make sure you click to move the 3D cursor before running as the duplicate will appear at the cursor’s location.

After running, notice that when you go into *Edit Mode* to change the Cube – all of the copies change.
In Blender, this is known as *Linked Duplicates*.

Next, we’re going to do this in a loop, to make an array of objects between the active object and the cursor.

```
import bpy
from bpy import context

scene = context.scene
cursor = scene.cursor.location
obj = context.active_object

# Use a fixed value for now, eventually make this user adjustable
total = 10

# Add 'total' objects into the scene
for i in range(total):
    obj_new = obj.copy()
    scene.collection.objects.link(obj_new)

    # Now place the object in between the cursor
    # and the active object based on 'i'
    factor = i / total
    obj_new.location = (obj.location * factor) + (cursor * (1.0 - factor))
```

Try running this script with the active object and the cursor spaced apart to see the result.

With this script you’ll notice we’re doing some math with the object location and cursor,
this works because both are 3D [`mathutils.Vector`](https://docs.blender.org/api/5.0/mathutils.html#mathutils.Vector "(in Blender 5.0.1 Python API v5.0)") instances,
a convenient class provided by the [`mathutils`](https://docs.blender.org/api/5.0/mathutils.html#module-mathutils "(in Blender 5.0.1 Python API v5.0)") module which
allows vectors to be multiplied by numbers and matrices.

If you are interested in this area, read into [`mathutils.Vector`](https://docs.blender.org/api/5.0/mathutils.html#mathutils.Vector "(in Blender 5.0.1 Python API v5.0)")
– there are many handy utility functions such as getting the angle between vectors,
cross product, dot products as well as more advanced functions in [`mathutils.geometry`](https://docs.blender.org/api/5.0/mathutils.geometry.html#module-mathutils.geometry "(in Blender 5.0.1 Python API v5.0)")
such as Bézier spline interpolation and ray-triangle intersection.

For now we will focus on making this script an add-on, but it’s good to know that this
3D math module is available and can help you with more advanced functionality later on.

### Write the Add-on

The first step is to convert the script as-is into an add-on:

```
bl_info = {
    "name": "Cursor Array",
    "blender": (2, 80, 0),
    "category": "Object",
}

import bpy

class ObjectCursorArray(bpy.types.Operator):
    """Object Cursor Array"""
    bl_idname = "object.cursor_array"
    bl_label = "Cursor Array"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        scene = context.scene
        cursor = scene.cursor.location
        obj = context.active_object

        total = 10

        for i in range(total):
            obj_new = obj.copy()
            scene.collection.objects.link(obj_new)

            factor = i / total
            obj_new.location = (obj.location * factor) + (cursor * (1.0 - factor))

        return {'FINISHED'}

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

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

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

Everything here has been covered in the previous steps, you may want to try run
the add-on still and consider what could be done to make it more useful.

The two of the most obvious missing things are – having the total fixed at 10,
and having to access the operator with [Operator Search](../../interface/operators.xhtml#bpy-ops-wm-search-operator) is not very convenient.

Both these additions are explained next, with the final script afterwards.

#### Operator Property

There are a variety of property types that are used for tool settings, common property types include:
int, float, vector, color, Boolean and string.

These properties are handled differently to typical Python class attributes
because Blender needs to display them in the interface,
store their settings in keymaps and keep settings for reuse.

While this is handled in a fairly Pythonic way, be mindful that you are in fact defining tool settings that
are loaded into Blender and accessed by other parts of Blender, outside of Python.

To get rid of the literal 10 for `total`, we’ll use an operator property.
Operator properties are defined via bpy.props module, this is added to the class body:

```
# moved assignment from execute() to the body of the class...
total: bpy.props.IntProperty(name="Steps", default=2, min=1, max=100)

# and this is accessed on the class
# instance within the execute() function as...
self.total
```

These properties from [`bpy.props`](https://docs.blender.org/api/5.0/bpy.props.html#module-bpy.props "(in Blender 5.0.1 Python API v5.0)") are handled specially by Blender
when the class is registered so they display as buttons in the user interface.
There are many arguments you can pass to properties to set limits,
change the default and display a tooltip.

This document doesn’t go into details about using other property types.
However, the link above includes examples of more advanced property usage.

#### Menu Item

Add-ons can add to the user interface of existing panels, headers and menus defined in Python.

For this example we’ll add to an existing menu.

[image reference removed]

Menu Identifier.

To find the identifier of a menu, first enable *Python Tooltips* in the preferences.
Then you can hover your mouse over the menu item and the identifier is displayed.

The method used for adding a menu item is to append a draw function into an existing class:

```
def menu_func(self, context):
    self.layout.operator(ObjectCursorArray.bl_idname)

def register():
    bpy.utils.register_class(ObjectCursorArray)
    bpy.types.VIEW3D_MT_object.append(menu_func)
```

For docs on extending menus, see: [`bpy.types.Menu`](https://docs.blender.org/api/5.0/bpy.types.Menu.html#bpy.types.Menu "(in Blender 5.0.1 Python API v5.0)").

#### Keymap

In Blender, add-ons have their own keymaps so as not to interfere with Blender’s built-in keymaps.

In the example below, a new object mode [`bpy.types.KeyMap`](https://docs.blender.org/api/5.0/bpy.types.KeyMap.html#bpy.types.KeyMap "(in Blender 5.0.1 Python API v5.0)") is added,
then a [`bpy.types.KeyMapItem`](https://docs.blender.org/api/5.0/bpy.types.KeyMapItem.html#bpy.types.KeyMapItem "(in Blender 5.0.1 Python API v5.0)") is added to the keymap which references
our newly added operator, using `Shift`-`Ctrl`-`T` as the key shortcut to activate it.

```
# store keymaps here to access after registration
addon_keymaps = []

def register():

    # handle the keymap
    wm = bpy.context.window_manager
    km = wm.keyconfigs.addon.keymaps.new(name='Object Mode', space_type='EMPTY')

    kmi = km.keymap_items.new(ObjectCursorArray.bl_idname, 'T', 'PRESS', ctrl=True, shift=True)
    kmi.properties.total = 4

    addon_keymaps.append((km, kmi))

def unregister():

    # handle the keymap
    for km, kmi in addon_keymaps:
        km.keymap_items.remove(kmi)
    addon_keymaps.clear()
```

Notice how the keymap item can have a `total` setting different than the default set by the operator,
this allows you to have multiple keys accessing the same operator with different settings.

For API documentation on the functions listed above, see:

- [`bpy.types.KeyMaps.new()`](https://docs.blender.org/api/5.0/bpy.types.KeyMaps.html#bpy.types.KeyMaps.new "(in Blender 5.0.1 Python API v5.0)")
- [`bpy.types.KeyMap`](https://docs.blender.org/api/5.0/bpy.types.KeyMap.html#bpy.types.KeyMap "(in Blender 5.0.1 Python API v5.0)")
- [`bpy.types.KeyMapItems.new()`](https://docs.blender.org/api/5.0/bpy.types.KeyMapItems.html#bpy.types.KeyMapItems.new "(in Blender 5.0.1 Python API v5.0)")
- [`bpy.types.KeyMapItem`](https://docs.blender.org/api/5.0/bpy.types.KeyMapItem.html#bpy.types.KeyMapItem "(in Blender 5.0.1 Python API v5.0)")

#### Bringing It All Together

```
bl_info = {
    "name": "Cursor Array",
    "blender": (2, 80, 0),
    "category": "Object",
}

import bpy

class ObjectCursorArray(bpy.types.Operator):
    """Object Cursor Array"""
    bl_idname = "object.cursor_array"
    bl_label = "Cursor Array"
    bl_options = {'REGISTER', 'UNDO'}

    total: bpy.props.IntProperty(name="Steps", default=2, min=1, max=100)

    def execute(self, context):
        scene = context.scene
        cursor = scene.cursor.location
        obj = context.active_object

        for i in range(self.total):
            obj_new = obj.copy()
            scene.collection.objects.link(obj_new)

            factor = i / self.total
            obj_new.location = (obj.location * factor) + (cursor * (1.0 - factor))

        return {'FINISHED'}

def menu_func(self, context):
    self.layout.operator(ObjectCursorArray.bl_idname)

# store keymaps here to access after registration
addon_keymaps = []

def register():
    bpy.utils.register_class(ObjectCursorArray)
    bpy.types.VIEW3D_MT_object.append(menu_func)

    # handle the keymap
    wm = bpy.context.window_manager
    # Note that in background mode (no GUI available), keyconfigs are not available either,
    # so we have to check this to avoid nasty errors in background case.
    kc = wm.keyconfigs.addon
    if kc:
        km = wm.keyconfigs.addon.keymaps.new(name='Object Mode', space_type='EMPTY')
        kmi = km.keymap_items.new(ObjectCursorArray.bl_idname, 'T', 'PRESS', ctrl=True, shift=True)
        kmi.properties.total = 4
        addon_keymaps.append((km, kmi))

def unregister():
    # Note: when unregistering, it's usually good practice to do it in reverse order you registered.
    # Can avoid strange issues like keymap still referring to operators already unregistered...
    # handle the keymap
    for km, kmi in addon_keymaps:
        km.keymap_items.remove(kmi)
    addon_keymaps.clear()

    bpy.utils.unregister_class(ObjectCursorArray)
    bpy.types.VIEW3D_MT_object.remove(menu_func)

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

[image reference removed]

In the menu.

Run the script (or save it and add it through the Preferences like before) and it will appear in the *Object* menu.

[image reference removed]

Operator Property.

After selecting it from the menu, you can choose how many instances of the cube you want create.