Hello and welcome to another epidose of
Scripting for Artists. My name is Sybren.
This episode is about Blender Collections.
These were introduced in Blender 2.8
and replaced the numbered Scene Layers and Groups.
I'm not recording this as usual at the Blender Institute,
but I'm home right now, as so many of you.
As a result, the video will be less scripted
and I won't have any slides to show.
We'll just dive into Blender
and I will show things there.
So in the Outliner here, you can see that
we have a Scene Collection
with Suzanne in it, like one object,
and it has that default collection,
Collection One.
So given that, this thing here is called
the Scene Collection,
let's look at the scene object
and see if we can find a Collection property.
Here we have Suzanne.
As a reminder,
here we have the Convenience Variables.
So capital C stand for "bpy.context"
and capital D stands for :bpy.data".
You can't use these in your own script,
but you can use them here in the console
while you're figuring things out.
Let's take a look at
"C.scene.collection"
then press Tab,
and you can see,
it already has a Collection property.
We can get its name, is called Master Collection
because
it's the main scene collection.
And it has a
property called Objects.
If you convert that to a List,
just arrow up and then type List
with parentheses around the line,
and there you can see that one object,
Suzanne, that was here.
"collection.objects" gives you all the objects
that are directly linked into that Collection.
So, let's see what happens
when we move Suzanne into Collection One.
It's still indirectly inside the Scene Collection
because it's inside Collection One,
and Collection one is inside the Scene Collection,
but it's no longer in the "scene collection.object".
If you want to have really everything,
then look at "all_objects".
This is like a magical view
that will give you everything in the Collection itself.
So it contains everything that Object also contains,
but then it also contains everything that
is inside collections in the collection
and the collection in the collection and....
And as you can see, we have Suzanne.
So as many things in Blender collections
have to have a unique name,
because they are stored in data.
So we have "bpy.data" or in the console
"D.collections",
and this gives us the Collection One.
It does not contain the Scene Master Collection,
but it contains all the other collections.
So here we also can get
"D.collections("Collection 1").objects"
and
convert it to a list to see what's in there.
And again, of course there is Suzanne in there.
So this already gives you a way to,
given the name of a collection,
iterate over all the objects in that Collection.
So maybe you want to write an exporter
that exports
everything in one particular collection,
now you know how to access it.
And I'm saying iterate over the objects,
let's just take a look at how that would happen
just to refresh your memory.
Let's say we have a nice little
Bohemian Rhapsody going on here
with four Suzanne in that one Collection.
If we say
"list collection.objects",
then we get all four of them.
Let's see how it would look like
in a little more complex code.
You would basically say
"coll=D.collections("collection 1")
and then I can say
"for ob in coll.objects:
print("OB name is:(ob.name)")
And this is a format string, it's really useful.
You can just type an F before the opening quotes,
and then within curly braces,
you can write any expression like "ob.name".
And what you can see here,
it lists all the names of all the object.
So it loops with the Forloop
that loops over all the objects in the collection,
and then does something with it.
This could be calling an exporter,
or this could be setting a marterial,
or it could be changing the name,
or whatever you want to do of course.
In our case, it just prints the name.
So let's take a look at creating a collection from Python.
This is also done through the "data.collections" collection.
So
"D.collections",
let's take a look, what do we have, a new function.
So let's type
"D.collections.new" tab,
and you can see that you can type "name",
and now we have a new collection called "demo".
You want to use this collection
as it's given to you, because,
of course, this is now available at the
"bpy.data.collections("demo")".
This happens to the best of us.
Everybody makes typos. Don't worry about it.
Given the name "demo",
we can get the Collection agian.
And this is a big big pitfall that
you could fall into, because you may think that
that Collection you just created
with an m demo
will have the m demo.
Let's take a look at what happens
when we do it again.
Exactly the same function call,
just telling Blender to make me
a new collection called Demo.
But this time, we already have a collection
called Demo. And as I said,
collections have to have a unique name.
So what happens is that, it calls a "demo.001",
actually use from Blender.
But now the name we asked Blender
to give the collection is not the same
as the name the collection was given.
So if you now access
"bpy.data.collection("demo")",
you will see that you get "demo", of course
and not "demo.001".
So what I would say is,
when you create a new collection,
always assign it to a name
like this.
And then you're sure that
you have the right collection.
Afterwards you can change the name,
like "collections.name="sfa""
and it will have the new name.
One thing you may notice in the Outliner
up here,
is that,
the name isn't there,
like the collection is not there yet.
So it exists in the Blend file,
it exists in memory,
but it's not linked to the
Scene Master Collection yet.
So how do we do that?
You go to the Master Collection,
and then "dot and tab" to just get
a list of everything in there.
One thing you'll notice is this property
called Children.
That contains all the Child collection.
That's all the collections
that are linked into this collection.
So, let's take a look there.
There you find a function called Link.
At this collection as a child of this collection,
it's a bit cryptic, two times this collection
referring to different things,
but I think you get the gist.
We had our collection in the name Collection.
So keep your eyes on the Outliner
while I press enter. There we go.
And now it is part of the Scene Collection.
Linking objects into this new collection
is pretty much the same thing
as linking other collections into it.
Instead of using ".children.link",
you used ".object.link".
So let's take a look. We have our collection.
It has
an empty set of objects.
Nothing is in there.
And we can use ".object.Link"
to link an object to it.
And like that,
we can add Suzanne.003 to it.
Let's put this into a bit more of a script.
So I'm going to subdivide here,
change to Text Editor,
creat your script, call it
"move stuff.py",
always start with "bpy".
Now let's say we want to move
everything that is inside one collection
and move it into another.
So we have to have a
"coll_from=bpy.data.collections("collection 1")"
and we have something similar,
"coll_to=bpy.data.collections("SFA")".
So now we have our two collections,
let's loop over 1 and then add all the objects
that we find into the other.
So "for ob in coll_from.object:
coll_to.objects.Link(ob)",
and this will already
link all the objects from one collection
into the other.
What's left to move, is to unlink
from the other collection.
So we need to unlink from "coll from".
but that also means removing that object
from "coll_from.objects".
So that means that by unlinking,
we're changing that thing
that we're looping over currently.
This is not a good idea.
You shouldn't do this.
At best it will be unpredictable.
At worst it will crash Blender,
and you will lose your data.
The most simple solution for this,
there's many of them, but the simplest
is to keep track of the objects
that you want to unlink.
So let's say to unlink is an empty list.
And in this list, we will keep track
of whatever we want to unlink later.
So we can say,
"to unlink.append(ob)"
And once this Forloop is done,
we can
unloop over this and unlink everything.
So "for ob in to unlink",
"coll_from.objects.unlink(ob)",
and this will work.
Let's give it a try.
My script field, of course,
let's take a look at the console.
If we look at the terminal
from which I started Blender,
you can see that it gives me a runtime error,
Objects Suzanne .003
already in collection SFA.
Fair enough.
It doesn't like us linking an object
that is already in there.
There's basically two ways
to avoid your script breaking
because of an error like this.
One is to avoid the error to begin with,
the other is to handle the error when it occurs.
I think in this case,
it's easiest to just catch the error and make sure
our script keeps running when that happens.
The error says that line #8 is the problem.
So let's take a look at line #8.
So line 8 is this line in the code.
It's delinking itself,
of course, that goes wrong.
You can tell Python to
give this line a try and see what happens.
Literally you say try,
and then a colon, and like the "Forloop",
and the "If",and the other things we've seen,
and in a column,
you have to indent the rest.
That indicate what it needs to try.
And then when we look at the terminal,
we saw the
runtime error
that is right over here.
That is the type of the exception.
I won't go into detail as to what it all means.
Just know that
you have to type it here.
So that Python knows that,
if an exception occurs in this try block,
of this type,
then execute this code over here.
And in our case, this is fine.
This happens
when the object is already in the collection,
which means that we're happy
because we wanted to link it there, it already is there,
so it is all fine, so we can let this pass,
which literally means just type "pass" there.
This tells Python,
don't do anything here, ignore the error,
nothing happened, really.
The next line is,
of course, still important.
Because we still want to handle this
althuogh the object moved.
The linking went bad because it was already there.
It still needs to be unlinked
from the original collection,
so we can just keep going.
And this should fix our code.
Let's give it a try.
And there we have it,
all the objects have been
moved into SFA,
have been taken away from Collection.
And of course, we can do the opposite.
We can say,
flip the names around, run the script again.
And they're back again
where they started.
So this is how you can move objects
between Collections.
One final thing we could do,
which maybe is a little bit useless,
Bbt just to give a demo.
We can also remove the collection
that we moved everything from.
That would be
"bpy.data.collections.remove(coll_from)",
let's flip them around again.
We moved all the objects from collection 1 to SFA.
And then once that is done, we remove the Collection.
And there we go.
Now Collection 1 is gone.
All the objects have been moved again.
And that's how you work with collections
from Python.
To end this episode,
let's take another look at the Outliner.
There are a few toggles you can set,
like the Restriction toggles.
Let's take a look. If I want to change
the viewport rendering of this collection,
you can see in the Points and Tooltip
which you can enable in the User Preferences,
can see that
"bpy.data.collection.SFA.hide viewport"
is that thing to go to.
Let's take a look there.
We take "bpy.data.collections(SFA).hide)",
there we already have,
Hide Render, Hide Select, Hide Viewport.
And you can just set them to "True",
back to "False",
and you can play around with this.
And it pretty much does what you would expect
from clicking on the icons.
So that's it.
If you have any questions, leave comments.
I'll see you in the next episode.