 All right, let's get started. Good morning, everybody. My name is Sebrin, and this is Scripting for Artists. It's actually a training course on Blender Cloud. How many people are Blender Cloud subscribers? Oh, that's so good. Thank you all, because you're paying my salary, among other things, so it's really great. Who have you have seen the Training for Artists course that actually exists on the cloud? A few people who have looked at it, a few videos. All right, well, that's good. I'm trying to cover a bit of the training as it is on the cloud and then skip some parts to keep you guys interested in what else is on there and also cover some things that people asked for but are not in the cloud stuff yet. It's aimed at artists who not necessarily want to become a programmer, but from Scripting could still be very useful. If you do any mundane monkey work that is doing the same thing over and over again, then Scripting could be for you. So basically, it starts with scratching your own itch. It doesn't have to be production ready. It doesn't have to be very fancy. As long as it works for you, that's fine, and you can already save a lot of time. So basically, it is getting to get Blender to press its own buttons. That's the whole goal. We're going to use Python for that, which is a programming language that Blender is using. So in the end, you will be a Blender programmer. Little bit about me. I got a PhD on crowd simulation before I started working at the Blender Institute Animation Studio thingy. I've been working in there since 2016. I got my first commit rights to Blender in 2014, and I've been programming stuff since 91. Then there is two very important things. This is a workshop, so it's not about me talking for an hour and then you guys just absorbing. So please interrupt me. I will try to keep my mouth shut every once in a while to get so that you have some space to think. But if I don't do that enough, just interrupt me and ask stupid questions. Even though, if it is, dude, I have no idea what you're saying right now. Just interrupt me, ask about it. We can discuss it. Maybe somebody else in the audience knows how to explain what I'm trying to explain in a way that clicks with you. So that's what I wanted to say about that. One thing to realize when you start programming computers is that they are really, really stupid. They will just do as they're being told. Princeton is called an imperative language, which means that you tell the computer, do this, then do this. To give an example, Prolog is a programming language that is more logical, so you can actually describe your problem and then tell the computer, please, computer, answer me this question. And it's awesome, but it doesn't work for Python. So you really have to lay out the steps it needs to do. One example is that I've seen people say, OK, A equals x location of this object. And then they start changing the location of the object and they assume that the computer understands that A should always symbolically mean the x location of that object. But it doesn't happen that way. Computer just sees A equals location plus five, maybe. And it will compute that. It says 10, OK, A is 10. That's it. And then from then on, A is always 10. So it's a different way of thinking about things. As I said, it's a workshop. So if you have a laptop and you want to type along, please do. If you have any of your own scripts that you want to discuss, could also be cool. So roughly the outline of what we're going to talk about. Going to look at mindless copy pasting. Blender is really good at that. You can just copy paste stuff from the other interface into your script or the work. So that's pretty awesome. Then we're going to look a little bit more in depth about names and objects because that plays a critical role in programming these things. And then we'll look at stuff on this because that's usually what you do. I have this list of 1,000 objects that I want to look through and do things with. So by then we'll be able to do that. Then on the cloud, I look at data types, strings, numbers, meshes, that kind of stuff, and collections, lists, dictionaries, sets. I keep those as a teaser for you guys. And then we get to stuff that I don't cover on the cloud, which is don't mindlessly copy paste. And I will explain why. Turning your code into an add-on, people have asked me that a lot. Oh, cool. You're explaining scripting. So you're going to tell us how are you making an add-on? No, that's not on there. So we'll cover that here. And I also got some questions about the production of the actual videos. I have my face in there on top of Blender, and it can be hidden, and shown, and I can cut it. And I also use scripting for that, and some drivers, and some Blender magic to get it all working. So maybe we'll get to this. I have no idea about the timing of all of this because that depends a lot on you guys, how much you let me talk, and how much you do the talking yourself, how many questions we get, how many discussion we can get. So I really don't mind if we only cover the first three bits. That's all fine. Maybe we cover everything. We'll just see where we end up. So mindlessly copy-pacing. That is pretty much the basic where you want to start. Oh, by the way, how many of you have actual Python knowledge? Oh, that's quite a lot. How many of you say, OK, I don't know how to program stuff, and I want to learn? That's also good. Nice. That's a nice mixture. So for the Python guys, I'm sorry. The first few slides will be familiar. So the Python console, I will show you in Blender itself. I was hoping I could get a mirror of what is shown there on here, but I can't. So I'm going to have to do it like this, and apparently maximizing, there we go. Is the interface size big enough? Can you guys read what's down here? OK, if I say 1 plus 2, can you guys read it's 3? There we go. So you can do math. 3 times 4 is 12, and 3 plus 4 times 5 equals 23, because multiplication goes before addition. Python is smart like that. If you want to do 3 plus 4 first, and then the times 5, you have to use the brackets, and then you get 35. Usually, I don't even grab a calculator when I'm on my desktop. I just start Python and type everything there. One thing that took me a long time, I wanted to repeat a string. Just give me, I want to print a little header that's like 50 stars, or 25 As, and I was looking for the string type and functions on it. It doesn't exist, but you can just do 5 times A, and then you get 5 As. So let's just stick to Blender here for a moment. If you have this cube, yeah, this is a bit awkward, you can hover there. And in the tooltip, you can see that there is a little bit of Python. If you don't see it, you can turn it on in your user preferences. And that bit of Python gives you a lot of information that you need. From hard, I hope it's right. I can't read it. It's bpi.data.objects, square bracket, quote, cube, quote, square bracket, dot location, dot square bracket, something, yeah, 0, because I'm on the x. And that is actually the Python code that you can type in to get the location of the cube, which is 0 at the moment because it's default cube at 0. And we can move it on the x-axis and then it's changed. So that's how you can get the x-location of the cube. Instead of square bracket 0, 1 is the y, and then 2 is the z. You can also do dot x, dot y, or dot z. And you can also get the location, which is the three numbers. So if you want to do anything based on the location of the object, if it's in a certain area or in your scene and you want to hide it or show it, this is what you could use. Yes? Oh, yeah, sorry. Usually, this is where the timeline is, but because I'm not doing animation at the moment. But this one, I cheated because I copied pasted it off the slide that I have here in front of me. This one I typed in because these are actual input fields. And if you copy an input field, then it will copy the value. The mindless copy pasting comes a little bit later when we go and look at things in menu, some buttons, and everything because you can copy-paste those as well. Sorry? How does one paste things? Yeah, because I'm used to using TAB on a shell prompt and everything. So I've rebound my TAB key. Otherwise, it just jumps that way. But control space is the default for the autocomplete. So you can type epi.data.controlspace, and then look, that's everything you can type after that. So all the objects live in .objects. I only have one, so it autocompletes to a cube. You may have noticed that I was using the double quotes whereas Blender is now using the single quotes. Doesn't matter as long as you're consistent. If you want to do, it's me, you put in double quotes because then you have a single quote in your text. If you want to have, he said hi, you would use single quotes because then you have double quotes in your text. Doesn't really matter which one. So we have the cube there. You can use arrow up and down to go back and forth in history. It says a lot of typing. It has a location property. There we go, enter dot x. And if we go back here, you can see that you can also change it. You can say that equals 1. And then immediately the cube jumps to location 1. So you could use this to, for example, align objects to the grid or to anything that you want to. Maybe you've calculated where something has to be. Then you can just put it there. You can say, y plus equals, that means to increment 0.1. And then we just walk. Or you can say, well, I want it at 1, 2, 0.3 equals. And then you construct what's called a tuple. It's a thingy that has multiple thingies in it. That's about as generic as you can get. And Blender understands that a tuple of three numbers will match with that location. So then it just puts the cube there. As for the mindless copy-pacing, let's do some of that. So we can say, add mesh cube. Oh, let's try another one. Eichelsphere. Then you see here, I won't use the mouse to point it out because then the thing moves again. But here you see, bpi.ops of mesh of primitive Eichelsphere add. And this is what we call an operator, which is why it lives in the bpi.ops namespace. And this is the code that is actually called when you press that menu item. Hit Control C. And here in your code, Control V, there you have it. You press Enter. And then at the 3D cursor, as you're used to, you get that object. Yes. Yeah. Oh, yeah, by the way, if you have questions and you want them to be on the live stream and on the recorded video, you have to use this one. Otherwise, I will just repeat the question because that's a little bit easier. So the question was, do you just hover and then press Control C? Yeah, that's all there is to it. Oh, by the way, this is also true with these guys. I used to click on it, and then everything is selected. Then I click Control C. You don't have to, you can just hover over any input, and then Control C will also work. Control V for paste will also work. So this is already pretty cool. We can now create a primitive. I didn't put this in my slides, but there is a, I'll cover that later. You can also set the location of the 3D cursor so you can control where it goes. You can also pass it a location here as a parameter. There's a lot of things you can add between these brackets to specify exactly what you want to have. So these options that you get in the user interface for how many subdivisions and the location and the rotation and all that align with you, all these things are actually options that you can get here. So you do an opening bracket, and then again you click Control Space, and then you see, well, we have view align equals false. Well, that's the default. It doesn't align with your view by default. If you say view align equals true, it does. But probably more useful is the number of subdivisions, the size, the rotation is in there, the location is in there. So if you want to create 300 different icospheres of a certain size at specific locations, then it's fairly easy to do. Yeah? Oh, yeah, I can. I can do it now if you want. I can also do it when it's in the stuff on list. Well, probably have it there. No, let's just do it. So yeah, that's easy to do. Let's move back here. So this is what we've just seen. So where do you find this information? There is, the primary resource for me is still the blender user interface. You can go to the manual, but then you have to know that maybe Suzanne internally is called a monkey, or maybe it was the opposite. I always forget. Whereas in the user interface, you can just copy paste the stuff. So that is a good source. There's also Blender's API documentation on docstopblender.org. And the Python standard library is also very well documented. So there's a lot of things in Python itself that you can use in your scripts. And the Blender developers really try to keep the Python that is bundled with Blender as standard as possible so that you can take any Python tutorial and apply it to Blender and it will respond in the same way. Of course, generic Python tutorial will not cover Blender specific stuff, but at least Blender won't get in your way after a Python specific stuff. So to recap, computers are stupid, really important. We've seen how to use the Python console in Blender. We've seen how to copy paste stuff and we've seen some sources of information. We've used the console for calculating stuff and for modifying the scene. Any questions about this so far? Yes. Ooh, is it possible to link iPython to this Python? I have no experience with that. I know, I would love it. This Python console, especially when it comes to history, like what the arrows up and down do, it's slightly different from what Python does. And the iPython now to complete is really powerful and there's lots more going on there. So I would love that, but I wouldn't know what it entails. Might be interesting, maybe. Who knows, in 2.8. Okay, so let's move on to the next chapter, names and objects. Or what does that dot actually mean? We were talking here about names, like Python names and Python objects. And in that sense, names refer to objects. Objects is a thing in memory from Python's perspective. So this is, as a clarification of what's on the cloud now, here I really talk about Python names and Python objects. So an object in Python can be anything. It can be five, digit, number, it's an integer. And it's actually an object in Python's memory. But it can also be the big prime module. It's all of Blender composed of many different things. And because you can give it a name, it's also an object. So object means pretty much anything. How does it work? Well, see, you have a ledge somewhere with a stack of monkeys, as one has. And you wanna talk about one specific monkey, namely the third from the top, the one with the upside down tilted head. And that's quite a long way to describe this particular monkey, but it works. And you probably now know which monkey I'm talking about. But we could also call them Steve. And then the next time I'm talking about Steve, you guys also know what I mean. And this is basically all there is to it in Python as well. There's one thing, that monkey, there are no five monkeys named Steve. It's just that one monkey. That monkey doesn't change. It doesn't move because it's a photo. So it doesn't matter which name are used for it, that third monkey from the top on that particular ledge or Steve. Bless you. And in Python, it's pretty much the same way. It's not a ledge, it's a bepy. And it's not that particular tile on that ledge where they are on, but it's data. And then you have dot objects. Within the object, you have the monkey. The monkey has a location, it has an X property. And that is what that dot does. It becomes more specific. It walks down this hierarchy of names. But we can also call it Steve. And after you've done this, these two things become exactly the same. Both said Steve's X location to minus 0.5. Except that it might be easier to read because it's all about Steve. It's probably also easier to avoid mistakes because if you have a whole line of these bepy.data.objects, monkey.do something with it, you have to be careful that on each and every line you have the same monkey. Maybe you have monkey one, monkey three, monkey 500. And you have to be careful that you always use it in the same way. Whereas if you say, okay, Steve is the monkey that I'm talking about now and then do everything on Steve, it becomes much clearer. And it also becomes faster to execute because every dot is a lookup in some table of names. So even for a blender itself, it will be faster to, if you say Steve, than if you say be part of data, the objects of monkey. And this works perfectly until we allocate something else. So it could also say Steve equals three. And that's the thing with Python name. In Python, objects are really strict to have a type. It's a mesh, it's a blender object, it's a digit. It's very strict, it won't change in the same way that that monkey will always remain a monkey. But those names are interchangeable and they can change dynamically what they are pointing to. And that's why Python is called a dynamic language. Steve equals three is also perfectly valid. Even if before we use Steve to point to a monkey, we can now say, okay, Steve now points to that object that represents the number three. And then, of course, Steve.location.x equals something won't work if we'll give you an attribute error location to something that's something after the dot is called an attribute. And it says an int object, which means an integer in memory, has no attribute location. So if you see an error like this, you know that maybe Steve isn't what you think it is. Yes, that's a very good question. The naming of Steve is that limited to the blend file or to anything else. And because we're talking about Python here, it's all about instructions that are being executed. So before this line Steve equals something is executed, it doesn't exist. There is no such thing as Steve. It is created on the fly when that line is executed. So it isn't even limited to that particular blend file because the next time you load up the blend file, the script hasn't executed yet. You start with a clean slate as far as Python is concerned. And so the name Steve has gone until it has executed again. And then it only lives in memory, so you can save the blend file. But that particular name Steve, it's gone. It has no meaning anymore. Doesn't matter that much because usually you do this in your script. So it runs the entire script from top to bottom. And at some point you say, OK, Steve is the thing I want to work with, and then you work with it. So every time the line number 2, 3, and 4 are executing, that means that number 1 has already been executed and Steve is available for use. That sounds a bit weird. So this whole concept of we have a name and what it refers to can change over time gives rise to Blender's context. And this is the thing that in your script you will use all the time. It's available as bpy.context.something. And that something depends on what you want to use it for. So you have .activeObject, which means the currently active object. You have .selectedObjects, which are the list of currently selected objects, and so on. I'm going to speed up a little bit because it's already half past 11. So you can get bpy.context.activeObject. If you want to know the name of it, do .name. If you want to have the modifiers list, you get .modifiers, and then you can look at it by name again, and then you have options of that particular modifier. You can keep dotting, and dotting, and dotting. And this you can also see in, if I can all tap, yeah. So let's add, oh, I can't click. Let's add a modifier here. Decimate modifier. And here you actually see that whole thing. So you've got bpy.data.objects.icosphere300, because we just added a few, modifiers.decimate.vertexgroup. And that just gives you the information. But it's particular to this object. So say we have, I don't know, 300 icospheres, and we all added meshes to that. You can replace this bit with bpy.context.activeObject, if you only want your script to look at the current active object. And sometimes you don't even need a name if you want to do, but I'll skip that. So to recap, everything is an object. Names refer to objects. We've seen how to use a context very briefly. And you can just dig through these names with the dot and the control space in the Python console. So you can look it up from that tooltip, but you can also just browse around, pressing dot control space, see what kind of list of interesting stuff you get. Next thing is stuff on lists. It's called looping or iterating over a list. It's like going over a list and hitting everything in there. As I just said, bpy.context.selectedObject is a list of all these selected objects. It changes over time depending on what you do in Blender's interface. And let's just copy paste this into Blender and see what happens. There we go. I've selected a few. And there we have the name of the selected objects. So the way it works is you say for some name. Could be Steve, could be ob, because we're now working with objects, we usually use ob. In whatever thing that contains many things that you want to loop over. And a column. And after that, you can see that I put four spaces. And this is how Python knows which bits to keep repeating and which bits not. So I can do this again and do that and then go back and say print hello. What did I do wrong? I don't know. It's wonderful, it's wonderful. There's probably me messing around with my Blender install as I'm a developer and mess around with stuff. What you would have seen was all those names because that first print statement was indented. That meant that it was part of the loop body and then the next print statement was pulled back again. That means it's not part of the loop body so it's only executed after loop is done. Yes. Do you need four spaces exactly? Yes and no. It's all up to you as far as Python is concerned. So any consistent white space would do. If you want to use a tab coverter, that's fine. If you want to use two spaces or eight spaces, that's fine. And fortunately, nowadays, mixing up spaces and tabs is forbidden as long as you're consistent. So that's the yes part of this answer. The no part is that there is the Python style guide and that says, OK, just use four spaces. If you Google it, it's PEP8, the Python enhancement proposal number eight that shows you how to name things, when to use underscores between words, when to use capital letters, when how many spaces to use, et cetera. And the thing is if you adhere to that, initially it's your own personal choice because it's your own personal script. But then if you want to go on to, say, the Stack Exchange website, you want to have some help with your script because you can't figure something out, it really helps in your response. If you follow that standard Python style guide, then people will be more likely to understand what you're coding because it's easier for their eyes because they're used to it. And it's more likely that you get a useful answer. It's also for collaboration is nice if everybody uses the same programming style. So another thing that we can do is not just print a name, but we can also modify a name. So we can just say, op.name equals op.name.upper. So let's see if I can copy, paste this in here. No, you know what? Let's just restart Blender, duplicate this cube a few times, select everything. Op.name equals op.name.upper. And what this does is it goes loops over all the selected objects and then in a really stupid way, it says, okay, I have to assign something to something else. That means that I have to do this part first because otherwise I don't know what to do with the rest. So it computes op.name.upper. That becomes cube in all capital letters because you can do like cube.upper is cube. And then it assigns it to op.name again. So it's a, it can look very strange that you say, well, the name is not quite the name. And if you say that to somebody, they may think you're a bit thin in the head. But for computer, this actually makes sense because it sees it as something to execute. And now we should see, well, there you have it in the corner, it's all capitalized. So you could extend this question, yes. All right, so the question is, is op.name now defined as op.name.upper? And will that stay that way? Or is something else going on? Did I? Yes, something else is going on. A name refers to an object. So before I did this, the cube was called cube with a capital C and lowercase rest. And that string of characters, cube, was in memory. And then op.name was just a little pointer pointing at it saying, yeah, that is my name. That's my content basically, that's my name. And then what we did was take that, construct a new string in memory. That is cube with all capitals. And that is basically this part. This says, construct that for me in memory. And then by assigning it to this name, that little pointer, op.name that pointed to the original string now points to the new one. And that is a fixed situation. That if you change, if you save your blend file, that is what will be saved to disk, the content of Blender's memory. I hope that answers your question. So that relation. Yes. So the question is, if you, right. So the question is, will it mess up something in the future because I'm now redefining something. And maybe you forget that you've redefined something and will that cause problems in the future? I think it's a brilliant question. It won't mess up anything. That relationship that it was something else that we turned into an uppercase version of itself. After this has been executed, it's forgotten. It doesn't matter, it's the new thing now, it's the new name. So if Steve becomes Alan, then from then on it's Alan. And the history is forgotten. So it's exactly the same as if you would go to every single object, go to its name property, type in the name in capital letters. That would do exactly the same thing as in the script except that here you've automated it. Very good question. What would happen if you would rename an object to a name that already exists? Exactly the same as what would happen if you typed it in. So I think by heart it would change the object to the name that you give it. And then the other one gets the .00 something suffix. And that is a very good question. It hints on what I'm not going to be able to reach, I think, in the presentation. So I'll just do it now. A lot of scripters think about these objects in say, Blender objects by name. So that's cube, that's Steve, that's Victor, that's Coro. And when you start scripting it's very tempting to keep using that name. Especially since in the tooltip you also see Bpi.data objects and then the name of the object. So it seems like a nice identifier for the thing. And my suggestion is don't. I can see if I can... Raise to that slide. Here we go. So don't try this at home. Every script if you start running it from the anything but the Python console it has to start with import Bpi. That binds the name Bpi to Blender's Python interface and from then on you can do Bpi.something. Before that Python doesn't even know you want to use it as that name. In the Python console in Blender this is already done for you as a convenience. But in your script you have to do it yourself. And then we create a cube in the same way just blame copy paste from the user interface and I call a function reset position with the name of that cube. Because I know if you create a cube it's called cube. And reset position. So I'm skipped ahead a bit. You can define functions with our bits of code that take a name. In this case it's reset position. And it gets parameters. In this case it's opname. And that parameter points to whatever I put in. So in this case this contains the string cube. This looks up as we've seen before Bpi data objects cube. And then we set the location to zero. This is a silly example but it brings across what's going on. Because that cube might not be cube. It might be cube.0025 if you're already added a few. So this is something tricky because it may break your code unexpectedly. You may test this on an empty Blender file and it will just work fine. Or maybe you test this with a default cube and then adding an icosphere and it will work fine. But then for other people it won't. So that's so tricky about using names. Also names are not unique. And this is something people also tend to forget. You can link in objects from other blend files. So I can have a cube that is local to this blend file. And I can link in a cube that is stored in another blend file. And then if you have 25 of those cubes linked in they can all be called cube but all come from different blend files. So you never know which one you will get if you just use the name. So the solution is, as you guys all know because you're all Blender artists, if you add a cube it becomes the active object. And we've seen bpoint.context.activeObject which is a reference to exactly that object that was just added. And if you use that, pass it as a parameter then you can just say ob.location equals 0000 and you've done exactly the same thing as you've done before. Except that you're 100% certain it works on the object that you just added. So that's a little discretion. There's a sidetrack on naming. Any questions about this? Yeah. Well maybe just grab this one so I don't have to repeat it. Interesting. I wouldn't, anybody? Yes. Yes. So for the guys listening on YouTube most likely there's a difference between what is exported. So you could change the object name or you could change the mesh name and probably one of the two is used in the obj import. The mesh you can reach by .data by the way. So if you have the object, so bpoint.context.activeObject or one of the selected objects you're looping over, you have that object, then you want to reach the mesh or the curve, if you're talking about a curve object or the text information, if you're talking about a text object, is all .data. So the thing that is linked inside that object is always .data. That's just something to remember because if you look up .mesh because you're looking for the mesh, you won't find it. We've already been there. So let's look at a more or actually useful example. Say you want to select all hidden objects that have .00 in their name. This is just an example, but I think you can always already see that if you can do this, you can also say select everything that has .00 in their name and you also know how to manipulate the name so then you can say remove that .something suffix from it. Like this you can extend to, oh, I've duplicated all my objects and then remove the originals and now I want to get rid of all these .something suffixes. This is a kind of code that you would use. So again, it starts with import PPI, then a little comment that's very important when coding. Comment the broad steps of your code and when it comes a little bit trickier, also comment why you're doing it that way. The what should be clear in your code, but the why is often forgotten and that is so important when you look back at it, like after this stressful deadline is gone and you want to look at your code again and you know what the hell was it doing, you want to know what you were thinking. So we're going to select all hidden objects which means that after this, I want that my selection reflects that. So we start by deselecting everything, then we go over all the objects in the current scene. So the current scene is given by BPI.context.scene and all the objects that are linked in that scene, you can find in that objects. We loop over them. Object.hide is either true or false. It's a checkbox. It's called a Boolean after Mr. Boole who invented all the logic behind that. And with an if statement, you can say, okay, if this is true, then do this, else do that. The else clause is optional. So this allows your code to branch. So far we've seen stuff that always happened. Every name was converted to uppercase. Here we can be more granular. So if it's not hidden, it doesn't match our criteria of the objects we want to operate on. So we say continue. That means stop with whatever you're doing in this iteration with this particular object, skip whatever I'm going to write next and just go to the next iteration of the for loop. Go look at the next object. If .00 not in op.name, some string in some other string gives you a true or a false depending on whether it's in there or not. And if you add a not in between, then it's exactly the opposite. So it reads pretty much like English, this. That's also why I like Python. It's very, very readable. Who agrees, by the way, that this is readable? Pretty okay. Who thinks this is not readable? Good. It's continued the same as return. In a sense, it's a flow control command. So it does cause your code execution path to jump. In this case, it basically means skip all the way to the end of this for block and continue with the next one. If you were on the last object, it would just stop looping because it's done. If you're on the first, it will continue to the second, et cetera. And this is also an approach that I really like to do. I will, it's been messing up the indentation. This will cause an error because Python won't understand what I mean. This has to be like four spaces that way. I will try and fix this before I put the slides online. But this is what we actually want to do. We want it to select it. So we set the select property to true and we want to unhide it because otherwise they were hidden. You can select hidden objects, but I want to see what's going on. I want to do something with that selection later. Maybe delete it, maybe move them to a different layer. So I want to unhide them. So I set up.hide equals false. And that is the script that selects all hidden objects that have .00 in their name. And as you can see, I've taken a bit of an opposite approach to this. Instead of saying if the object is hidden and it has .00 in op.name, I'm going to execute this code. And that's perfectly fine, but it does mean that if you add yet another condition, maybe you want to check that the color is red. Maybe you want to check that it's actually a mesh object before doing something. That one if statement becomes longer and longer and longer and longer. And with this, you just add two more lines to your code. And then the bit that actually does something is always here at the bottom. It's much easier to spot. Yes. Yes, it's all about, we loop over everything here. This is indiscriminate theory, everything. Then we exclude what we don't want to see. And then we do the thing. And I apply this to pretty much every programming I do, also on Blender Cloud. Say you have some entry point into the web server and then I say, if you're not allowed to do this, return an error message. And then it breaks away from the control flow. It returns that error message you're done. So in the end, when you see those steps of validation and maybe you typed your passwording wrong, et cetera, et cetera, then you get to the block that actually does the thing. And if you do, if it's good, then if it's good and if the other thing is good and if the other thing is good, you start moving to the side and then you get to the else clause. Oh, but if that one wasn't good, then we have to do this. And then the password was wrong and then we do this. Oh, you didn't type the password at all. Oh, that was there. And you get this curve in your code. It becomes a mess. So excluding is a nice approach. Well, yes, another question. Hello, two questions. The first one is, are we able to assign something with a frame number? And the second question, can we put in some code that gets called when the frame changes? Thank you. The answers are yes and yes. Thank you. You're welcome. So frames are putting stuff in F curves. You can do that. Basically, you have to do a few different steps. I hope I still know them by heart. I've used it a lot myself. With the crowd simulation that I did during my PhD research, I wanted to do this a lot. I used the game engine to run the simulation which recorded all the motions of the crowd agents to F curves. And then I also wanted to store some choices that my crowd simulation system made and also put them into animation data. Angles that it found between different characters, distances, that kind of stuff. So basically what you have to do is first you have to ensure that there is an animation data block for that particular object. Which is something like B pi dot, I'm going to see C by the way because here, you see C equals B pi dot context. So here you can just type C. It's like your context, Steve. Yeah. C dot active object. Animation data lives on the object level. So this, and then animation, there we go. Data create, create animation data to this ID. ID is another word for data block, I think in Blender's memory. Note that not all the ID types support this. Yeah, fine, we know it's a mesh, it does. So this creates an anim data object. And then there we go, that's that animation data. It's an optimization, like normally in the user interface, you don't have to do this. You just say I want to have a keyframe here and then Blender does this behind the scenes. But when you're scripting, you have to do this automatically. It doesn't create animation data for every object because that would be very inefficient. So now that we have this, how do we do that again? Well, you can see the drivers, you can see, hey, there we go, keyframe insert. And that takes all the way up there, there we go. A data path, an index, a frame. That data path you can also get from the user interface. So these are the things that are location or location, bracket zero. So let's say I want to keyframe the location. There you go, copy data path. And we paste it in. Oh, we press the wrong button. Oh, this happens by the way. You see the little dots that did change? The standard prompt is the little arrows. It changes to dots, it did that in for loop. The column, it knew I had to have an indented bit of code so it started with dots and it stops that when it understands that you're done. Here, I try to do backspace, I hit enter. And it has an open bracket. It knows this has to be balanced. So if you see those dots, you know that you've told Python something that needs to be continued and to get out of this, you can try and keep entering but it doesn't work. So you have to just close the thing, swallow the error message that you get. Jesus, oh, I hope that it's still working. So the data path equals one. Then, sorry, equals our location. Then we had that index that had a default value of minus one, which means no, no index whatsoever. This works for a property that's just one number. Opacity, that will be have index minus one because it's one number. But location, as we've seen, takes a zero, one, two. It consists of multiple things, color as well. So then we have to say the index, well, say we want to change the x-coordinate index zero, add a certain frame, let's say frame 25, is that enough? No, I've ruined it. I have ruined it. That's beautiful. It's the live demo effect. I've never seen this error before. Restart blender. Yeah. Oh well, in short, that is the keyframe insert function is where you want to be. Then you can, yes. So say that again, you can copy from? Maybe you could call it from the log window. Yeah, yeah, you could do that. I hardly use that myself but there are people very happy with it. Do you say insert single keyframe? No, nothing coming up. I think that, there you go. I'm moving it here, what does it say? Yeah, it just has that translate operator call here because that log window, it doesn't log everything that's happening. It logs the operators and operators are the bits of code that are linked to hotkeys, menu items, buttons, everything. All the user interface stuff. But inserting a keyframe as we saw was calling a function on that NM data and that's not an operator so it won't get logged there. The default frame is the current frame but you can also say I want to insert it at frame one, two, three, four, five. So you could be reading in a text file that has location on every line as like three numbers for the location. You could loop over every line, split every line into three numbers and create keyframe from that. There's actually a good one because it's fast. It doesn't require a blender to change the current frame so you can just insert keyframes like that. If you say, okay, current frame equals something then you move blender to a new frame then you use some operator to insert that keyframe there and then you move to the next frame. That's going to be much slower. So we're already one minute past and who's the monkey work? Yeah, well, this is basically what we've already seen except that now there's 600 tiny monkeys. I don't do them in a straight line but in a grid of 25 rows of 25. If you're interested in how to create the monkeys, how to smooth them, subdivide them, you can do it like this. I will race through the slide. I will be here today and tomorrow. If you have questions, maybe if more people have questions or interested in more scripting stuff, we can book a room. We can do a more like a smaller scale workshop. Walk up to me and then we can do that. One common thing and the last thing I want to really cover is do not modify the collection that you're looping over. If you're looping over all selected objects, don't start selecting other objects. Don't start de-selecting them because you're looping over that list of selected objects. If you start changing that, Python becomes confused, things drop out, it could crash, it could just skip certain objects. So don't do that. So here this will fail. You're looping over contacts of selected objects, you say all those selected was false. So a much safer approach is to create a list of empty lists, that's the square brackets. To de-select dot append will append the object to that list and that's all we do. So instead of having Steve, it becomes to de-select zero and then the next one becomes to de-select one and then to de-select. So while we're looping over the selected objects, we're just adding them to a list if we find them interesting. And then once we're done, we say, okay, for every object in that list that we found interesting, we're going to do something. We're going to de-select them. And now we're no longer looping over de-selected objects but just offered to de-select. And again, we're not changing to de-select, we're just looping over it. And by the time we're done, Python knows that