 Hello and welcome to Scripting for Artist. My name is Sibren and in this episode we will be looking at linking of assets. Because there is a lot more code in this episode than in previous ones and the code is getting complex for as well. I've put the final add-on in a link in the video description so you can download that and use that as a reference while you're watching this video. Now we'll go to Blender Cloud to take a look at the Settlers project. It's a public project so you don't need a Blender Cloud subscription to access this and it will serve as a nice example for this episode of Scripting for Artist. I've also put a link to the project in the video description. Here you see an animation on Blender Cloud of the Settlers project. As it's typical for such animation projects it's seen as a set and each set consists of assets in this case like rocks and plants. Here on the left you can see in their set development different blend files for the rock assets and for the cacti assets. They're all linked into the environment file. Here you can download the desert file. Just click on the download button here and this will give you the following files. Here I've unpacked the files into Atlanta Desert. It has an Nodes and Assets directory. The Nodes directory has the environment assets so the desert plants and the rocks. Nodes has some shaders we won't be looking into those and in Sets you will find AtlantaDesert.blend. I've already created the JSON file that we're going to use of course in the preparation for this video and we have a new setup blend which we're going to build up automatically from scratch. Now before we start automating things it's always good to take a look at Blender itself and manually go through the steps that you want your script to perform. So typically you would go to File and then Link. You would go to the environment assets say rocks and there we have collections that we want to link in. The naming is as follows. You have en- which stands for environment asset and then there is a name and a number. So we want to have all the en rocks. We want to have all the en smooth. En rocks is like the entire thing. We don't want that one. One thing you can already see is that all the collections have a certain prefix. This we can use in our script so that it will automatically pick up the right ones. So en-rock underscore s underscore that will work for the first set and en-rock underscore smooth underscore will work for the second set. Now let's just link in one of those. You click link and automatically Blender instances this into the scene. If you want to link this into our scene ourselves in our script we have to investigate further into what this actually is. So what we have here is an object and it's actually an empty object with instancing set to collection. Instancing a collection from rockstop blend. Furthermore the empty is of course part of the scene collection and that is why it appears in our scene. So this is what we have to do. We have to link in the collection from the rockstop blend into the current blend file then we have to create an empty and then empty will instance that collection into our scene. Now contrary to what we did before we're not going to use this link operator. It's very hard to use from python and there is a much better way to do this. If you go to help python api reference and then search for bpi data libraries you find it's a library data blocks of type bpi data libraries click on that and here you will get to the load function. So this is what we're going to use bpi.data.libraries.load. This takes a file path it takes a link parameter that can be set to true if you want to link which is what we want to do otherwise it would append to the current file. Relative should be true if the file path is to be interpreted as relative to the current blend file. Here are some examples of how to use this function. The function actually acts as a context manager which means that you use it with a width statement. A width statement has an indented body so you have the width then you have an expression here then you have colon again like we know from the if and the while etc and then again you have a bit of code that is indented that is called the body of the width statement. Now the width statement does something before it enters the body and then it does something else when it exits the body. You can use this for all kinds of things like opening file and reading the context and then making sure that when that width statement is over the file is automatically closed again cleaning up as you go. In this case it loads the blend file and it exposes the contents of the blend file in data from and then you can assign to data to whatever data blocks you want to link from that blend file. So the data from and data to they look pretty similar to the bpy.data that you already know so it has a dot scenes dot objects that's collections etc. The only difference is that they're not really objects or scenes or collections they're just lists of names so we can act based on the name of the scene the name of the collection the name of the object and whatever names we give to data to will be finally imported when the width statement is over. Here in this example it appends the scene called scene to the current blend file it appends because the default for link is false so that's the default behavior there's no link parameter given so it will append down here you'll see an example with link is true so that will link instead of appending the data to dot scenes dot measures dot objects etc are lists like we've seen before so we can also append things to them similar with data from data from dot collections is also a list of collection names so we can loop over those this means that we can loop over the collections look at their name see if the prefixed with that prefix that we wanted to have and if so we append them to the data to dot collections list now let's go back to blender and write some code to explore this a little bit see how it behaves so we're back at blender and this will all look pretty familiar by now we're importing bpi we set a file path and because I've saved this file inside the sets directory we have to go one directory up then to nfs and then we have rocks dot blend the rest is pretty similar to the example that we've seen already so we have with bpi data libraries load then the file path and link equals true because we want to link and then we have as data from comma data to as we expected now all we do here is print data from dot collections just to get a peek on what is exactly in there for our particular file click play and then we go to the terminal where we started blender if you're on windows you can go to window and then toggle system console in order to see this here we see that list of collection names just like we saw in blender itself when we manually went through all the steps now even though we didn't actually link anything from that blend file yet it did have some effect if you look at the outliner you can see that it did load the rocks dot blend file that we wanted so it's already doing something now we have our list of names let's do some filtering on the nameset we find interesting and actually link those into the current blend file for this let's first determine our prefix so remember we had two sets of rocks let's just focus on one now and then we'll do the other later and that started with en dash rocks and score s underscore we can remove this print and just say for collection name in data from dot collections if it doesn't start with our prefix we're not interested so we skip them by using the continue keyword this will just tell the for loop to go to the next element and skip the rest of the body in other cases though we have the prefix and that means we have to tell blender that we actually want to link this collection this means we have to do something dot data to dot collections and since it lists we can append to it let's give this a try this all worked fine and now we can open this file in the outliner and we can see that these collections have been added to it these are exactly the collections that we wanted and all the collections that we didn't want are not here so our code is doing the right thing now all that's left is to create an empty that instances these collections and then link those empties into a scene collection there's something special about this data too within the body of the width data to dot collections is a list of names but once the width body is done and the part data libraries dot load has actually loaded what we needed data to dot collections is no longer a list of collection names it's actually a list of the collections that were linked in so what we can do is loop over them now here call is first the first collection then the second collection etc now let's create an empty it should be named after the collection and it shouldn't have any data this way blender understands that this object should be an empty and not a camera or a mesh or anything else now we have to set some properties and let's take a look at what those are so just to test I add an empty empty so I have to set this one which is object dot instance type and of course we have to set the collection to one of the collections and there you have it this is all we need to do so hover over here and you see it's instance collection so we have to set instance underscore type and instance underscore collection I'm guessing that instance underscore type will have to have to value a collection but we can always look that up of course and there you have it the instance type should be set to collection in capitals so let's just copy this and then we have that and instance collection is of course the collection that we want to instance which is call this will create the empty but it's not yet part of our scene now I think it's nice to create a special collection to have all these empties rather than having them at the top level so before we start doing this we want to make sure that that collection exists scene is bpi context scene later when we change this into an operator that does all the work of course we will use context.scene and not bpi.context.scene but for now this will suffice let's say that the collection that we want to link everything to should be called environment now I want to create this collection but only if it doesn't exist yet the easiest way to do this is just to act as if it already exists and then see whatever occurs when it doesn't exist yet and then simply handle that case so let's do the simple thing first let's say it already exists then we can access this collection through the scene like this and it will just give us a collection now if it doesn't exist yet this will raise a key error now we have the information that we need let's go back to the code as we've done before we want to use try and accept so we tell python try this for us and if this fails we'll have to create a new collection and then once that collection is created we have to add it to the scene collection so now this code will just use the existing collection if it's there and create a new one if it isn't now that we have the collection we want to link to we can do the just that and this will link our empty you can see that running now to considerably longer and here we have all our assets linked into the file they're all empties they're all inside the environment collection and we don't have anything we don't want to have now as a final touch let's line up all the empties in one line along the x-axis so that they're nicely spread out and not all lumped in one spot for that we have to keep track of the x location of the previous empty so let's just start at zero and let's give every empty two blender units now that they're linked to our scene we can set their location and then we can increment location x by step x so that the next empty will use the next location let's delete all the empties run the script again and there they are all neatly lined up our script is working but it's very limited if we want to load assets with a different prefix we have to go in change this prefix in the source code run the script again if we want to load from a different blend file we have to do that again if we want to link the empties into a different scene collection again we have to edit the script and run it again it will be much nicer if we had a file that contained all this information it contains which blend files to use which prefixes to use which collections to link into we can then put our code into an add-on that doesn't need to change and all our project specifics go into a separate file before we do that though let's copy our code put it into a separate python file and shuffle some code around into separate functions so that we can work with some higher-level building blocks instead of looking at one big blob of code all the time so let's copy what we have now and go to the add-on I already prepared we've seen this a bunch of times it's just a python file with a BL info we import bpi I've already added the operator with an empty execute function so we'll fill that out it is added to the import menu so that we can just do file import and then load stuff from json and we have the blender classes where we have the operator class and the register and unregister functions that we've seen before so above all of that let's paste the code that we just copied and let's turn this into some functions so there's a few things going on here at the same time this first bit is linking data into the current blend file then we have some code that creates a collection for us if it doesn't exist yet so actually that's quite a different thing so that can be moved into a different function then we look over all the collections that we linked into the blend file so this actually belongs to the first bit of code so let's take this part out and start writing a separate function for it let's call it ensure collection because it ensures that the collection exists it need access to the scene it needs to know the collection name of course and then we declare that it will return the collection let's move our code in here and change that to collection name now all we have to do is return the link to collection and then this function is done now we have the other bit of code to look into let's call this function link to a scene it will take the file path as a parameter instead of having it hard coded here similarly it will take a prefix as a parameter instead of hard coding it here then we have the link to which is a collection and then the location y later on we will be linking multiple sets of assets from multiple blend files and then having them in different rows is kind of nice so that is why I added a location y parameter here as well now the rest of the code needs to be indented because it needs to be part of the function and of course now we also have to set the y location so we can do that like this now that we have our two functions we can just call these from our operator still with the same hard coded values that we used before so that at least we can see that they're working so first of all we have to create the link to collection and then we can call link to a scene with the same file path and the same prefix as we used before then the collection then the collection we want to link to and let's just use zero as the y location for now now of course you have to enable your add on you know how that's done now you have to install and enable your add on if you want to have a refresher on how that is done look for this scripting for artist chapter called from script to add on there it's all explained now you go to file import link assets from json and you can see that everything works as expected now it's time to look at that json file that i've mentioned a couple of times already json stands for javascript object notation and it's quite a common file format it's not the easiest to work with but it is doable and is not that complex and python has built-in support for parsing those files and for saving those files so it's rather convenient file format for you to use for our case here you see a high-level overview of what that file looks like you have curly braces want to open one to close the file and within the curly braces you have what is basically a python dictionary so it's a name between quotes and that name has a value in this case it's another dictionary so here we say these are our collections and we want to have two collections we want to have rocks and we want to have plants and within each we say link which will contain a list of files and prefixes that we want to link into the scene into this collection so here you see a list of things it has the same notation as python so with these blocky parentheses kind of things and within it we have a list of in this case two dictionaries again each dictionary has a file and it has a prefix of all the collections to link in from that file we do something similar for the plants we have a link and then again file prefix file prefix file prefix in this case there are three of them even now we could have removed this link and put this information directly into plants but i i always like to have things a little bit more explicit and this will also give you a chance later if you want to add something extra to it maybe uh maybe you want to have something visible by default or not or maybe you want to enable only in certain view layers or not by having this under a link key you can add multiple keys later for your own workflow for now let's keep it simple and write some code that loads this file then turns it into python data structure that we can use so let's comment out these two lines we'll just leave them sticking around as a reference for us later first we have to load the json file and for that we need to have the json module this is built into python so we can just do import json and then we have access to it i think it's a nice touch to have the json file sitting next to the blend file which means that we want to write it in slash slash notation which means we have to translate the file first this can be done by passing the slash slash ss.json file name through bpi.apps path and this will take care of that so now all we need to do is open the file and feed it to the json library i will be copy pasting some code instead of typing at all because now we have a lot of python code that will do auto completion and show information pop-ups and everything because i scaled up my editor for this video is going to block a lot of things and be more distracting than anything so this is how you open a file you can say with open json file name as infile and this will open the file for reading and once the width statement is done it will close it again making sure that we don't leave any open file handles around on linux this is not such an issue but on windows it's a problematic because if the file is open by one program another program won't be able to read or write to that file you won't even be able to delete the file so always make sure that you close a file after you've opened it and the width statement is a perfect way to do that after we've opened the file we can pass it to json.load which takes the file as a first parameter and this will load the file and parse it into a python data structure this means that link info here is a dictionary if you want to see exactly what in there you can use a print statement run the code and then you can see for yourself so going back to our json file we know that it represents a dictionary with one key called collections which contains another dictionary so let's see how we access this in python here we take the link info dict we get the key collections and we can loop over this by taking key value pairs and for this we use the dot items function dot items on a dictionary gives us something that we can loop over with a for loop and it will get you pairs of the key and the value in our case it will give us rocks with that dictionary and then plants with the second dictionary so call name is first rocks and call info is its dictionary and then on the second iteration call name is plants and then call info is the plants dictionary so the first thing we have to do is ensure that this collection actually exists we have a function for that so let's move that code and put it here and instead of having environment as a hardcoded collection we of course take the collection name from the json file now might also be a good time to just save your file reload it in blender and see that it actually creates those collections for you just as a first step to see that that is working and also run it twice to see that it still works when the collections already exist so the next thing we have to do is take a look at that link key and the list that is in there so call info link will give us this list and of course because it's a list we can just loop over it and it will give us that dictionary with a file and a prefix let's make it a little bit easier for us and take the file and the prefix keys and store their values in these variables so for the first iteration file path will be this path and this prefix will be this prefix what is left now is to link everything to the scene but instead of using our hardcoded file path we take the file path from the json file and the same for the prefix now we have to make sure that the y location is increased not everything ends up at y is zero so we can do the same thing for this as we've done before with the x location we can start location y at zero and then pass that here after this is done all we need to do is increment the y location with the step size and that's it let's go back to blender and see it in action let's delete everything there was including the collection and there it is we have rocks we have plants and everything is there in the file for you to start dressing up your set now there's one final touch let's say the asset files change and something new was added this is not automatically loaded into this file if we rerun the operator again it will create all the empties again including the ones that already exist in the file let's add a little filter to the creation of the empties so that our code doesn't try to recreate an empty if it's already there for this we have to go to the link to scene function linking things that are already linked is fine blender will skip these automatically for us so it really only the creation of the empties that we need to worry about so here we are at the line that creates the new empty so before this we want to have our check this i think is a nice approach the collection name is going to be used as the empty name as well so if the collection name already exists in link to dot objects we know that the empty is already there and we can just skip it so i'll move this as it way so we should not see anything new pop up here and of course you can also see it here in the outliner that these numbers shouldn't increase because everything has already been loaded and there you have it it all works fine so this is it for this episode of scripting for artists of course this is just a start but you can imagine that things get pretty powerful when you can store information into a jason file load that into blender and use that to instantiate certain collections of course collections can also consist of links collections so the set that you're building with this tool can also be a collection that you instantiate into a shot file for example so i'll leave that up to your imagination and your workflow for now this is it if you have any questions or comments leave them below and i will see you soon