 I am Sebran. Last year I presented this stuff I would be doing my promotion in. As of last April I am actually promoted so I'm now a doctor. Very happy with that. Also since March I've been working at the Blender Institute itself which I think is really awesome. Doing both Blender and Blender Cloud development. I have committed right since 2014 and the oldest thing I could find with the date on it was my developer dot Blender dot org account which shows that I've been nagging fellow developers about things not working since 2004. So two things interrupt me. This is not a talk. This is a workshop. I'm very interested in learning what you guys think of what I say or questions that you have. Sorry no you want to have a chair. Yeah and ask stupid questions. So to give a very rough outline we're going to talk about the work environment and project structure, best behavior of your add-on and some common pitfalls. So as for work environment I think pretty much any of you know and have used at some point at least develop. Who is a developer? Who is developing stuff? Add-ons, Python code. That's awesome. PyCharm and PyDev are like higher level more complex IDEs which are both really powerful. Atom Sublime are text editors with also some powerful features. Feminine makes you get into the older stuff. My desktop, just for people that are interested, I run Kaboom to Linux. I use Z Shell as my shell. I use Cometline a lot. I use Atom PyCharm, the community edition because the pro edition is really powerful but also not open source so I'm not using it. Community edition is fine and Qt creator for C and C++ development. And of course every once in a while I still use the Blender text editor even though it's quite simple. It is integrated in Blender and just gives you a very quick turnaround. So I'm pretty sure that when you guys start working on some Blender code, you look up a tutorial, maybe if you don't type it into the Python text editor itself, you get these red squeaklies. Whoever you have seen the red squeaklies, who got, yeah, exactly loads of people. So there's one thing I want to talk about. You can rebuild Blender as a module. Who has done, who has tried it? One, two, three, four. Who has succeeded? There you go. It's not that difficult. If you CMake, you first do it normally, you get a build of Blender, then you edit your CMake cached text with Python module boos. On, make and make install and then you have a Python module. But it's still somewhere installed in your Blender directory and not necessarily where Python can find it. So what I use a virtual end, which creates a virtual environment for your Python, you don't need to be system operator to modify your Python install then. These two lines, these two symbolic links, look them up if you ever want to try this because if you do that, and then, of course, you have to change workspace, Blender, build Python, your building directory, Blender is your source directory, modify it for your own system. But if you do this, then you can just start Python, import Bpy and instead of quitting Python the normal way, you can use a Blender operator for it. If you do this and then you configure your IDE to use that virtual environment, which pretty much any one of them can, you get rid of the red squeegees, you get code completion and it all works a lot nicer. So this is something I would really recommend as a setup. Now for project structure, how many of you have written add-ons as a single file? It's nice. I wouldn't say make it a Python project. Who if you have made an add-on as a full Python project? Awesome, cool. So this would be what I consider to be a fully Python project whether read me in the setup.py that creates your package, package name with an ended py, sub-modules, and even unit tests in there. This is a nice reference. Somebody complaining about that it was difficult to create? Never mind. Who uses version control for their add-ons? Git, few, subversion, few, CVS, anybody problems with where the files are? I've seen descriptions of where to, well, they're where you want them to be. I've had questions. People ask me where do I put the files? I see the tutorial about the add-on, how to create it, where do I put it? Well, you can just make them or find them. You can put them anywhere you want with some sim links. I'm reading through them, by the way. If you have any questions about this, of what it means or anything, just raise your hand, let me know. Or is this all known to you guys? A lot of nods. One question, yeah. What is a sim link? It's a good question. I like a pointer on your file system. It's like a directory that is not really a directory, but just pointing to another place on your system. You can have a sim link called Blender Cloud Add-on, right here in this directory. That points to your working copy of the Blender Cloud Add-on. I'm not developing my add-ons in .config Blender to seven eight scripts add-ons. I'm putting there somewhere nice in the workspace. Some are directly in my workspace, like C workspace. Or if I were to use windows like that. Some are in home workspace cloud for all my cloud stuff. I separate them out in different rougher structures. Blender wants to have them in one directory, that is, script slash add-ons. I can make the links from that one directory into all these other places on my machine where I have these add-ons. And Blender can still find them. And at the same time, I have all my code in the place where I want it. So both Blender and me are happy. Does that, yeah? Create a sim link rather than copy it in? To repeat, would there be an option for Blender to create those sim links for you, effectively? The short answer is yes, of course, it can be added. Yes, I have commit rights. And you can feel this coming. There's a big but. I think that it's mainly useful for developers because they know what is going on. They might know what a sim link is. So they could understand it. But as a developer, it's easy enough to make that sim link yourself. Whereas other non-developer users will just get confused by that checkbox. And then they check it. It still works. So they think it's good. Then they delete their downloads directory because their hard disk is getting full and all of a sudden things stop working. So I don't think it's a really good idea for general. But it doesn't, when you install from file and add-on, it's a zip file and it extracts the zip file, right? How can you sim link the zip file? That wouldn't work, I think. I mean the. Yeah, that's right. The thing he said about sim links because of the zip thing. Yeah, that's a very good point. And I would argue that unless the add-on is very small, it's a good idea to split it up into multiple files anyway and to have a proper Python package structure. I would recommend distributing it as a zip. So that's a problem. So getting started with your add-on. There's many examples. If you go in the Blender text editor, templates, Python, simple add-on. So sorry, simple operator, you get this. And this is something that is quite important. You see a certain structure. You see the simple operator class, which is the operator itself. It's the thing that you bind to a menu or a button. And then there is a main function that gets called from that operator. So that's something I would recommend to do, to have your functionality in regular Python functions and then have that operator call into those Python functions to do that work. I've had many questions that people are asking, how do you get an operator to run several operators after each other? One starts. So I would say don't just create an operator that calls to write functions. If you follow the structure that's given to you by Blender itself, you would get this. So then you have turning your packaging to an add-on. You just paste the VL info at the top and you're done. One thing to know is that it's not executed. You keep things fast. Only the first bit of the file is parsed. And Blender just looks at the structure of the code. So you can't do any fancy stuff in here. There's something that I ran into at some point. I wanted to have the version of the module defined at one central spot. I don't want to copy things all over again. But that doesn't work. You can't put a variable here that you get from something else because it's not executed. So now we get to the more interesting bits, the behavior of the add-on. I'll skip this, you guys know about naming. Reloading with F8. How many people have made their add-on specifically to be able to reload with F8? A few, that's nice. I also saw some people speaking no, which was good. So what happens if you press F8? It calls unregister on your add-on. It reloads all the Blender loaded modules from your disk. And then it executes the modules code. It calls registers again. So this is what happens with F8. And the trick is that it reloads only the modules that Blender loaded. Then you have a thing that code on disk is not the module in memory. I will skip this. You guys are more advanced than the questions I got before. So that's interesting. You know this stuff? You've seen it? No? Good, good, good. This is often at the top of the top-level module of an add-on. And this is what allows F8 to do its work. And this hooks into how Blender works. Locals is a function that returns a dictionary of all the local variables. And if you say import BPI, which is below that, BPI becomes a local name that points to the BPI module. So if BPI is in locals, that means that the code there is already run. It's on top and it executed just goes down. So how is that possible? Well, that happens if you reload. Everything in memory or the entire module, it just stays in memory. And that BPI name just exists. If it exists in locals, that means that you're reloading, you can reload something, a submodule. And I just copied this code from the Bolt factory add-on. So if we can reload it, we can tell Python to reload the submodule. And otherwise, we're running for the first time. And then you just import that submodule as normal. Maybe an important point to precise is that you can call import on a module several times. But it will only be evaluated the first time. That's why you have to use reload afterwards. Because you can import Bolt factory two times and the second call will just do nothing but return the module already in memory. Thank you very much, Pachin. Yeah. This is something that I also got quite a few questions about. How do you know what's happening in your code? So show of hands, who used the print statements as awesome. Self-report, I put it in there as well, because it is also a very important function to use. And it's also for telling the user what is going on. Error messages, info messages, warnings, that kind of stuff. I won't go into details about this because it's not really for developers. It's for telling your users what is going on. Who have print statements but another one is remote debugging? Who has used remote debugging? One, two, three, four, five. Yeah, that's good. Who has tried remote debugging and failed? Yeah, one, two, three. Excellent. And there is the logging module. It's a basic Python module. I think it's brilliant. I think more add-ons should be using it so we'll have a quick look at that as well. So print statements are, as you can see, everybody used them. It's very simple to use. It's well-known. But the problem is that you have to remove it again. It's for just debugging. You add a print statement, you see that the code executes. Then you have to remember to remove it all before you commit the code. Otherwise, the user gets all these prints and gets a wall of text in their console. There's also no metadata. You don't know where the print is, so you have to type that yourself. You don't get timestamps, so if you have a longer running thing, you don't know anything about the performance or whatnot. If you see a screenshot from a user that says, well, Blendo says this on the console, you have no idea from where that was or what the performance was or anything. You don't have severity levels like this is a debug message or it's an error or it's a warning. And there is no user control. And it's also a question that I've seen quite a few times on Stack Overflow, et cetera, asking, guys, Blendo is outputting all this information. I don't want to see it. How do I remove it? I said, well, yeah, sorry. You can't without editing the code. So another way to figure out what is going on is remote debugging. And remote might think that it's about a different machine, that you have a debugger on one machine and you code on a different one. But it's actually, you can use it on the same computer. But have Blendo running and have your debugger running and have one connected to the other. So it's more remoting between applications than between computers. With remote debugging, you get really detailed info about your code flow, about the stage. You can set break points and then when it hits, Blendo will just stop and your debugger will pop up. You can step through it. You can see everything. It doesn't require any additional code. So you don't have to remember that you have to remove stuff. And your break points can be really smart. I've been using Partcharm now for a while. And it really has some cool break points. Like you set a break point here that shouldn't do anything. And then a break point there that should only hit if that break point was hit. So it doesn't do anything. It does register that it was hit. So in this very common bit of code you can break only if that special case was hit before. That kind of advanced stuff I think is really powerful. Still you have little metadata so you don't have any timestamps or severity level. It doesn't really matter that much usually when you're doing this kind of debugging. And it requires IDE support. It requires configuration. It's quite tricky to set up. And it's only for developers. And I know at least somebody who was asking about how to set this all up. So I did that last night. I started with sudo apt-get install Eclipse PyDev because PyDev is also a really nice powerful development environment. I mentioned Partcharm. I used that before. But the remote debugging features are only pro. It's closed source so I can't say that it's good to use here at the Blender conference. So I wanted to go full open source route. Just apt-get install Eclipse PyDev. Then install my add-on and configure it and everything. But it didn't work. Just download and install it from there and then install PyDev from the other URL and then you get the latest version that does work. I made an add-on on GitHub. I might include it in the Blender add-ons or an add-on codefrip. You have to configure it for PyDev. Somewhere you'll find a path on your machine. I had to look really hard because nobody tells you where it is. But it's somewhere along those lines. I search for, I don't know, 15 minutes to find the thing. This is PyDev with the code loaded off the actual add-on for remote debugging that I was adding. And you have to click on that little thingy. Then choose the debug perspective. Then click on that little thingy. That will start the debug server. And it's really specific tiny little thingy that you can find. But this starts the whole thing. You will get a message I didn't screenshot that about a port number. I hard-coded that port number in the add-on hoping that it's always the same. I don't know if it doesn't work, let me know. This is the add-on you activated. And this here you see the the full path of the thing. So in your system you go to a home, your username p2, pidefd.py. The pidefd.py is the thing that you have to point Blender to. If you've done that you press space search for PyDev, hit enter. It connects to the debugger. You can set a breakpoint. At this point I set it to the unregistered function because at the time it was past midnight last night. And I just wanted to have what function that was easy to call. So it had a breakpoint there. You hit F8, it stops. How many of you have experience with this kind of debugging? Not so many. You guys want me to talk through what is here on the screen and what it can give you? Right, then I will go and yeah that's because I have to point a lot. So what you see here is the code and there's a little green dot over there. That's my breakpoint. I just double clicked in the margin and it sets a breakpoint. As soon as your code reaches that point it will pause and blend the roll lock up. It will no longer refresh. It will no longer respond to anything. Don't panic. That's what breakpoints have for. So the line is green to tell you that that is where the execution halted. And then here you see what was loaded. What is currently executing. So there's some string module that's in Blender. And then we will load script function in some initpy. We have a disable function in addonUtil. So py apparently Blender calls disable on the addons when you press F8. And that then calls our unregister function in remote debugger.py. So this is very useful if you want to answer the question. What is my code running? What is it doing? Especially when you have a common function that can be called from a hundred different locations. This will tell you where it was called from. And then here you have all the global variables, local variables you can really inspect and drill through to the state of all your code. And here is the console output of Blender. There's buttons over here that you can use to step over a function, execute it, or step into a function and execute it bit by bit. So it really helps in stepping through the code, seeing what's going on. Is that a bit clear? I hope. Yeah? Good. No? So this was my, I can turn it off. This was my commit from last night because Vibran at the dinner said he really wanted to hear about this and other people also wanted to hear about this. There you go. And then we come around to the logging module. Who has read my code.log.org article about this? Not bad. One thing I really like about, well everything I like about the logging module, but one thing is really important that it can be left in the code. But default only the winding and error messages are shown to the user. So if you log at debug or info, you can just leave it in. It's all fine. String formatting of that message is only done if the message is actually logged. So it also doesn't work down your code that much. This metadata available. You have timestamp, you have severity levels, where it is in the code which module. And it is under the user's control. And I think this is the most important bit. Anybody can configure their blender if they know how, but in a while you will know how. They can configure their blender to see those specific messages from those specific modules that they're interested in. And play with that to to get the info you need. It's slightly more complex than print, so I'm sorry about that. You have to import an extra module. You have to do some configuration. I don't see it really a downside to using a login module apart from that. So this is an example. So instead of using print, I make a logger. I just say in my class log equals logging.getLogger. And then I think it's useful for operators to use bpi.ops. Their name. So these loggers are hierarchical. So you can say all the bpi. I want to only see errors. No warnings, no nothing. That then works for bpi.ops. It works for bpi.anything because it's a hierarchy of dotted notations just like the Python modules. So I'm still not entirely sure what to put there, but maybe we can have a little bit of a discussion about that once I'm done explaining about this logging module. So once I have that logging object, that logger, I can call self.log.info.error.warning and it will just put that into the logger system. So we have an info, we have a warning and we have a debug showing here. Default behavior is that it will say no active object, not doing anything. So that was that warning that we had there. And the rest is just not shown. We can leave it in the code. It's useful for developers. It's not shown by default, so regular users won't even see it. The way I like it is showing info or higher, debugger for selected stuff that I'm actually working on and showing, and I'm being a little inconsistent here, sorry, showing only the time because the data I know it's today and working on my own stuff. So here you see that you have a nice overview of what's going on, the severity of everything. So how do you configure it? And I've had a discussion with it about Campbell. I said, Blender should be configured to log nicely. I think this is nice. This is not because it doesn't even tell you whether it's an error or a warning and I think that's an important difference. But Campbell said no. We're not going to do that because Python has a certain default. Blender should just use Python as a default as it is. If you want to change Python's default, go talk to Hilo von Rasmussen. And yeah, I actually agree with him. He said, you have to follow Python tutorials. You should be able to just get a Python book somewhere. Use it with Blender, get the same output as they show you in the book. I think it's a very good reasoning. So you can just put anything, you can in your OS specific prefix. You can script startup, put any Python file in there. It will get executed at Blender startup. So I made a config logging.py in which you put this. At first, I thought it was a bit scary looking. Version one is something that you always have to put in so that the logger knows that your theory is. Then you get the formatters, which is cool because you can say I want to have the ASCII time. That's not minus 15 seconds. It's a string of 15 characters. And I always forget what plus and minus is. One aligns to the left, the other to the right. I never know which, but this one works. Level name is debugger info or that kind of stuff. And then you get the name of the logger and the actual log message. And this way you can just configure yourself how you want to have it. Then you can give it handlers and these are really powerful. For me, while debugging me on stuff, I just want to see it on console. I want to standard error, standard out, something like that. But if you have a bigger environment with multiple Blenders running and you want to know from all of them what they're doing, you can actually send this through a network connection to a central machine that listens to all these log messages and then can also log the hostname of the machine it was running on. So you can create this lovely output of what all your machines are doing. I think then you really lift it to another level that you can't get with print statements. Before you could do everything with print statements. You could write your own name in there, you could explicitly say if it's a warning or an error, you could even put the time in there. Then to send it through a network, that's something else. You can also let it write to a file that gets gzipped automatically at the end of the day and then moved aside and then it keeps them for like seven days and then deletes the old stuff. That's all given for free in these logger code. Then you have the logos that well this configuration for logger specific stuff. So operators are debug and Blender ID is debug because I'm working on it. And stuff that isn't configured gets to the full pointing level. Any questions about this kind of stuff? Who is now going to use logging? Awesome. Really cool, really cool. So one thing I want to talk with you about is this bit. Often you see the module. So if I'm in the Blender cloud dot something that something else module, I would say Blender cloud dot something with something else or simply underscore and underscore name underscore underscore. But operators are being that important and injected into this two days bpi.ops namespace. So it might also be nice to log them there. Does anybody have an opinion on that? What would you love to see in Blender? Well I'd like to see the logging integrated perfectly because with respect I disagree with Campbell Barton and your agreement with him. When I power up the Python console in Blender the opening lines say we've already imported the following modules, the following convenience variables are available for you bpi.context as capital C. I know in advance that's not a plain Python environment. What's wrong with just adding another line that says login configured access it with this convenience if you want? Just as that heads up but it's not a pure vanilla Python. One I think is a very good point. One big difference is that the things that you see printed there that are not vanilla Python are only for that console where you see that message and the logging module is global. So if you change that configuration it goes for everything that you import every add on every built in Blender stuff. So that's the difference you might use it without seeing that message. Does that change your opinion? No. I agree that is a risk but the impact of that risk I think is quite small and I think the benefits of having logging switched on by default outweigh that risk. So would you agree with me that this is nice? Because what I've done here is create a nice example that aligns this all nicely and that minus 15 up there. That is because my module names are not that long so everything is within that 15 characters it all lines out nicely. Actually I'm not fussy. You know the benefits of having logging will outweigh oh no my module is 20 characters long it won't fit it's the end of the world. I'll come on get over yourself. No I'll take logging I'll take it however you give it to me. Cool. Thanks. Because even though I agree with Campbell that is very good like his points are very good I also agree with you and I would love to see more logging in our add-ons. Oh maybe you 15 characters gonna be the last one because there is the name of my module. Sorry can you say it again? When you in the format you trim the name. Well fortunately it's not trimmed so that it's just the default space that it gives it so I'm actually full of myself and talking nonsense because the name itself isn't trimmed or it doesn't have a width but I do play with that because I want to visually separate the name of the thing that's logging from the message itself and have all the messages in a nice column so I often do change that. Does long have a runtime cost even when enabled or when not enabled? Since it's a global thing will it influence the runtime of a script? No I don't think so. Maybe a little bit but the formator I think runs after the handler so if the handler decides that it shouldn't be logged anywhere the formatting doesn't run. Also you can ask a logger whether it's enabled for a certain level so if I'm really going deep with the debugging with the loggers I sometimes want to log a lot more info that takes time to gather so what I do is if self.log.isenable for debug then do all the complex debugging stuff so you can avoid that runtime overhead. Of course the if also takes its time so it's really sensitive but one thing I would quite like I don't yeah. When you pull down the file menu you get the list of operations that have just happened the logging within Blender's interface where it tells you what operators have happened a lot of stuff. If there was a logger built into Blender one of the logging output stream types or whatever it is that would output your log data to there. I think that's a very good idea. Then I think a lot of people would be persuaded to start using logging just for that convenience. Having it go to the console for standard out is fine but if there was a way to do that that would be awesome. I think it would also make logging lot more useful for regular users because if you start Blender with a desktop icon and you're not on Windows you can't even show your console and I think it's a good idea and you can create your own logging classes because this is just a stream handler that outputs to some stream but you can also make a Blender handler that puts them all in there. Another advantage by the way that I'm just thinking of I didn't put it in here but it's I believe it's thread safe so you can in different threads do logging and then it won't be a big jumble when it comes out. I think logging is super important and well designed logging is important for applications to help avoid debugging, help users and all that stuff but one thing I wanted to add is that if you don't want to add a default for all Python for the logging which I think is quite reasonable another approach you could take is to offer a utility module or a function inside the Blender modules where you just say getLogger with this name but it's bpy.utils.getLogger and then if you encourage a lot of the module or developers who don't want to actually spend all this time carefully configuring a logger which let's face it most don't then they'll just get some default logger that includes the name, the time and all that stuff and I think that might be a nice way to sidestep the issue of changing the defaults while also providing an easy path to encourage more module writers to do that. So effectively if I understand right you would say configure logging but leave the defaults in but create some sublogger say bpy here that has its own formatter and then everything you log to bpy.something gets that default formatting that might be interesting but I think we might even with your idea of letting it show in the user interface we might have nicer about your idea to show in the interface sometime ago I I won an interactive console for the game engine and found a console grotted in bitmap funds you know yeah but recently I discovered there is a post draw function in the viewport and you can write directly with bgl and I have my interactive console for the game engine in the runtime but that this guy say is possible with the bgl in the viewport. So you would have logging at real time drawn on top of your viewport like in the 3d view yeah yeah I wouldn't I wouldn't do it by default but I think once we have that framework for logging set up and something that can receive those log entries in in our blender code somebody could write an add-on that does that and if you like it you can enable it yeah yeah might be interesting so I don't hear anybody saying anything about or is it this name should it be the module name that the operator is in or should it be the name of the operator as you can call it I just say it's up to developer to choose yeah yeah I couldn't choose so now I put this in but I'm very inconsistent because I still can't choose so let's move on to the third bit some common pitfalls comments please please document why your code is doing something what and how is it should be clearing the code I think there's one exception or one big thing that dog strings like the documentation that belongs to a function should describe what it does for me it's the first thing I write I create a function I create this dog string I explain what it should do I see a lot of people nodding yes who do who does that who starts with the documentation wow sorry for me I know I can be chaotic I know I can lose my head in and the thing that I'm working on and it helps so much to have that little description to that says this function does that if I'm doing something else I know it should be in a different function I don't care about performance laws of doing another function call it's Python anyway it's not that fast anyway so I just it scopes my mind so to speak I saw a hand raised there yeah so my dad hope I work with legacy code and we all hope that our add-on lives forever and one thing about documentation if you change the code not always the documentation above the methods changes so yeah the other thinking is the real the code speaks for itself so we can read code so we can the code should be structured in a way that it's easy to read and then we know what this method does I think what I did with my add-on the more important stuff is the user documentation that the user knows how to use it and then they can use it and I think some plugins are really good or add-ons but the user documentation not so that's my point about documentation but that very much I think depends on the the goal of your add-on but I think like I wrote a huge add-on for my phd work with crowd simulation and everything and five different ways of the crowd simulation four and a half of which were wrong because that's what you do when you research stuff the documenting why something is done I think it's always important for other developers and that includes you in two weeks or maybe even you after having that good night's rest you have a different mindset yeah question does unit testing exist does unit testing exist in python yes it does it's a very good one and I really like unit testing it's also something that can be tricky with blender especially when you can't import bpi from without blender so what I sometimes do is separate my code into bits one bit is blender specific imports bpi does all the blender stuff one part of the code is not blender specific and won't load bpi won't load back into the blender specific stuff and then you can just import it from a unit test and do everything there are different unit test runners part of test is at the moment my favorite it also has plugins to measure things like code coverage so you can see that file line that through that that through that that through that is covered IDEs have support for it as well so I can just right click run this test with coverage and then the IDE will highlight which lines were hit by the test and which weren't so there's quite a lot of unit testing stuff available for python and if you build python as a module then you can even unit test your blender stuff but I must say I haven't haven't done that this one hit me and it hit me in a weird way my last name is Stufel with an umlaut on the U and that creates all kinds of funny errors so from the start of internet related stuff different computers talking to each other I've been blessed with I think my list I can look it up later it's about 24 different ways long like 24 different ways of spelling my name wrong it's pretty much all character encoding issues but my favorite error message was during the invalid name each name has to contain at least one alphabet so you can imagine that when I well I test a lot with Unicode stuff I love to go to Wikipedia go to a set it to Thai look up some nice recipes copy paste Thai or Burmese or what not into unit tests or into the interface to test with and this one hit me as a unit test as a Unicode issue because ASCII was doing fine and then Unicode was all garbled and messed up Blender didn't crash and back then this bit will misbehave was not in the documentation so I thought well it doesn't crash so I'm not being hit by this well I was so I changed the manual to include the misbehaving the thing is that Python works differently with certain short ASCII strings things that are really common like the I don't know what they include there but really short stuff doesn't get deleted from memory because you use it so often it's fine it keeps it in memory but more complex stuff it is deleted so then it went wrong so in the end I did this I created a decorator which is kind of a wrapper function around your function and what it does here it calls the wrapped function with certain parameters it stores it in a result and finally in the end before it returns it will store it in a property on the wrapped function itself so as long as that function that should return the enum items lives the last result that it returned will live in Python's memory I start with result equals none so if this fails for some reason exception exception then the last result is just set to none so it does clear the memory if it turns out not to be possible to return anything and this works this works pretty nicely does anyone see a problem with this? I'm not sure but I'm wondering if I mean how could you if your function is called several times in an enum property for example for each item how comes it doesn't return in the end always the result of the last call only or all the because you are catching only the last the result of the last call yeah so you mean you have many enum properties yeah that all call into the same callback yeah or even in the same property if you are I know I had that famous bug when I was using a function to generate the don't remember if it was the name of whatever of a set of enum values yeah and if you are using the same function to generate some text for each values of that enum I wonder wouldn't it only return the last the last result here it always returns what the function returns so it always calls but it only caches the yeah the last call so all the other calls are no more cached true so you'll hit again the problem yeah I think I'm not sure it has to be tested but so we could we could add another function outside of this to make it a now you need some kind of dictionary or something yeah we would you would have to pass a key into it but becomes tricky can we make just blender remember it do it properly I don't know oh yeah so yeah now that's that's the downside I didn't think of that for my case it worked because I only have one call back for one enum property by the way this was in the attract extension of the blender cloud add-on you have to it has a list of projects that you have available and that was where it went wrong well there's only one attract for us anyway with one list of projects so that worked fine but yeah that's a good point so that means it doesn't get into blender as a fix yet probably not no because then you need you need to keep the warning because the enum property still has the same and you can't really provide a good solution who knows this one functions should have only one return point who's heard of this who agrees with it but why why why less less confusion is the answer but yeah please give me the microphone because I want to I want to know why well um why I would say could be confusing to have like mysteries of if and and else if to have many returns I guess so you're better I don't know okay put a result in a variable and time is flying so we have six minutes left I love your answer so I want to move to this common approach if it's good then we do it sounds good on the surface I don't agree I say if it's bad you abort it sounds more more anyway so this is an example we have a special snowflake multiplication is very special the first parameter must be a non-zero integer the second must be a positive float and it returns our magic number and otherwise it fails so we do the if it's good then we do it approach if parameter one is not zero and parameter two is bigger than zero we return our magic snowflake there it is otherwise we have to raise a value error because something was wrong and otherwise we have to then I raise another value error because something else was wrong and this is very very common but I don't like it I say no no yeah if it's bad thank you yeah thank you yeah if it's bad common approach stop yeah in any case yeah in any case this is correct yeah and I like this so much because if you have yet another thing that you have to check the network is still up special special snowflake what happens here is that you just push that thing that does the actual work you push it down it can be easily found here at the bottom and it just pushes down whereas here this would indent even more you would have a get diff that shows that the thing that does the actual work has changed whereas it didn't it just need an extra test with this you see in your diff that you added an extra test and that's it so do you still think that it's a good idea to have one return point or not well in this case you have one this is kind of return you could see return minus one or return minus one here if you're any more C style you return error codes as well exiting out of next nested loops yeah if you make it a function you can just return yeah if you use generators you can often just avoid having a nested loop all together at least in the main thing that has to do the thing yeah I don't know this is also something I've seen so many times and it's just just do that like this is a thing an expression that returns true or false so if it the thing that returns true or false is true we return true and if the thing that returns true or false returns false we return false so just just do this I think we're yeah so this one I want to give you yeah but go back to that right go back to the previous one that is more amenable to adding debugging or adding comments or adding alternative actions that need to happen which will then appear in the right place in your diff so actually there are some times when that is the right thing to do true I don't completely agree yet because always doing this because at some future you might want to add something that might be a bit clearer then I would say if you need it just rewrite it to this until then just do that I would say because it's so much harder so much easier to read this one is a must read for every python programmer it's funny it's a shit load of of anti-patterns it's easy to read it's just it's just hilarious I think we're running out of time but Andrew don't do this the problem with putting your data in the add-on is that if you upgrade your blender to to from say 277 to 278 everything is copied so then you have an 2 gigabyte add-on if you then upgrade to 279 you have a 3 gigabyte add-on so instead call this function give the cache parameter to give you the nice system specific cache directory when I implement t something something until the time there is that abdir.py that can give you a very nice alternative I use this also in the blender cloud add-on to put temporary files that you download in there and one last thing that x plus equals y is not the same as x equals x plus y it's often described yeah it's just a shorthand or the few people that don't are not programmers x plus equals y means increment x with y and the other one means compute the sum of x and y and assign it to x now for numbers it's exactly the same thing but if you're talking about lists for example first one will append y to the like list or compute the union if x and y are both lists the list of y is added to x and it becomes a larger list the second one computes a new list so the original x and the original y are not modified and then that new list is assigned to the name x modified and then that new list is assigned to the name x in a more complex situation where x may be a parameter that you gave from a function and the new x is something that you return the first one would modify the input and the second one which just generates a new output without modifying its input so it's a very big difference and we've argued on ISE with developers who try to do a clean up of Python code and change the button to the top and then things break so please know the difference so to look back we created work environment we created add-ons and didn't look at naming classes because you guys know we look at mostly at logging and returning and that other silly things thank you very much