Hello and welcome to Scripting for Artists. My name is Sybren.
And in this episode, I will show you how to create
your own operator.
So far, we only created snippets of code, you can use them,
but they're not the most user friendly.
Many people have asked ways to create menus and buttons and custom panels and stuff.
In this episode, we will not look at that yet.
But we will look at a necessary first Step, which is,
creating your first operator.
We will look at what an operator is exactly, then,
we will see how to create your own operator and how to make it
more flexible by passing parameters to it. And then,
we conclude by limiting the operator to specific context.
An operator is the glue between menus, buttons, and hotkeys,
and the code that should be executed by them. In the first episode,
we already worked with operators by copy pasting them
from menus and buttons into our own script. Operators can be implemented
in Blender itself in the C programming language.
Or, they can, of course, be written in Python.
No matter how they're made, they all have the same ingredients.
First is an identifier. So this is what you would put after the "bpy.ops",
so in this Operator, for example, it's far from complete,
but we're building it up. So in this example, it would be
"bpy.ops.sfa.example". These are always in the form
"some category.a name". Best pick a category
that already exist, like object or mesh or sculpt
or whatever is suitable for your operator. But as you can see,
you can also create your own. Then, we have a label.
This is what appears on the menu items and buttons,
and a description that is shown as Tool Tip. Then, everything is combined
into a class. And this is something that we'll look at in a minute.
For now, just think of it as some pot in which
we gather all the ingredients of our operator. You can see that
the class is named after the operator. So we have the category
in capitals, then OT for the operator type, and then
the rest of the identifier. So far, we've seen description,
but not actual code yet. And this can be added in different ways
for different purposes. So here are the possibilities
from simple to complex. You have "Execute", this is where you put
the main functionality of the code. If you want to create 600 monkeys,
then this is the place to do it. The Execute function is also
what gets called from Python directly. So if you call an operator,
like "bpy.something.something"
that's the function that gets called. Then we have "Invoke",
this function is called when the operator is called for a menu
or a button. If you want to do something extra, in that case,
you want to have slightly different behavior, then you
can do that in Invoke and you then call your Execute function from there.
And now, we get to the most complex, the Modal one.
The Modal function is for special operators that need to run
for a longer period of time. This receives all kinds of events
from Blender, from when you move your mouse,
you click on buttons, you use the keyboard, that kind of stuff.
Things like the Blender Cloud, texture browser, or CG cougars,
Retopo, Float, Implemented, like this. If after all of these,
you are in doubt about which function to use, keep it simple,
just use the Execute function. If that turns out not to to be able to do
what you want to do, then you have a concrete reason
to look at something more complex. But before that,
just keep things simple.
Now, let's turn this into an actual operator that we can use.
Let's create some more monkeys.
Let's start by removing what we don't need. Let's keep it simple,
just an Execute function. If you look at the Mesh Operators,
you can see that, they start with "bpy.ops mesh primitive monkey add",
or "bpy.ops mesh primitive ico sphere add"
So all the Operators that create a new mesh object,
they start with "mesh". So let's keep doing that.
So the ID name becomes "mesh" and we're going to create a monkey grid.
Monkey Grid, label "monkey grid", the Tool Tip will be
"let's spread some joy"
and then the class name, we have to change, that becomes the category
"mesh_ot_monkey_grid".
The Execute function has to do something,
but it also has to tell Blender whether everything succeeded or not.
So everything was alright, you return "finished"
and that tells Blender that the code actually performed well,
but maybe your Operator cannot work, maybe it is for extruding a mesh
and the mesh doesn't have any vertices.
So in that case, you return "cancelled". There are a few other options
and they are all described in the menu. To go there,
you go to Help and then Python API Reference, and then
to the section about "bpy.ops". So let's assume that
everything will work correctly,
we will return "finished". And now, all we have to do is
put the code here that creates our monkey grid.
So let's copy the code from Chapter 3 of Scripting for Artists
and put it here. So this creates our 600 monkeys in rows of 25,
having one Blender Unit between them.
The Operators almost done already. So we have all the ingredients here.
The only thing that we need to do now is
tell Blender that this operator exists and this is called Registration.
Fortunately, this is ather simple, we just call
"bpy.utils.register_class" and then the name of the class,
in our case, "mesh OT monkey grid". For symmetry, there should also be
a Unregister function. So that looks like this.
Instead of calling "register class", it's called "unregister class".
Basically it undoes what the register function was doing, and then,
we're almost there. It's just that, as you know, when you have
a function like this, it's not automatically called when you run the script.
There's a little trick for this.
If you add some magic like this in here, and it looks a bit weird.
But basically what this means is that, if this script is run,
then it should call the Register function.
There's multiple ways in which Blender can access your script,
it can execute it, but it can also be imported from something else,
just like we import BPY, other code can import this file as well.
So there's a distinction between being imported from somewhere else,
and being run. And only when it's being run,
should it called the Register function, and that is what this code does.
So now, what we can do, let's just name this "monkey_grid.py",
and then let's click play.
And nothing happened, which is kind of a good thing.
We didn't see any error message, our code doesn't show any message.
So silence is kind of good here.
So, let's see what happens now,
"bpy.ops.mesh.monkey_grid",
there we go, we can create a monkey grid.
The operator is there. Let's spread some joy. Let's not call it here.
Let's see if we can find our new operator here in the F3 menu
with a monkey and there's your monkey grid. So that's really cool.
Press "enter" to execute it. And as you can see,
Blender now freezes one is creating the 600 monkeys.
And this is what happens when you do something that takes time
in the Execute function, and that is okay. The code is part of
this Blend file. So, let's see what happens when we create a new Blend file.
As you can see, the operator is still there.
Has been loaded into memory of Blender. It's been registered
and it just keeps taking around. Now, let's see what happens
if we restart Blender.
And now, the monkey grid operator is no longer there.
This is because, Blender doesn't just execute all the scripts
that it can find in a Blend file, you have to tell it to do that first.
You have to make sure that the name of the text file
ends in ".py", and then here in the Text menu,
you can check the Registered checkbox.
Now, let's save, restart Blender,
and now it's there. So let's have some fun and add some parameters.
When you add a cube, what you see here on the left
is called the Redo panel. When you open it, you see different parameters
which you can set. So what happens is that,
when you change the parameters value, Blender pushes the Undo button
automatically, and then re-execute the operator.
That is why it's called the Redo panel, because it redoing things
all the time. So let's add support for the Redo panel to our operator.
We start by declaring the properties we want to have.
This will be the number of monkeys in the X direction,
number of monkeys in the Y direction, and monkey size.
Just like operators live in "bpy.ops",
the properties live in "bpy.props". So again, if you want
to have more information, just go to Help, Python, API Reference,
and then click on the "bpy.props" section. I will show you
what it looks like. Let's call it "count x: bpy.props.IntProperty".
IN stands for integer, which means a whole number,
because we cannot have half monkey. So count X has to be either one or two or three,
but can never be like 2.5 or something. So it's whole numbers only,
and this declares that we want to have that property.
There is a big difference between the ":" we have here,
and the "=" sign we have there.
This sets a name to a value, and this is for things
that just have to be set. We have to set "BL ID name" to match
"monkey grid" to make it work. But here we don't really set anything.
We just declare to Blender, this is an IN property,
please act accordingly.
You can add some parameters here to change behavior.
First of all, we should set a name.
We can call it X. It may be a bit cryptic,
but for now, will do.
And we can add a description,
"number of monkeys in the X direction".
So let's copy this to the Y.
name this Y and "number of monkeys in the Y direction", and whatever size,
which is another "bpy.props.FloatProperty".
Float stands for floating point,
is a technical term for just decimal numbers.
"Name = size", and "Description = size of each monkey",
this gives us our properties, but they're not used yet. So let's take a look
at the Execute function where we're supposed to use them.
This comment, it has to go, because we no longer creating 600 monkeys
in rows of 25, but it's all flexible, so let's remove that for now.
Now we have to access the properties. So we've declared them
and this "self" gives us access to the actual value.
"Self" is a point thing and I won't go into detail here
about what it represents. Just know that, if you declare
a property here as count X, then you can access it here as "self.count X".
So the total number of monkeys
is the number of monkeys in the X and in the Y direction,
and this 25 here was the number of monkeys in the X direction.
And then, all that is left is to set a size, and that's it.
Let's give it a try.
Let's see what happens in the Console.
There you can see our Properties, count X, count Y, size,
and they're all equal to 0, because we haven't set any default yet.
And there, we have our three monkeys.
Before we actually call it from the Viewport,
let's set some sensible defaults.
Default is 3.
For the Y, default is 2, I choose different values there on purpose.
This is default, so when somebody clicks on monkey grid button,
or select it from the F3 list",
this will be what they see. Because they see a 3x2 grid,
they immediately know what they need to change.
If they want to change the 3 to a 4, that just change the 3 to a 4,
without having to think about whether it's the X or the Y direction.
So if we were to give them the same default,
then they wouldn't be able to see it. And then, they would have the thing
like which direction is X, which direction is Y,
which value do I want to change. And this is why I give them distinct values.
Finally, default for the size, run again,
delete everything.
And there, you can see your defaults. And when we execute
without any values, it just uses those default.
And the same for the F3 menu. There's one more step left,
and that is, to get that Redo panel. We have the properties,
so Blender knows what should hypothetically go in there,
it just doesn't know yet that we support that Redo panel.
So let's tell Blender that we do.
This is done through "bl options". The default value is "register",
and we have to add "undo" to it. This will give us the final ingredient.
There you go.
Now we can make them big and small, and give them even invalid values.
So to make it pleasant to use for people,
we should really set limits to the slider, so that,
it's all sensible values that you can select there.
Fortunately, this is quite simple.
We can set a minimum. Let's set it to 1 because creating 0 monkey
doesn't make sense. And creating negative monkeys even less.
So let's start at 1, let's set the maximum to 10 for now.
We can do the same for the count Y,
and we can set the size 0 to minimum, and the maximum,
let's set it to 1.
And now, we can drag and things are still nice and sane.
You can see that, it already gets little bit slower
when these numbers increase. And we still can't get to the 600 we had before,
even when I type 100 here, it is cut off to the maximum.
So this is why Blender has a soft limit.
The hard limit is what we set now, that's the absolute limit
of the value of the property. Whereas the soft limit is
what the slider will do when you drag around.
So if you want to allow maximum values that are bigger than 10,
or sizes that are bigger than 1, but still want these limits
as the limit for the slider, you can change the maximum into a soft maximum.
The minimum that we have now really is a minimum,
because creating 0 monkeys doesn't make sense,
negative monkeys is impossible, also, creating a negatively sized monkey
is impossible. So let's change the maximum to soft.
Now the dragging is still the same, but we can set it to create
much more monkeys than you would get with dragging.
Now, let's get back to that class declaration.
I'll explain a little bit more about how classes work in an inituitive way,
I hope, without going too much into technical details.
Classes use a system called inheritance,
just like with people, it's like a parent-child structure.
So in this case, "bpy.types.operator" is the parent, and "mesh OT monkey grid"
access to the child. If you ask a child a question that
you know the answer to, say you want to have a cookie,
the child will say yes immediately without asking the parent, of course.
But if you ask about taxes, then "Mom, please help me out here,
because I don't know anything!" and then the child defers
that question to the parent. This is the same way
that Classes work in Python, roughly.
We could do without the "bl" options. Our child didn't know
anything about it, so Python asked the parent.
And when we want to have something different,
we can add "bl" options to our child, to our class. And then, that is asked first,
so that takes precedence over whatever the parents wants.
So we always have lucky children who gets all the cookies they want.
So before I end this video, I have two more things
I want to tell you. One, again, we look at the Execute function.
You see that it gets a Context Parameter and this is pretty much like
the context we've seen before. It gives us "context. object",
"context.scene", "context.selected object",
all these things you've seen before in "bpy.context",
it gets here as well, but then, localized specifically for this operator.
So as a general rule of thumb,
if you have a function that gets a Context, use that Context,
don't use "bpy.context. So if you want to do something
with all the selected pose bones in this Execute function,
you would use "context.selected pose bone".
Another function and that is the second thing that I wanted to show you,
another function that gets a Context is the Poll function,
and it's written in a bit weird way, but just take it from me,
it works like that.
Again, you get a Context in this function. But instead of
doing something like, instead of performing an action,
this function is meant to check whether that action will be possible.
So this Poll function is called by Blender
every time this Operator is supposed to show up in a menu,
or as a button on a panel. I can show you how this works.
Right now, returns "true", which means that it is always allowed,
just as it was before. And just as it was before,
it shows up here in the F3 menu.
When I change it to "false",
it will never show up anywhere, which is kind of useless.
Let's tweak this to make it a bit more useful.
You can see here, in the Console,
we have Autocomplete, which makes sense in a Python Console.
But that same operator does not work
in the 3D Viewport, because the context is wrong. This is also something
we can do ourselves. Right now, when I change this to "true" again,
the monkey grid operator also shows up in the context of the Python comes on.
And it will show up anywhere in Blender,
which is not exactly the right way to go. Let's take another look
at a variable inside that context.
"Context.area" gives you the area that is currently active
in Blender User Interface. So in this case, it would be the Console.
But if the mouse is here, and we pull up the 3 menus,
then all of a sudden, its 3D Viewport that is active, and here,
would be the Outliner. So every area in Blender has its own area type
and we can use that in our Poll function to make sure that
it's only working in the area
where we wanted to work. One thing I don't really like is guesswork.
So we know now that the Area Type of the console is Console,
but is it in 3D Viewport? Is it "3D View"? "3D_view", is it "3D Viewport"?
I don't know, so let's make our script tell us instead.
I can just say "print my area is context.area.type"
And let's see what happens on the terminal where all these prints
are sent to.
Here, you seen the console, and immediately, you see it being called
"My area is view 3D". But when I do it here in the Outliner,
my area is Outliner. If I do it here in the Properties panel,
my area is Properties. So that way, you can just use these print statements
to know what you have to put where.
So now, we know which value to use for the 3D Viewport,
so let's just do that.
If "context.area.type=view 3D" then return "true",
otherwise, we can return "false". Let's see how this works.
Here, it still shows up.
But here is no longer there and, of course, here and there,
now it only shows up where we want it to show up. One final thing,
this kind of construct, I see them a lot, also from experienced programmers,
it's more complex than necessary.
Because this guy is already an expression that is true or false.
So basically, what the code is saying is that
if the value of this thing, this selected expression,
if the value of that thing is true, return true, and if the value
of that thing is false, return false. So that basically means
return the value of this thing, which you can of course write
as this.
So that was it for this episode of Scripting for Artists.
If you have any questions or remarks, please leave a comment below,
and I will see you soon.