Hello and welcome to scripting for artists.
My name is civilian and in this episode we're
going to take a look at custom properties.
In the last video we created a panel and added existing properties to it
and this is already quite powerful and if that is enough for
your situation then please don't make it any more complex on that
simpler is often better.
However,
there are these situations though where you want
to attach your own data to things in blender
to start,
let's take a look at the kind of properties there are throughout the series,
you've seen existing properties like object location or context not seen.
Then there are the custom properties that you
can create in the Custom properties panel.
Maybe you've also seen these properties where the edit button is gone
and where it says api defined and this means it's been defined from python.
This is what we're also going to look at in this video to give
us a concrete goal to work towards will be creating a maso BJ importer.
Each scene will get a property that contains the
path of the folder that contains its object files.
And then we'll create an operator that loads all the object files from that folder.
And finally,
we'll look into creating an operator that reloads those object files from disk.
So in that way you can have one button reload of all your O J files.
This is quite a bit of work. So let's get started for this episode.
We're going to create a new add on and I've started already.
I've added a new python file.
I've installed it as an ad unloaded in my editor which is visual studio code.
I will quickly raise through this because you've seen
all of this before in the previous videos.
If you haven't seen this yet,
please take a look at the videos about creating your own add on
and creating user interfaces. So we start with the bill info dictionary.
I've said the name to the add on to scripting for artists mass importer.
The rest of the information is pretty much as she would expect import beep.
I of course comes next.
Then I want to have everything that we're going to do available
to us in a panel there we can show the buttons,
we can show the properties we can do everything we want.
I've created a new panel by copying the code we had for
a monkey grid and then adjusting it so it's pretty much the same
except that it has the label mass import and that is also reflected in the class name.
It's now called View three D. P. T. For panel type mass import.
We're going to draw something but not just yet.
Then of course we have to register and unregistered functions.
Those are necessary for any adult.
We're going to add more classes to this file so we'll have
an operator for doing the mass import and then also will have an
operator for doing a single reimport of an object and then also
an operator for doing a mass reimport of all the imported objects.
So we're going to have more and I didn't
want to copy paste the beep Mariota's registered class.
And then make sure that in the unregistered also copied
to the unregistered class and and managing all of that.
So instead of that, I've created a list called blender classes,
in which I have only the panel that we have now.
And then in the registered unregistered, we look over it with a for loop.
So for blended class and gender classes will go over this list.
And for every item in that list it will
call people you to register class or unregistered clasp.
And this makes it a little bit easier to add new stuff later because new classes,
we can just add to this list.
Now that our code is really simple.
Let's see that it actually works in blender so that we can be sure
that when we start adding stuff we added on a basis that actually works.
So in the years of preferences enable the add on.
And then here in our monkey stop, we can see our mass import panel.
Of course there's nothing drawn yet, but that is what we expected.
So the basis for a code the registering the
unregistered and the existence of the panel itself.
That's all correct.
Okay, so let's start creating these properties that we wanted to have.
This is quite simple actually.
You know that there's a lot of stuff in the product types and we want to have a property
on the scene that contains the important path of the O J files that we want to have.
So we start with the pipe types dot scene with a capital c,
dots and then the name of her property.
Let's call it mass import path.
Now it's very similar to adding your own properties to your own operator.
Except that with the operator we had our own class that we could use to declare
the properties and now that class is already there so we cannot add anything to it.
So it's a little bit different. So instead of using the column, we used the equal sign
and then again we assign property
and
they live in the pilot props
and a file path or a directory path is just a string.
So that gives us a string property.
Now for the user interface we may want to give it a nice name.
So let's call it a BJ folder
and they always end up with a coma because then I can add stuff on
the next line without having to go back up and at that coma again,
this is valid python and it makes your changes a bit more localized.
You can now add other stuff here without changing the line above.
This is in the register function. So there we add a property.
Of course we have to delete it again in the unregistered to clean up what we did.
Then basically you take this and you say del
that property and that tells python just delete the thing
and this is enough. So let's see how this looks in blender here.
We have to reload our code in blender 2.83
nearer. You can do that here with reload scripts.
If you run an older version of blender press F three
and then type reload scripts.
They both do the same thing except that through the menu you
can add it to the quick menu when you press Q.
And this is the approach that will use from now on.
So we've reloaded our code and let's take a look at the scene.
There you go. We have our own property. Mass import path.
The default is empty because we haven't set any default
and it's just there and every scene will have it.
We can also change the property from python and keep your eyes
on the Custom Properties panel here which is in the scene tab.
Take a look at that. What happens when I actually assign a value.
Mhm.
You have to whether your mouth a little bit to make it redraw but
you can see that now that we have assigned a value to it.
It is actually stored in the custom properties.
It's a p I defined which means that you cannot just delete it.
And this is one of the major advantages of using your properties like this
instead of just clicking the add button here and creating a new one.
When you create it through python, you can be sure that the property is there,
even if it hasn't been set, it will have a default.
So it's one less thing to worry about.
The other advantage is that we can set subtypes and this
will indicate to blender what kind of string we're talking about.
So this is going to be a path
and it would be nice if it had like a little button that you could have a file
browser that selects the path for you instead of
having to type it in the python console.
All of this is done by blender. Once you set the subtype correctly. So let's do that.
The subtype is set by
the sub type parameter and then yet pass a string that indicates
a subtype in order to find the subtypes for a specific property.
You can go back to blender,
help,
python, api reference
and search for bp dot props of string property.
And there will tell you which sometimes there are in
our case it's a directory path or deer path.
So now let's reload the code and see what
happens here in this corner of the interface.
There you go.
It has a button to browse
and you can select directories
because I set it to a silly value.
It will just start at a silly directory because it's not a valid path.
But if I change this to slash slash which
indicates the current directory of the blend file.
Mm
It will open it here and I can navigate to my O B J files directory, click accept.
And then it has said here the path
and this is exactly what you wanted.
Except that we want this behavior in our own panel of course. And let's do that
in our panel code.
We can remove the past because we're actually going to
do something and let's start with getting the layout.
So we didn't do this before but do know that every
self dot something will do a little dance to see.
Is it defined on this class. If not look at the parents is defined there.
If not look at the grandparents et cetera.
So we're saying layout equals self taught layout.
We only do this dance once and then just use the found layout object.
That's not a column.
Okay.
And then at the property
the property was defined on the scene which is context dot scene.
And the property name was mass
import path. That's it.
Well look the code and there we have our property browse button and all.
Now let's go and create an operator that imports
all the O B J files from that directory.
And I want to name it quite similar to the
operator that already exists for importing a BJ files.
So let's take a look at that
That is be piled on top start import scene that LBJ.
So let's call our operator importing that LBJ mass.
So here we have an empty operator. You've seen this before.
I just named it differently. It's called importing that LBJ mass.
It has an execute function.
Are put in this little comment as a reference and then it calls self report.
This takes a report type which can be info or warning
or error and then a strain that is um reported.
It returns councils because nothing is done yet again, I'm taking small steps.
I first make sure that the operator is there and that it registered properly.
Then we can add it to a panel.
We can click on the button,
we can see that it works before adding more complexities to it.
So the operator is here.
Now I copied this name, I go to the blender classes list,
I add the name to the list and that
takes care of the registering and the un registering.
So what is left now is added to the panel.
So all we need is the
B O I D. Name, paste it in, save in blender.
Reload the script and we have a button, we click on it.
It says no code to load from slash slash
which is the error that we expected.
Now the next step is finding the object files and importing them
and afterwards we want to be able to re import them.
So we have to keep track of which object was imported from which file,
the best way to do that is to add another property to the object types.
So let's do that. Now, before we start actually importing them
here in a register function,
we're going to do the same thing as we did to a scene but then to an object
because
and then it also becomes a string property.
But then with BJ file instead of O J folder,
so the folder and the file name combined will give us the final path of the O J file
because there is no subtype for just a file name.
It's only for a full file path or full directory path.
We'll just keep this as a string and don't define any subtype.
So before we look at the importing that's at this property to the panel,
we have the panel code here,
let's create a new column for this
and there we have the property. But remember context object can be none.
So we have to add a little guard around this.
Okay?
Yeah.
Mhm.
None is considered false. With python and a real object is considered true.
So if context object will evaluate it true if
it's real object and then draw the property.
It will evaluate to false.
If there is no active object and then it will draw the label instead.
Let's give it a try.
Yeah.
And there we have our property and we can set it for object as you can see
now, let's build the importing itself.
So here we are at our mass importer operator,
let's write a little layout of what it is supposed to do, has to find the object files.
And for each file,
import it
and record his file name in the object property.
Let's look at that first step.
This is going to take a few steps actually
because for finding files we have to use python
functionality and python doesn't understand the blender specific slash
slash meaning of relative to the current blend file.
So we have to convert it to something that python understands.
Then use that to find the O B J files.
Then convert it back to something blender understands
and import the files.
First of all,
we have to turn that slash slash into something absolute And
this is done through a function in bp dot path.
Mhm.
Okay.
Be part of the path of a B S path or absolute path will
give us the absolute path of the file that we want to import.
We can check this by reporting this in our error message.
Just pause the video, give this a try and then come back here.
Now we have to convert this to a path object from python's path lib library
and then we can use this import path variable to find all the
O B J files and loop over them in one go like this.
Mm.
This will find everything that matches. Start at LBJ.
So files or directories ending in dot oh, BJ.
Now,
for now we'll just assume that you won't
create any subdirectory called something 0.0.0 b J.
And that everything is an actual LBJ file that we can import now.
All we have to do is import it,
your blood sugar.
Go to the LBJ importer and press control C and then you can control V in your code.
This converts that path object that we have back
into a string so that blender understands it.
And then this whole line will import the LBJ file.
So now we have covered the for each file imported.
And the only thing that's left is recording its file name.
Part of the working of this import operator is that it
dislikes everything and then it selects the objects that are imported.
And this means that we can loop over all selected
objects and then assign our custom property to them.
This is why I like the path lib objects so much. It's quite simple to work with.
If you just want to have the final name, component of a path,
you can just do the path dot name.
If you want to have to drive letter, you do not drive
and it's a very object oriented approach to paths and
I quite like that.
Now we're importing, we record the file name where it came from.
So this should be it. Let's give it a try.
I'll throw out the existing objects
browse to my O J. Fast directory
and then mass import everything.
And there we are. The files are imported.
They're all at the origin and each of them has the O. J.
File name said to whichever they came from.
Now all that's left for us is to make an operator that reloads the O.
B J file to keep it simple. Let's not do this a mass and just reload the active object.
That means deleting the object from the scene and then loading it again from the O.
J file.
Of course,
before we deleted from the scene we have to make
sure that we stored the transform of the object so that
when we reloaded it can be restored so that it's positioned
in the same way as it was before we reloaded it
1st. Let's start with a new operator.
Let's call it a BJ. Reload here. I select some text. I press Control D.
And that will select the next occurrence of that text.
So then I can just type reload and both the class name
and the idea name will will change to 20 B J.
Reload
let's say reload mass, imported O B J.
And then we can read the execute function for this code.
I will assume that every O B J has one object in it.
You can extend the code so that it can handle multiple objects for a BJ.
But let's keep it simple for now
we're going to refer to the active object quite a few times.
So let's make life simple for us
and just call it a job. So let's make a little overview for ourselves. Again,
first we have to store what we want to remember,
remove the object from the scene, load the LBJ file
and restore what we remembered,
loading the A P. J file will not automatically set our custom property in the end.
That's what we want to restore.
And we want to be able to restore the transform as well.
Let's remember the file name
and let's take a copy of the matrix.
If I wouldn't make a copy of the Matrix,
then the variable Matrix world would be a reference to the objects matrix.
And if we delete the object, that reference won't be valid anymore.
And you can get all kinds of weird results.
Now,
removing the object from the scene actually means removing
the object from all the collections that it's in.
Fortunately blender gives us a list of all those collections.
Yeah,
this will unlinked the object from all the collections.
But the problem is that of course as soon
as we unlinked the object from the first collection,
this thing that we're looping over will change.
Changing something while you're looking over. It is never a good idea.
We can do this. List, update users collections
and this will create a new list for us.
That new list is a copy.
So it won't be altered by removing the objects from the collections.
Making this for loop safe
if you want. You can also remove the object from being part of data now.
But this is only safe if it's not referenced from anything else.
Again, blended to the rescue.
Okay,
update users will give us the user account
and that is that tiny little square that you
see also with materials and measures when you use
them in different places throughout the blend file.
If this number is zero,
that means that the object is no longer used
and we can actually remove it from lenders memory.
Mhm
Yeah.
And there we go
from this point on.
We can no longer refer to ob because it's been removed from lender.
So to prevent this from ever using it again,
I just say dal ob. This removes the job name from python's memory.
That means that if I say up the location here,
python will scream at us because it doesn't know what that means anymore.
After this mine,
it's just a little bit of security for ourselves so
that we don't use something that maybe no longer valid
now. We need to load the LBJ file again. We treat them before.
So let's take a look at that code.
We'll have to do this trick again and then we
have to do this line for just that one object.
So let's put this into a function that we can call from both places.
Okay
here, I've created a new function
that takes a scene and this means that it will return a path lit dot path.
And I like this notation quite a lot because then you can see what
something returns without having to dive in the code and see what happens.
This is not something that will be checked by python.
So it's up to you to actually live up to your word.
This code is now pretty much the same as this and it returns to new path object.
That means that we can remove this line
and we can just do
mass import path
contact. Start seeing here.
I specifically past is seen and not the context
how to fix a little mistake here,
I explicitly passed the scene and not the entire context.
It would have worked fine if we were to pass the context,
but then you wouldn't be able to see which part
of the context was actually used by this function.
This is why I wanted to pass a scene itself and not the context.
Of course we could have also passed context.
Not seen that mass import path, but that would make the call of a little bit longer.
And I think this is a nice middle ground. But of course you do with your coat.
Whatever you think is best.
Now we have two code to get the import path. We can copy this line
into a reload operator.
And then we can get this line
and put that here
And now all we have to do is
get from the import path which is a directory to the import file path,
which is a file name.
And the import file path is the import path slash mass
import file name that we took a copy of up there.
Path objects in python supports the slash notation.
It will work correctly automatically for any platforms on windows.
It uses the backslash on Lennox and a macro as it uses the forward slash
now that we have constructed the file name,
we can pass it to the operator that loads the LBJ file.
Now all that's left is to restore whatever we wanted to restore.
So again we have to loop over the selected objects and set the mass import file name.
Mm hmm.
Now we have to restore the matrix.
Okay.
We'll tell blender that the operator did his job.
And then we fix two typos
Emily here Colin
and here it has to be user collection, not user collections.
Finally,
what we have to do is registered and unregistered
the operator and add a button to the panel.
We copy the class name we added to this list.
Mm hmm.
We copy
the blog name and added to the panel.
Okay,
save the file.
Reload the script
and there we have our button.
As you can see we click on it and as you use from importing LBJ files,
there is no active object anymore and they're just
imported objects are selected in this case Suzanne.
Now let's see if it really, really looked.
I put a new file in their cube, that LBJ
and now it should replace Suzanne with that cube
and it does. So there we have it. Our code is working.
We can mass load O J files and we can select individual objects to reload.
I will leave it as an exercise for you to make a mass reload operator
that it rains over all the objects or maybe only all the selected objects.
I'll leave that up to you.
So this is it for this episode description for artists.
This is not the last time we'll talk about custom properties,
but I think this video has gone on long enough already.
If you have any questions or commons, leave them below and I will see you soon.