 Howdy guys. All right. So for our next Python project here inside of Houdini, what I wanted to do is write a generic JSON exporter. I know you probably or maybe you've seen my other JSON exporting Tutorial. The problem with that one is it's all hard coded, right? So you have to make sure that you have those particular Attributes available on your points when you export. Well, what if, you know, you're you want to reuse your code and have it fit to a different set of attributes? All right. And so what we're going to do is we're going to walk through this process where we use a multi-param so you can add in the attributes you want to actually export from your points. All right. So if I wanted to, you know, export, you know, a color value just by adding that on. So currently my previous test there, let's actually go to my desktop and then add points. So the previous test, you can take a look, doesn't include that color attribute, right? Just included the rot, p scale and height that I added. So now that I've got this color attribute, let's just add a new attribute onto this and call this color. And I'm just going to create a random value there. And now if I export it, it'll take into account the color because I added that back on and you make sure I actually add it. Not cool. There we go. So now I've added a new attribute and I just want to update my JSON file and I don't want to have to update my Python code. I just want to update my JSON file. So I'm going to export points. And if I already go now and take a look, you can see it updated and added color. Pretty cool. All right. So that's what we're going to walk through. We're going to walk through the creation of this HDA and all the Python to make that work and how to work with multi-params inside of Python. All right, let's get started. All right. So let's walk through the process of creating this generic JSON exporter. So the first thing I'm going to do is I'm going to create a geometry object because this HDA is actually going to be a soft level HDA, not an object level. So object level meaning it would be an HDA you create up this object level right here. We're going to actually put it inside of this geonode. So I'm just going to call this points for JSON, something like that. And inside of here I'm going to drop down a grid like so just because I want to scatter some points to test with. All right. And so let's turn that off. And then let's go and scatter some points. And there we go. All right. And, you know, I'll probably just leave it at something like 45, something like that. Doesn't really matter actually at this point. So at this point what I want to do is I'm going to create an attribute randomized because I want, you know, some data on these points to test with as well. So let's do an attribute randomized and we're going to do a p scale for this one. All right. And what I want to do is uniform discrete. And so our min is going to be, let's say 0.5. And then our max will be something like two. And we only need one dimension for this and the name needs to be p scale. There we go. So now you can see we have a random value over here in our geometry spreadsheet. All right. And so the whole point of this particular JSON exporter is that I want to be able to export out any attribute that I add to any set of points. And I don't want to constantly have to rewrite the code to take in a different set of attributes. All right. And so that's basically what we're going to do. All right. And so I'm going to create a sub network here. And this one, it's just going to be called generic JSON exporter, something like that. You can call whatever you want. And we're just going to pipe that into the first input there. Turn that on. And all we really need in here is a null node like so. And then we just want to hook up the incoming geometry from this first input here into that null node. And then I'm just going to call this something like export geo. Yeah, you can call whatever you want. All right. So with that done, let's go and make an HDA out of this. I'm just going to right click that sub network. And I'm going to put on my namespace here like so. And then we'll call this version 1.0. And then what I will do is get rid of those guys there and just give it a pretty name. All right. And then what I want to do is save it into my project here. So my HDA, I'll probably move it to my master library some point here. All right. I'm going to hit accept. And that will open up the type properties window for us where we can start to add our Python code for this. All right. So a couple of things that we need to set up for our parameters is we need a file directory. So we want the user the ability to assign where the file is actually going to be saved. All right. So I'm just going to call this file dir. We'll call this file directory. Go and we'll hit apply. And then we also need to provide a string. So I need to get the string parameter here and just put that in there. And this will be called the file name. So we'll call file name. And what we'll do is we'll actually append on the dot JSON file format to it. So that way all the user has to do is provide a name for the file. And then finally we just need a button to actually export. So we'll call this the export button. So we'll say export points. There we go. Hit apply. And what I want to do with this particular system, like I was saying before, is I want the ability to add on as much or as many attributes as I want. I don't want to have to constantly go and update the code, the Python code here to accommodate some new attribute that has been added. All right. It would be great to have the ability to just, you know, generically say I want all these attributes to be exported. And so to do that, I need a multiparm. All right. And so what I'm going to do is I'm going to drag and drop out a folder. And then I'm going to set this to a multiparm block list. And I'm just going to call this attributes, like so. And then underneath that, I want to start to add the parameters that will actually allow the user to say I want this particular attribute to be included in the export. And so all we really need to do for that is to add a string. So I'm just going to add a string underneath that folder. And we'll call this the adder name. And I should put two T's there. All right. So we'll call this adder name. And that will be the field or the parameter that the user will then type in the attribute they want included in the export. And so I'm going to hit the plus button here just to give myself one. And in this case, I want to export out P scale. There we go. So that pretty much concludes all the UI side of things. All right. So I'm going to hit apply just to make sure it's saved. And so let's focus now on the code side of things. So to start adding Python to our HDA, we just need to go and add a new Python module to it. And I'm going to create my first function and we're going to say export points for the function name. And we're going to pass in the quarks there. And that includes a bunch of information about this particular HDA, this node. All right. And the first thing I usually do here is I just put in a print statement just because I want to make sure that my button is actually hooked up to this particular Python function. So I just say this is working. Go back to my parameters tab and go to the button. And I want to make sure that this is set to Python. And what I'm going to say is who.pwd. All right, that references this particular node. And we're going to call its Houdini module or HM for short. And then we call the export points function and pass in our quarks there. Let's go back to the end there. So we say quarks like so. Awesome. So now if I come over here, just kind of tuck this down there and open up my Python shell here. And if you don't see that, you can just go and open it up from here to hit the little plus button there. All right. And so if I hit my button now, you can see it's printing out this is working. So now we've got our button hooked up, which is great. We should actually also just pick a directory here because I'm going to make sure that all my folders and paths are all hooked up properly. So I want something to test with. And so I'm going to create a new folder called points like so. And we'll keep it like that. Yeah. So allow us to assign a directory. And then I want to call this point data or something like that for the name. All right. And so I'm just setting that up so I have data to test with. All right. So let's go to the script tab here. And the first thing that I want to do is I want to import a couple modules from Python. So we're going to import OS. All right. That allows me to work with the operating system and like files and inputs, outputs, stuff like that. And then I also want to import the JSON module so we can actually save to a JSON file. All right. Cool. So the next thing I want to do is I want to get the parent node because we're going to need to get our Parm data here from the file directory and the file name over there. So I'm going to say parent is equal to quarks. And then we need the brackets, square brackets and we'll call this we want to get the node. All right. So quarks is a dictionary contains a lot of information. And what I'm wanting to do is I'm wanting to find the value that has the key node. And that'll pass me this particular parent so I can access these parameters up here. Cool. So with that done, let's get access to that null node. So we're going to say geo node is equal to parent dot node. And we want to get that node that we that null node that we created inside of here and it's called export geo. So I'm just going to copy that name there and put that in for the node that we want to return. So that gets us access to this node and we have to do that because we need to basically read all the geometry that's sitting inside of that Null node. All right. So all these points here. Okay. So now we've got that all hooked up. What I need to do now is get the directory. So I'm just going to say dir for the variable name. We'll say parent dot parm. And we want to look for the file dir. I believe is what I called it. So let's go and see a file dir. There we go. And we'll just put that in here. And then we want to get the file name. So it's a file name is equal to parent dot parm. And we want to get file name like so. So now we have the ability to actually create the full path. All right. So we'll call this full path like so. And that's going to be dir plus file name plus and actually let's just leave it there for now. And let's print it out. So we'll say print. We'll call this full path or we'll print out full path. So I'm going to hit apply. And then we need to jump up and out so we can access our HD UI and I'm going to hit the export points. And I got an error on line 10 there. It says that I can't attach on. Oh, that's because I also need to make sure I eval this stuff here. So I get the real value, not just the parameter class. There we go. All right. So let's hit apply. And let's hit export points and look at that. We now have our path. All right. And all we need to do is really append on the dot json there. But before we do that, let's make sure that the folder actually exists. All right. So in order to do that, I'm going to do if not OS dot path dot is dir. All right. So we can actually check to see if the directory exists and that directory is this full path here. All right. So that doesn't exist. And we should probably make it because we're going to need that folder there. OS dot make dir full path like so. That just ensures that we actually have the proper folder in place. So we won't throw any errors when we're saving out the json file. All right. So with that done, let's now access all the data inside of the multi-parme here. So let's do this. Let's come down here. And what I'm going to do is I'm going to say multi-parme. All right. So this is how we access all the extra attributes or extra parameters that are being put in here by the user or the end user. All right. So we're going to say multi-parme is equal to parent dot parme. All right. And we want to get what did I call that? I think it was attributes. And we'll call this adders. So there we go. We'll put that in here. So that will get me that particular folder. All right. And then what we need to do is we actually need to get all the instances. All right. So I'm going to say instances is equal to the multi-parme. All right. So that's this variable that we created here. We're storing that folder parameter by doing that. All right. And we want to get the multi-parme instances like so. All right. So this will return all of the instances that are added on here. Okay. Cool. So now we have a way to access multi-parme data, which is great. And what we should do now is we should put them in a custom. Because let's do this really quick, actually. Let's actually print the instances so you guys can see this. So I'm going to hit apply. And currently, it looks like I lost my one there. There we go. We want to do pscale. And let's add a couple more here. So I want to show you what the output looks like so far. Let's say like added raw to maybe added, you know, a height, something like that, right? So if I were to print this out now, so I'm going to print all that stuff out, you can see what the output looks like. All right. So we have, we're getting all this data here from those guys. So we have three individual options there. So add or name three, add or name two, add or name one. All right. And that's coming from this naming convention. You can see it puts this little hashtag in front of it. And so it's just automatically inserting the index, the order. What we need to do is actually get the value from that particular parameter. All right. And so let's go back to our script over here. And let's do this. We're going to store it all in an array or a list. We'll say adders is equal to an empty array there. And I'm going to say for X in instances, like so, we're going to say adders dot append, like so. And we're going to append X dot eval. All right. So then now let's print out the adders here. So I'm going to hit apply. And let's go and print this out. And look at that. We get p scale, rocks and height. All right. So we're pulling all the data out from that multiparm, which is super cool. It makes it very generic, right? So we can expand it or just make it fit forward over data set we want to pull off the points. All right. So with that done, all we need to do now is create our initial data structure. So I'm going to say data is equal to empty dictionaries at first. And we're going to enter in points. And that's going to be equal to an empty array. Like so. Let's make ourselves a little bit more room here. There we go. Cool. And then now what we need to do is need to get access to the geometry that's sitting on this null node here. All right. So do that with Python here inside of Houdini. We're going to say geo, that's the variable name that's going to store all this information. I'm going to say null node dot geometry, like so. All right, we need the parentheses there. So we're pulling that actually I called it geo node. There we go. So geo node dot geometry. So pulling off the geometry, we're storing it in this geo variable there. All right. And then we need to get all the points from that geometry. So I'm going to say points is equal to geo dot points. So that's how we start to get access to all the point data. So all these points here. Okay. So now we've got all that stored in that points variable right here. What we can do now is loop through all the points and extract the data that we need. All the attributes that have been set up. All right. And so to do that, we just say four point in points. Like so. We're going to say ID is equal to point dot number. All right. So always good idea to at least throw the point number into your data. So these are kind of built in data. The other thing I'm going to build in is the position for the point. Most likely we're going to always need that. So we're going to say point dot position, like so. And then what I want to do is create an array out of that. So I'm going to say pause array is equal to the pause dot x and pause dot y, pause dot z, like so. Right. So that's how we read all the individual components from that vector, that position vector. All right. And so now we can say point dot or point data, is equal to a new dictionary. All right. And we're going to say we're going to store the ID and that's equal to our ID. So we need a comma. And then we're going to say pause is equal to our pause array. And I just needed to do that particular step to make sure it actually serializes to JSON. All right. So the next thing I want to do is I want to add on. So basically we're going to be adding on or pending more data to this point data. But these two particular bits of information are always built in. So they're always going to get exported for our point clouds. And so now what I do is I want to say user adders is equal to an empty dictionary. Okay. And now I want to go and pull off all the data. All right. Because I have the names of the attributes, but I also need to get the data for that particular attribute. And we also need to make sure that if the attribute doesn't actually exist on the point itself, it doesn't try to find it. It doesn't throw an error or anything. Okay. So let's actually walk through that. So I'm going to say 4x in adders like so. And I'm going to say if, sorry, I was doing C sharp there earlier. So I don't need the parentheses here. All right. So if geo.find point atrib. So I say point atrib like so. And we want to pass an X and we want to make sure it's not none. All right. Cool. Then we can add it to the point data. So we're going to say point data. And we're going to look for or we're going to set the name. Because remember point data is a dictionary. So I'm setting the name and then I'm going to set its value. I'm going to be point dot at your value like so. And we do X. All right. So hopefully that makes sense. We're getting the name of the attribute. And then we are pulling the value off of it. And then we're setting the key and the value right here in the dictionary. All right. So now that we've got all that all set up. All we need to do really is add it to our data. All right. So let's do that. So I'm just going to tab in there. And we are going to say, and actually I want to put this into this loop there. Yeah. So I'm going to say data dot or should say points. Just so I can access the dictionary. So points and we're going to say dot append. And we want to append point data like so. Okay. Cool. All right. We're almost done actually. And so now all we need to do is we need to write it out to a JSON file. So we're going to say JSON underscore object is equal to JSON dot dumps. And then we want to send in data and we want to indent equals four. That way it's human readable. And then we want to say sort keys is equal to true like so. And then finally with that JSON object created and all the data stored, we can write it to a file. So we'll say with open. All right. So we'll say file path or full path, I should say. And you know what? Now that I'm thinking about it, we've never added on the dot JSON. So we actually need to do that here. So I'm going to say full path plus equals dot JSON. And we need quotes around that. There we go. All right. So we'll do a full path for the path and name. We'll set it to write like so. And we'll say as out file. And then finally we will write to it. So we'll say out file dot write. And we'll send in that JSON object. And that is everything that we should need to do. So let's apply and let's actually export the points. And we'll get an atrib. We'll get an error for atrib value. And that's because it's not capitalized. This should be atrib value lowercase that apply and export points. And we get another error here. And it's because those particular items actually don't exist. So let's go take a look here. And oh, this needs to be is not none. There we go. So to apply. Let's check it one more time here. Yeah. All right. So let's go to our desktop here. So let me just open a folder. And we'll go to our desktop and we have points. And we have a JSON file here. So let's actually open this with code and take a look. Yeah. And there we go. So you can see that we got pscale, but we didn't get those other two because there were no values or they couldn't the system couldn't find it on the point itself. And so we got only the data that was available. Pretty cool. So I noticed also that it created this point data folder there. And that's because I am checking what we need to do really is check the just the dirt, not the full path here. There we go. All right. So let's do one more test here. So I'm just going to actually close code down here. So it's not open. So let's delete this here. And let's run another test. Yeah, there we go. So we didn't get that extra folder. Awesome. So that's pretty much that. Let's hit accept and just make sure we save our HDA. And let's just test this out. Let's do another attribute randomize here. And actually, you know what? I'm just going to copy the pscale one and just rename it. So we'll do rods. I just want to see if it actually picks up the extra data. So we'll call this rods, like so. And then we want a random height attribute as well. So we'll do height. There we go. And height. Yeah. All right. So with those guys actually available now on the points, let's hit export points. And let's go up here. Go to point data.json. And I keep getting release notes here. There we go. And we got our extra data. All right. So it's expandable. Or it's just it's really flexible and modular. So in generic. And so that's what I wanted to show. Thanks so much.