0
$\begingroup$

Is there a way to store objects in a list and change their properties? I want to make a for loop and move objects one-by-one using this loop. My attempt so far is below but I get the error KeyError: 'bpy_prop_collection[key]: key "FINISHED" not found'.

import bpy
from random import randint

#how many cubes you want to add
count = 3
listOfCubes = []
Cubes = bpy.data.collections.new("Cubes")
bpy.context.scene.collection.children.link(Cubes)

for i in range(count):
    x = randint(-10,10)
    y = randint(-10,10)
    z = randint(-10,10)
    listOfCubes.extend(bpy.ops.mesh.primitive_cube_add(location=(x,y,z)))
    bpy.data.objects[listOfCubes[i]].location.x = 0
$\endgroup$
1
  • $\begingroup$ The return value of the operator is not a reference to the object. Instead check the active object to get that reference. Is there a particular reason why you don't set the x location right away when creating the object? You may also want to move the created object into the new collection. $\endgroup$ Commented Sep 28, 2019 at 22:15

1 Answer 1

2
$\begingroup$

The operator bpy.ops.mesh.primitive_cube_add() returns the status of the execution and not a reference to the created object. It does set the newly created object as active though, which means it can be accessed by using bpy.context.active_object.

Since you already know that you want the location on the x-axis to be zero, you can set this right when the object is created instead of changing it later.

In your current code the objects are not linked to the newly created collection. You can add an object to a collection by using collection.objects.link() where collection is an object of type bpy.types.Collection. You can unlink the cubes from the currently active collection where it was originally added to, if you don't want them to appear in both collections.

import bpy
from random import randint

num_cubes = 3
master_collection = bpy.context.scene.collection
collection = bpy.data.collections.new("Cubes")
master_collection.children.link(collection)

for i in range(num_cubes):
    y = randint(-10, 10)
    z = randint(-10, 10)
    bpy.ops.mesh.primitive_cube_add(location=(0, y, z))
    active_object = bpy.context.active_object

    # Link to the newly created "Cube" collection 
    collection.objects.link(active_object)

    # Unlink from the active collection in the view layer 
    # (otherwise it will appear in both collections)
    bpy.context.view_layer.active_layer_collection.collection.objects.unlink(active_object)

If the changing of the location was just meant as an example and you do need the objects in a list, you can store them at each iteration through the loop:

import bpy
from random import randint

num_cubes = 3
master_collection = bpy.context.scene.collection
collection = bpy.data.collections.new("Cubes")
master_collection.children.link(collection)

cube_list = []

for i in range(num_cubes):
    y = randint(-10, 10)
    z = randint(-10, 10)
    bpy.ops.mesh.primitive_cube_add(location=(0, y, z))
    active_object = bpy.context.active_object

    # Link to the newly created "Cube" collection 
    collection.objects.link(active_object)

    # Unlink from the active collection in the view layer 
    # (otherwise it will appear in both collections)
    bpy.context.view_layer.active_layer_collection.collection.objects.unlink(active_object)

    cube_list.append(active_object)

Alternatively you can retrieve them from the Cubes collection by using:

collection.all_objects()

This assumes that you still have the collection variable in scope with the reference to the collection. Otherwise you will have to access it through:

bpy.context.scene.collection.children["Cube"].all_objects

The latter two examples don't give you a list, but also an object which is iterable of type bpy.types.bpy_prop_collection. Therefore it can still be used in a loop similar to how you would iterate through a list.

import bpy

objs = bpy.context.scene.collection.children["Cube"].all_objects

for obj in objs:
    print(obj.name)

Be aware though that when creating multiple collections with the same name, Blender will automatically append a number to avoid duplicate names. Which means if you're executing the script multiple times, you will have collections called "Cubes", "Cubes.001", "Cubes.002", "Cubes.003" etc. If you want to avoid this and only have one "Cubes" collection, you will have to check if it exists before creating a new collection:

import bpy

name = "Cubes"
master_collection = bpy.context.scene.collection
collection = bpy.data.collections.get(name)

if collection is None:
    collection = bpy.data.collections.new(name)

if master_collection.children.get(name) is None:
    master_collection.children.link(collection)
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .