How to programmatically add empties?

Hi, I want to write a plugin that allows me to procedurally generate a world based on a graph of nodes with xyz positions (i.e. can be place in a scene) as well as some attached metadata (biome information, references to connected nodes, etc), but am already failing with how to model/implement that node graph. The best recommendation I got on IRC was using empties for the nodes, but when I try adding an empty to a scene, I get RuntimeError: Error: ID type 'OBJECT' is not valid for an object.

My code currently looks like this:

    bl_info = {
        "name": "collator",
        "author": "phryk",
        "version": (0, 0),
        "blender": (2, 80, 0),
        "location": "View3D > Add > Mesh > New PoI",
        "description": "Adds a new Point of Interest",
        "warning": "",
        "doc_url": "",
        "category": "Add Mesh",
    }
    ​
    ​
    import bpy
    from bpy.types import Operator
    from bpy.props import FloatVectorProperty
    from bpy_extras.object_utils import AddObjectHelper, object_data_add
    from mathutils import Vector
    ​
    ​
    def add_object(self, context):
    ​
        poi = bpy.data.objects.new("New Point of Interest", None)
        poi.empty_display_type = 'PLAIN_AXES'
        poi.location = Vector([0,0,0])
        print(dir(poi))
        object_data_add(context, poi, operator=self)
    ​
    ​
    class OBJECT_OT_add_object(Operator, AddObjectHelper):
        """Create a new Point of Interest"""
        bl_idname = "mesh.add_poi"
        bl_label = "Add Point of Interest"
        bl_options = {'REGISTER', 'UNDO'}
    ​
        scale: FloatVectorProperty(
            name="scale",
            default=(1.0, 1.0, 1.0),
            subtype='TRANSLATION',
            description="scaling",
        )
    ​
        def execute(self, context):
    ​
            add_object(self, context)
    ​
            return {'FINISHED'}
    ​
    ​
    # Registration
    ​
    def add_object_button(self, context):
        self.layout.operator(
            OBJECT_OT_add_object.bl_idname,
            text="Point of Interest",
            icon='PLUGIN')
    ​
    ​
    # This allows you to right click on a button and link to documentation
    def add_object_manual_map():
        url_manual_prefix = "https://docs.blender.org/manual/en/latest/"
        url_manual_mapping = (
            ("bpy.ops.mesh.add_object", "scene_layout/object/types.html"),
        )
        return url_manual_prefix, url_manual_mapping
    ​
    ​
    def register():
        bpy.utils.register_class(OBJECT_OT_add_object)
        bpy.utils.register_manual_map(add_object_manual_map)
        bpy.types.VIEW3D_MT_mesh_add.append(add_object_button)
    ​
    ​
    def unregister():
        bpy.utils.unregister_class(OBJECT_OT_add_object)
        bpy.utils.unregister_manual_map(add_object_manual_map)
        bpy.types.VIEW3D_MT_mesh_add.remove(add_object_button)
    ​
    ​
    if __name__ == "__main__":
        register()

And the generated traceback like this:

    Python: Traceback (most recent call last):
      File "/home/phryk/devel/gfx/blender/poi.blend/Text", line 45, in execute
      File "/home/phryk/devel/gfx/blender/poi.blend/Text", line 27, in add_object
      File "/usr/local/share/blender/2.91/scripts/modules/bpy_extras/object_utils.py", line 127, in object_data_add
        obj_new = bpy.data.objects.new(name, obdata)
    RuntimeError: Error: ID type 'OBJECT' is not valid for an object
    ​
    ​
    location: <unknown location>:-1

I’m not sure I’m interpreting this right – does this mean adding an empty just isn’t valid? If so, what would be a good fit for an object type to use instead?

bpy.ops.object.empty_add() adds an empty at the current 3d-cursor, bpy.ops.object.empty_add(location=(1,1,1)) adds another one at (1,1,1).

Just try it in the Scripting workspace via the Console.

empty = bpy.context.object
empty.name = "ThatsMyEmpty"

renames the last one added to “ThatsMyEmpty”.