﻿WEBVTT

00:00:00.360 --> 00:00:03.000
Hello and welcome to Scripting for Artists. My name is Sybren.

00:00:03.000 --> 00:00:05.480
And in this episode, we're going to take a look at Custom Properties.

00:00:05.480 --> 00:00:09.000
In the last video, we created a panel

00:00:09.000 --> 00:00:12.600
and add existing properties to it. And this is already quite powerful.

00:00:12.600 --> 00:00:15.240
If that is enough for your situation, then please

00:00:15.240 --> 00:00:19.120
don't make it any more complex. Simpler is often better.

00:00:19.120 --> 00:00:21.360
However, there are these situations though

00:00:21.360 --> 00:00:25.120
where you want to attach your own data to things in Blender.

00:00:25.120 --> 00:00:27.120
To start, let's take a look at the kind of Properties there are.

00:00:27.120 --> 00:00:30.000
Throughout the series, you've seen existing Properties

00:00:30.000 --> 00:00:33.360
like "object.location" or "context.scene".

00:00:33.360 --> 00:00:35.960
Then, there are the Custom Properties that you can create

00:00:35.960 --> 00:00:38.840
in the Custom Properties panel. Maybe you've also seen these properties

00:00:38.840 --> 00:00:41.840
where the Edit button is gone and where it says API Define,

00:00:41.840 --> 00:00:46.080
and this means it's been defined from Python.

00:00:46.080 --> 00:00:48.720
This is what we're also going to look at in this video.

00:00:48.720 --> 00:00:51.600
To give us a concrete goal to work toward, will be creating

00:00:51.600 --> 00:00:55.600
a Mass OBJ Importer. Each scene will get a property that contains

00:00:55.600 --> 00:00:58.840
the path of a folder, that contains its OBJ files. And then,

00:00:58.840 --> 00:01:01.960
we'll create an operator that loads all the OBJ files from that folder.

00:01:01.960 --> 00:01:05.240
And finally,  we'll look into creating an operator

00:01:05.240 --> 00:01:08.960
that reloads those OBJ files from disk. So in that way,

00:01:08.960 --> 00:01:12.240
you can have a one-button reload of all your OBJ files.

00:01:12.240 --> 00:01:15.600
This is quite a bit of work. So let's get started. For this episode,

00:01:15.600 --> 00:01:18.080
we're going to create a new Addon and I've started already.

00:01:18.080 --> 00:01:21.120
I've added a new Python file. I've installed it

00:01:21.120 --> 00:01:24.080
as an Addon, load it in my Editor, which is a Visual Studio code.

00:01:24.080 --> 00:01:27.240
I will quickly race through this

00:01:27.240 --> 00:01:30.120
because you've seen all of these before in the previous videos.

00:01:30.120 --> 00:01:32.960
If you haven't seen this yet, please take a look at the videos

00:01:32.960 --> 00:01:36.480
about creating your own Addon and creating User Interfaces.

00:01:36.480 --> 00:01:39.000
So we start with the BL Info Dictionary. I've set the name

00:01:39.000 --> 00:01:42.480
to the Addon to Scripting for Artists "Mass importer".

00:01:42.480 --> 00:01:45.600
The rest of the information is pretty much as you would expect,

00:01:45.600 --> 00:01:48.600
"import bpy", of course, is next. Then, I want to have everything

00:01:48.600 --> 00:01:51.120
that we're going to do available to us in a panel there.

00:01:51.120 --> 00:01:53.720
We can show the buttons, we can show the properties,

00:01:53.720 --> 00:01:57.080
we can do everything we want. I've created a new panel by copying

00:01:57.080 --> 00:01:59.600
the code we had for a monkey grid and then adjusting it.

00:01:59.600 --> 00:02:02.480
So it's pretty the same, except that it has the label "mass import",

00:02:02.480 --> 00:02:06.360
and that is also reflected in the class name.

00:02:06.360 --> 00:02:11.480
It's now called "view 3D PT mass import"

00:02:11.480 --> 00:02:14.120
We're going to draw something, but not just yet. Then, of course,

00:02:14.120 --> 00:02:16.720
we have the Register and Unregister functions.

00:02:16.720 --> 00:02:20.360
Those are necessary for any Addon. We're going to add more classes to this file.

00:02:20.360 --> 00:02:23.240
So we'll have an operator for doing the mass import.

00:02:23.240 --> 00:02:26.720
Then we also will have an operator for doing

00:02:26.720 --> 00:02:30.120
a single re-import of an object, and then also an operator for doing

00:02:30.120 --> 00:02:33.480
a mass re-import of all the imported objects.

00:02:33.480 --> 00:02:36.720
So we're going to have more. And I didn't want to copy paste

00:02:36.720 --> 00:02:39.720
the "bpy.utils.unregister class", and then make sure that

00:02:39.720 --> 00:02:43.000
in the unregistered, is also copied to the unregister class,

00:02:43.000 --> 00:02:46.080
and managing all of that. So instead of that, I've created a list

00:02:46.080 --> 00:02:49.720
called Blender Classes, in which I have only the panel

00:02:49.720 --> 00:02:53.240
that we have now. And then, in the Register and Unregister,

00:02:53.240 --> 00:02:56.360
we loop over it with a Forloop. So for Blender Class and Blender Classes

00:02:56.360 --> 00:02:59.720
will go over this list. And for every item in that list,

00:02:59.720 --> 00:03:03.080
it will call "bpy.utils.register class" or "unregister class"

00:03:03.080 --> 00:03:05.960
and this makes it a little bit easier to add new stuff later,

00:03:05.960 --> 00:03:09.360
because new classes, we can just add to this list.

00:03:09.360 --> 00:03:11.960
Now that our code is really simple, let's see that

00:03:11.960 --> 00:03:14.480
it actually works in Blender, so that we can be sure that

00:03:14.480 --> 00:03:17.840
when we start adding stuff, we added on a basis that actually works.

00:03:18.960 --> 00:03:22.840
So in the User Preferences, enable the Addon. And then here,

00:03:22.840 --> 00:03:26.000
in our Monkeys tab, we can see our Mass Import panel.

00:03:26.000 --> 00:03:29.080
Of course, it's nothing drawn yet, but that is what we expected.

00:03:29.080 --> 00:03:32.120
So on the basis for our code, the registering and the unregistering

00:03:32.120 --> 00:03:36.600
in the existence of the panel itself, that's all correct.

00:03:36.600 --> 00:03:39.120
Okay. So let's start creating these properties that we wanted to have.

00:03:39.120 --> 00:03:41.480
This is quite simple actually.

00:03:43.240 --> 00:03:46.000
You know that there's a lot of stuff in "bpy.types",

00:03:46.000 --> 00:03:48.840
and we want to have a property on the scene that contains

00:03:48.840 --> 00:03:53.000
the import path of the OBJ files that we want to have.

00:03:53.000 --> 00:03:56.360
So we start with "bpy.types.Scene", with a capital Scene,

00:03:57.360 --> 00:03:59.000
"." and then the name of our property.

00:04:00.840 --> 00:04:04.080
Let's call it "Mass import path". Now, it's very similar

00:04:04.080 --> 00:04:07.120
to adding your own properties to your own operator,
except that,

00:04:07.120 --> 00:04:10.000
with the operator we had class we could use

00:04:10.000 --> 00:04:13.000
to declare the properties, and now, that class is already there,

00:04:13.000 --> 00:04:15.240
so we cannot add anything to it. So it's a little bit different.

00:04:15.240 --> 00:04:18.080
So, instead of using the ":",

00:04:18.080 --> 00:04:21.840
we use the "=" sign, and then again, we assign property.

00:04:23.720 --> 00:04:27.360
They'll even be a path of props, and a file path or a directory path

00:04:27.360 --> 00:04:29.080
is just a String.

00:04:31.000 --> 00:04:34.240
So that gives us a String Property. Now for the User Interface,

00:04:34.240 --> 00:04:37.720
we may want to give it a nice name. So let's call it "obj folder",

00:04:37.720 --> 00:04:41.240
and they always end up with a ",",

00:04:41.240 --> 00:04:44.840
because then, I can add stuff on the next line without having

00:04:44.840 --> 00:04:48.840
to go back up and add that "," again. This is valid Python,

00:04:48.840 --> 00:04:51.720
it makes your changes a bit more localized. You can now add

00:04:51.720 --> 00:04:55.480
other stuff here without changing the line above.

00:04:55.480 --> 00:04:57.960
This is in the Register function, so there, we add a property,

00:04:57.960 --> 00:05:00.480
of course, we have to delete it again in the Unregister

00:05:00.480 --> 00:05:01.840
to clean up what we did.

00:05:03.080 --> 00:05:07.840
Then basically, you take this and you say "del" that property.

00:05:07.840 --> 00:05:11.360
And that tells Python, just delete the thing. And this is enough.

00:05:11.360 --> 00:05:14.600
Let's see how this looks in Blender.

00:05:14.600 --> 00:05:18.720
Here, we have to reload our code. In Blender 2.83 and newer,

00:05:18.720 --> 00:05:21.600
can do that here with Reload Scripts. If you've an older version of Blender,

00:05:21.600 --> 00:05:23.080
press F3,

00:05:24.240 --> 00:05:27.360
then type"reload scripts". They both do the same thing.

00:05:27.360 --> 00:05:29.720
Except, through the menu, you can add it to the quick menu,

00:05:29.720 --> 00:05:33.120
when you press Q. And this is the approach that I will use

00:05:33.120 --> 00:05:36.360
from now on. So we've reloaded our code.

00:05:36.360 --> 00:05:37.080
Let's take a look at the scene.

00:05:40.000 --> 00:05:43.840
There you go. We have our own property "Mass import path".

00:05:43.840 --> 00:05:47.120
The default is empty because we haven't set any default,

00:05:47.120 --> 00:05:50.480
and it's just there, and every scene will have it.

00:05:50.480 --> 00:05:54.080
We can also change the property from Python. Keep your eyes

00:05:54.080 --> 00:05:58.120
on the Custom Properties panel here, which is in the Scene tab.

00:05:58.240 --> 00:06:01.240
Take a look at that, what happens when I actually assign a value.

00:06:04.000 --> 00:06:05.840
You have to work with your mouse a little bit to make it redraw.

00:06:05.840 --> 00:06:08.600
But you can see that, now, with that, we've assigned a value to it.

00:06:08.600 --> 00:06:13.120
It is actually stored in the Custom Properties.

00:06:13.120 --> 00:06:17.480
It's API defined, which means that you cannot just delete it.

00:06:17.480 --> 00:06:20.000
And this is one of the major advantages of using
your Properties like this,

00:06:20.000 --> 00:06:22.960
instead of just clicking the Add button here

00:06:22.960 --> 00:06:26.120
and creating a new one. When you create it through Python,

00:06:26.120 --> 00:06:29.080
you can be sure that the property is there.

00:06:29.080 --> 00:06:32.240
Even if it hasn't been set, it will have a default.
So it's one less thing to worry about.

00:06:32.240 --> 00:06:36.120
The other advantage is that, we can set Subtypes.

00:06:36.120 --> 00:06:39.480
And this will indicate to Blender

00:06:39.480 --> 00:06:42.840
what kind of String we're talking about. So this is going to be a path,

00:06:42.840 --> 00:06:45.360
and it would be nice if it had like a little button that

00:06:45.360 --> 00:06:48.600
you could have a file browser that selects the path for you,

00:06:48.600 --> 00:06:51.480
instead of having to type it in the Python console.

00:06:51.480 --> 00:06:55.080
All of this is done by Blender once you've set the Subtype correctly.

00:06:55.080 --> 00:06:58.360
Let's do that. The Subtype is set

00:07:00.080 --> 00:07:03.480
by the Subtype parameter, and then you pass a string

00:07:03.480 --> 00:07:06.360
that indicates a Subtype. In order to find the Subtypes for a specific property,

00:07:06.360 --> 00:07:09.600
you can go back to Blender, Help,

00:07:10.600 --> 00:07:15.480
Python, API Reference, and search for "bpy.props.string property"

00:07:15.480 --> 00:07:17.840
and there it will tell you which subtype there are.

00:07:17.840 --> 00:07:23.120
In our case, it's a Directory Path or "Dir Path".

00:07:23.120 --> 00:07:26.480
Now, let's reload the code and see what happens here

00:07:26.480 --> 00:07:27.720
in this corner of the Interface.

00:07:29.600 --> 00:07:33.960
There you go. It has a button to browse, and you can select directories.

00:07:33.960 --> 00:07:38.240
Because I set it to a silly value,

00:07:38.240 --> 00:07:41.600
it'll just start at a silly directory, because it's not a valid path.

00:07:41.600 --> 00:07:45.360
But if I change this to "//" which indicates

00:07:45.360 --> 00:07:47.080
the current directory of the Blend file,

00:07:48.360 --> 00:07:52.080
it will open it here, and I can navigate to my OBJ files directory,

00:07:52.080 --> 00:07:57.080
click "accept" and then it has set here the path.

00:07:57.080 --> 00:07:59.720
That is exactly what we wanted, except that we want this behavior

00:07:59.720 --> 00:08:03.960
in our own panel, of course. Let's do that. In our Panel code,

00:08:03.960 --> 00:08:06.240
we can remove the pass because we actually going to do something

00:08:06.240 --> 00:08:09.480
and let's start with getting the layout.

00:08:11.960 --> 00:08:14.840
We didn't do this before, but do know that every "self.(something)"

00:08:14.840 --> 00:08:18.480
we'll do a little dance to see, is it defined on this class,

00:08:18.480 --> 00:08:21.600
if not, look at a parent, is it defined there,

00:08:21.600 --> 00:08:25.240
if not, look at the grandparent etc.. So we're saying

00:08:25.240 --> 00:08:28.840
"layout = self.layout" ,  we only do this dance once,

00:08:28.840 --> 00:08:32.000
and then just use the layout object. Let's add a Column,

00:08:34.240 --> 00:08:37.600
and then add the Property. The Property was defined on the scene,

00:08:37.600 --> 00:08:42.240
which is "context.scene". And the property name was

00:08:42.240 --> 00:08:45.080
"mass import path". That's it.

00:08:46.600 --> 00:08:49.840
Reload the code, and there we have our Property browse button.

00:08:51.360 --> 00:08:54.080
Now, let's go and create an operator that import all the OBJ files

00:08:54.080 --> 00:08:57.840
from that directory. And I want to name it quite similar

00:08:57.840 --> 00:09:01.120
to the operator that already exists for importing OBJ files.

00:09:01.120 --> 00:09:05.240
So let's take a look at that. That is "bpy.ops.import_scene.obj"

00:09:05.240 --> 00:09:09.600
so let's call our operator

00:09:09.600 --> 00:09:13.000
"import scene.obj mass". So here, we have an empty operator,

00:09:13.000 --> 00:09:15.600
you've seen this before, I just named these differently,

00:09:15.600 --> 00:09:18.720
called "imports scene.obj mass" and it has an Execute function.

00:09:18.720 --> 00:09:22.080
I put in this little comment as a reference, and then it called

00:09:22.080 --> 00:09:25.960
"self.report". This takes a report type which can be

00:09:25.960 --> 00:09:29.960
info or warning or error, and then a string that is unreported.

00:09:29.960 --> 00:09:33.120
It returns "cancelled" because nothing is done yet.

00:09:33.120 --> 00:09:36.080
Again, I'm taking small steps. I first make sure that your printer is there,

00:09:36.080 --> 00:09:38.240
and that it registered properly. Then we can add it to a panel,

00:09:38.240 --> 00:09:41.240
we can click on the button, we can see that it works

00:09:41.240 --> 00:09:45.000
before adding more complexities to it. So the operator is here.

00:09:45.000 --> 00:09:49.120
Now, I copied this name, I go to the Blender classes list,

00:09:49.120 --> 00:09:51.720
I added the name to the list and that takes care of the Registering

00:09:51.720 --> 00:09:54.480
and the unregistering. So what is left now is add it to the panel.

00:09:56.360 --> 00:10:01.960
So all we need is the "BL ID name". Paste it in, save in Blender,

00:10:01.960 --> 00:10:05.120
reload the script. And we have a button, we click on it.

00:10:05.120 --> 00:10:08.240
It says "no code to load from / /", which is the error we expected.

00:10:08.240 --> 00:10:12.000
Now the next step is, finding the OBJ files,

00:10:12.000 --> 00:10:15.080
and importing them. And afterwards, we want to be able

00:10:15.080 --> 00:10:18.080
to re-import them. So we have to keep track of

00:10:18.080 --> 00:10:21.120
which object was imported from which file. The best way to do that is

00:10:21.120 --> 00:10:24.240
to add another property to the ObjectTtype. So let's do that now

00:10:24.240 --> 00:10:27.840
before we start importing them. Here, in a Register function,

00:10:27.840 --> 00:10:29.960
we're going to do the same thing as we did to a scene,

00:10:29.960 --> 00:10:31.840
but then to an object.

00:10:34.840 --> 00:10:38.120
And then, it also becomes a String Property,

00:10:40.960 --> 00:10:44.080
but then with OBJ file, instead of OBJ folder. So the folder

00:10:44.080 --> 00:10:47.360
and the file name combined will give us the final path of the OBJ file.

00:10:47.360 --> 00:10:51.600
Because there is no subtype for just a file name,

00:10:51.600 --> 00:10:55.000
it's only for a full file path or a full directory path.

00:10:55.000 --> 00:10:58.480
Will just keep this as a String and don't define any subtype.

00:10:58.480 --> 00:11:01.240
So before we look at the importing, that's add this property to the panel.

00:11:03.360 --> 00:11:06.840
We have the Panel code here, let's create a new column for this.

00:11:13.240 --> 00:11:15.960
And there, we have the Property. But remember "context.object"

00:11:15.960 --> 00:11:18.720
can be none, so we have to add a little guard around this.

00:11:28.840 --> 00:11:31.840
None is considered false with Python, and real objects is considered true.

00:11:31.840 --> 00:11:35.480
So if "context.object" will evaluate to "true",

00:11:35.480 --> 00:11:38.600
it's real object, and then draw the property. It will evaluate to "false"

00:11:38.600 --> 00:11:41.600
if there is no active object, and then it will draw the label instead.

00:11:41.600 --> 00:11:44.240
Let's give it a try.

00:11:46.360 --> 00:11:49.080
And there, we have our property, and we can set it "per objects",

00:11:50.000 --> 00:11:51.960
as you can see.

00:11:53.960 --> 00:11:56.480
Now let's build the importing itself. So here we are,

00:11:56.480 --> 00:12:00.360
at our Mass Importer operator. Let's write a little layout

00:12:00.360 --> 00:12:03.840
of what it is supposed to do. It has to find the object files,

00:12:05.120 --> 00:12:06.240
and for each file,

00:12:08.120 --> 00:12:09.240
import it,

00:12:11.120 --> 00:12:15.000
and record its file name in the Object Property.
Let's look at that first steps,

00:12:15.000 --> 00:12:18.240
this is going to take a few steps actually.

00:12:18.240 --> 00:12:20.360
Because for finding files, we have to use Python functionality.

00:12:20.360 --> 00:12:23.000
And Python doesn't understand the Blender specific "//" meaning

00:12:23.000 --> 00:12:26.120
of relative to the current Blend file.

00:12:26.120 --> 00:12:29.080
So we have to convert it to something that Python understands,

00:12:29.080 --> 00:12:32.480
then use that to find the OBJ files, then convert it back

00:12:32.480 --> 00:12:35.120
to something Blender understands, and import the files.

00:12:35.120 --> 00:12:38.080
First of all, we have to turn that "//" into something absolute,

00:12:38.080 --> 00:12:41.360
and this is done through a function in "bpy.path.abspath"

00:12:47.480 --> 00:12:51.480
"bpy.path.abspath(context.scene.mass_import_path"

00:12:51.480 --> 00:12:54.120
will give us the absolute path of the file that we want to import.

00:12:54.120 --> 00:12:57.080
We can check this by reporting this in our error message.

00:12:58.120 --> 00:13:01.960
Just pause the video, give this a try, and then come back here.

00:13:01.960 --> 00:13:04.840
Now, we have to convert this to a path object

00:13:04.840 --> 00:13:05.720
from Python's Path Lib Library.

00:13:12.480 --> 00:13:15.840
And then, we can use this import path variable to find all the OBJ files,

00:13:15.840 --> 00:13:19.000
and loop over them in one go, like this.

00:13:26.480 --> 00:13:29.360
This will find everything that matches "*.obj",

00:13:29.360 --> 00:13:33.080
so files or directories ending in that "obj".

00:13:33.080 --> 00:13:35.720
From now on, I'll assume that you won't create
any subdirectory called

00:13:35.720 --> 00:13:39.240
"(something).obj", and that everything is an actual OBJ file we import.

00:13:39.240 --> 00:13:42.720
Now, all we have to do is import it.

00:13:43.720 --> 00:13:44.360
In Blender,

00:13:45.600 --> 00:13:48.600
go to the OBJ Importer and press Ctrl C, and then,

00:13:48.600 --> 00:13:50.120
you can Ctrl  V, Code.

00:13:57.120 --> 00:14:00.360
This converts that Path Object we have back into a String,

00:14:00.360 --> 00:14:02.080
so that Blender understands it.

00:14:03.240 --> 00:14:05.840
And then, this whole line will import the OBJ file.

00:14:07.240 --> 00:14:11.240
So now, we have covered that for each file imported,

00:14:11.240 --> 00:14:13.600
the only thing that's left is recording its filename.

00:14:14.600 --> 00:14:17.240
Part of the working of this Import Operator is that

00:14:17.240 --> 00:14:20.120
it deselects everything and then it selects the objects that are imported.

00:14:20.120 --> 00:14:23.080
This means that we can loop over all selected objects

00:14:23.080 --> 00:14:25.360
and then assign our Custom Property to them.

00:14:39.840 --> 00:14:44.000
This is why I like the Path Lib Object so much.

00:14:44.000 --> 00:14:46.480
It's quite simple to work with if you just want to have

00:14:46.480 --> 00:14:50.840
the filename component of a path, you can just do the "path.name".

00:14:50.840 --> 00:14:54.120
If you want to have the drive object, you do ".drive"

00:14:54.120 --> 00:14:59.600
and is a very object oriented approach to Paths, and I quite like that.

00:14:59.600 --> 00:15:02.240
Now we're importing. We record the file name where it came from.

00:15:02.240 --> 00:15:05.840
So this should be it.....let's give it a try.

00:15:05.840 --> 00:15:07.120
I'll throw out the existing objects,

00:15:08.600 --> 00:15:10.240
browse to my OBJ files directory,

00:15:11.720 --> 00:15:15.000
and then Mass Import everything, and there we are. The files are imported.

00:15:15.000 --> 00:15:18.120
They're all at the origin, and each of them

00:15:18.120 --> 00:15:22.720
has the OBJ filename set to whichever they came from.

00:15:22.720 --> 00:15:25.840
Now, all that's left for us is to make an operator that reloads the OBJ file.

00:15:25.840 --> 00:15:29.080
To keep it simple, let's not do this on mass,

00:15:29.080 --> 00:15:31.480
just reload the active object. That means deleting the object from the scene

00:15:31.480 --> 00:15:34.720
and then loading it again from the OBJ file.

00:15:34.720 --> 00:15:36.960
Of course, before we deleted from the scene, we have to make sure that

00:15:36.960 --> 00:15:39.720
we stored the transform of the object, so that

00:15:39.720 --> 00:15:42.720
when we reload it, it can be restored. So that it's positioned in the same way

00:15:42.720 --> 00:15:46.360
as it was before we reloaded it.

00:15:46.360 --> 00:15:47.360
Let's start with a new operator.

00:15:50.080 --> 00:15:53.600
Let's call it "obj reload". Here, I select some text,

00:15:53.600 --> 00:15:57.080
press Ctrl D and that will select next occurrence of that text.

00:15:57.080 --> 00:16:00.600
Then, I can just type Reload, and both the class name and the ID name

00:16:00.600 --> 00:16:03.120
we'll change to "obj reload".

00:16:04.480 --> 00:16:08.360
Let's say "reload mass imported obj", and then we can write

00:16:08.360 --> 00:16:11.960
the Execute function. For this code, I will assume that every OBJ

00:16:11.960 --> 00:16:15.360
has one object in it. You can extend the code so that it can handle

00:16:15.360 --> 00:16:18.600
multiple objects for OBJ. But let's keep it simple for now.

00:16:18.600 --> 00:16:21.600
We're going to refer to the active object quite a few times,

00:16:21.600 --> 00:16:24.000
so let's make life simple for us.

00:16:25.960 --> 00:16:28.600
Just call it "ob", let's make a little overview for ourselves again.

00:16:28.600 --> 00:16:30.840
First, we have to store

00:16:32.000 --> 00:16:33.480
what we want to remember.

00:16:35.080 --> 00:16:36.600
Remove the object from the scene,

00:16:37.600 --> 00:16:38.840
load the OBJ file,

00:16:40.080 --> 00:16:44.480
and restore what we remembered. Loading the OBJ file

00:16:44.480 --> 00:16:47.360
will not automatically set our Custom Property.

00:16:47.360 --> 00:16:50.600
In the end, that's what we want to restore. And we want to be able to restore

00:16:50.600 --> 00:16:51.840
the transform as well.

00:16:55.080 --> 00:16:56.480
Let's remember the file name.

00:16:59.600 --> 00:17:03.080
And let's take a copy of the Matrix. If I wouldn't make

00:17:03.080 --> 00:17:06.840
a copy of the Matrix, then the variable Matrix World

00:17:06.840 --> 00:17:10.080
would be a reference to the Object Matrix. And if we delete the object,

00:17:10.080 --> 00:17:13.480
that reference won't be valid anymore,

00:17:13.480 --> 00:17:14.840
you can get all kinds of weird results.

00:17:16.080 --> 00:17:18.240
Now, removing the object from the scene actually means

00:17:18.240 --> 00:17:21.600
removing the object from all the collections that it's in.

00:17:21.600 --> 00:17:24.960
Fortunately, Blender gives us a list of all those collections.

00:17:33.960 --> 00:17:37.600
This will unlink the object from all the collections.

00:17:37.600 --> 00:17:40.600
The problem is that, of course, as soon as we unlink the object

00:17:40.600 --> 00:17:43.080
from the first collection, this thing that we're looping over

00:17:43.080 --> 00:17:46.000
will change. Changing something while you're looping over it

00:17:46.000 --> 00:17:50.240
is never a good idea. We can do this, "list(ob.user_collections)"

00:17:50.240 --> 00:17:54.480
and this will create a new list for us.

00:17:54.480 --> 00:17:57.840
That new list is a copy, so it won't be altered by removing the objects

00:17:57.840 --> 00:18:01.600
from those collections, making this for loop safe.

00:18:01.600 --> 00:18:04.120
If you want, you can also remove the object from "bpy.data" now,

00:18:04.120 --> 00:18:07.960
but this is only safe if it's not referenced from anything else.

00:18:07.960 --> 00:18:10.840
Again, Blender to the rescue.

00:18:14.840 --> 00:18:18.360
"ob.users" will give us that user count.

00:18:18.360 --> 00:18:22.480
 And that is that tiny little square you see also with Materials and Meshes,

00:18:22.480 --> 00:18:25.000
when you use them in different places throughout the Blend file.

00:18:25.000 --> 00:18:28.960
If this number is 0, that means that the object is no longer used,

00:18:28.960 --> 00:18:31.720
and we can actually remove it from Blender's memory.

00:18:35.720 --> 00:18:39.960
From this point on, we can no longer refer to "ob",

00:18:39.960 --> 00:18:44.000
because it's been removed from Blender.

00:18:44.000 --> 00:18:45.720
So to prevent this from ever using it again,

00:18:46.960 --> 00:18:50.600
I just say "del ob". This removes the "ob" name from Python's memory.

00:18:50.600 --> 00:18:56.360
That means that, if I say "ob.location" here,

00:18:56.360 --> 00:18:58.720
Python will scream at us, because it doesn't know

00:18:58.720 --> 00:19:03.080
what "ob" means anymore after this line.
 It's just a little bit of security for ourselves

00:19:03.080 --> 00:19:05.600
so that we don't use something

00:19:05.600 --> 00:19:09.120
that's no longer valid. Now. We need to load the OBJ file again

00:19:09.120 --> 00:19:11.480
which we've done before. So let's take a look at that code.

00:19:11.480 --> 00:19:15.600
We have to do this trick again, and then we have to do this line

00:19:15.600 --> 00:19:19.240
for just that one object. So let's put this into a function

00:19:19.240 --> 00:19:21.720
which we can call from both places.

00:19:35.360 --> 00:19:39.080
Here, I've created a new function that takes a scene,

00:19:39.080 --> 00:19:42.720
and this means that it will return a "path lib.path".

00:19:42.720 --> 00:19:45.360
I like this notation quite a lot, because then, you can see

00:19:45.360 --> 00:19:48.240
what something returns without having to dive in the code

00:19:48.240 --> 00:19:51.240
and see what happens. This is not something that will be checked at Python,

00:19:51.240 --> 00:19:54.120
so it's up to you to actually live up to your work.

00:19:54.120 --> 00:19:56.960
This code is now pretty much the same as this

00:19:56.960 --> 00:20:00.960
and it returns the new Path Object. That means that
we can remove this line,

00:20:00.960 --> 00:20:04.120
and we can just do "mass_import_path(context.scene)" here.

00:20:05.360 --> 00:20:09.360
I specifically pass this scene

00:20:09.360 --> 00:20:13.080
and not the context. Have to fix a little mistake here.

00:20:14.000 --> 00:20:18.000
I explicitly passed the scene and not the entire context.

00:20:18.000 --> 00:20:21.000
It would have worked fine if we were to pass the context,

00:20:21.000 --> 00:20:24.120
but then, you wouldn't be able to see which part of the context

00:20:24.120 --> 00:20:26.960
was actually used by this function. This is why I wanted

00:20:26.960 --> 00:20:30.080
to pass a scene itself and not the context.

00:20:30.080 --> 00:20:33.480
Of course, we could have also passed "context.scene(mass import path)",

00:20:33.480 --> 00:20:36.000
but that would make the call of it a little bit longer,

00:20:36.000 --> 00:20:39.120
I think this is a nice middle ground. But of course, you do with your code

00:20:39.120 --> 00:20:42.000
whatever you think is best. Now, we have the code to get

00:20:42.000 --> 00:20:44.240
the import path, we can copy this line

00:20:46.120 --> 00:20:47.720
into our Reload operator.

00:20:49.120 --> 00:20:51.120
And then we can get this line,

00:20:53.240 --> 00:20:57.480
and put that here. And now, all we have to do is get from the Import Path,

00:20:57.480 --> 00:21:00.480
which is a directory, to the Import File Path,

00:21:00.480 --> 00:21:05.120
which is a file name. And the Import File Path is the

00:21:05.120 --> 00:21:11.960
"import_ path / mass_import_fname" that we took a copy of up there.

00:21:13.600 --> 00:21:16.480
Path Object in Python supports this "/" notation.

00:21:16.480 --> 00:21:19.000
It wil work correctly automatically for any platform.

00:21:19.000 --> 00:21:23.000
On Windows, it uses the back slash, on Linux and Mac OS, it uses the "/".

00:21:23.000 --> 00:21:26.000
Now that we have constructed the file name,

00:21:26.000 --> 00:21:28.840
can pass it to the operator that loads the OBJ files.

00:21:28.840 --> 00:21:31.360
Now, all that's left is to restore whatever we wanted to restore.

00:21:31.360 --> 00:21:34.840
So again, we have to loop over the selected object,

00:21:34.840 --> 00:21:36.840
and set the Mass Import file name.

00:21:44.480 --> 00:21:46.080
Now, we have to restore the Matrix.

00:21:50.240 --> 00:21:52.360
Will tell Blender that the operator did its job.

00:21:55.480 --> 00:22:00.480
And then we fix two typos. Here ":", and here, has to use "collection",

00:22:00.480 --> 00:22:02.480
not "collections".

00:22:03.600 --> 00:22:06.480
Finally, what we have to do is Register and Unregister the operator

00:22:06.480 --> 00:22:08.600
and add a button to the panel.

00:22:10.840 --> 00:22:13.720
We copied the class name, we added to this list,

00:22:16.480 --> 00:22:20.000
We copy the BL ID name and added to the panel,

00:22:23.840 --> 00:22:24.720
save the file,

00:22:25.840 --> 00:22:28.120
reload the script, and there, we have our button.

00:22:30.360 --> 00:22:34.120
As you can see, we click on it. And as you use from importing OBJ files,

00:22:34.120 --> 00:22:37.360
there is no active object anymore,

00:22:37.360 --> 00:22:41.600
just imported objects are selected, in this case, Suzanne.

00:22:41.600 --> 00:22:42.960
Now, let's see if it really reloads.

00:22:44.120 --> 00:22:48.240
I put a new file in there "cube.obj", and now it should replace Suzanne

00:22:48.240 --> 00:22:52.360
with that Cube. And it does, so there we have it.

00:22:52.360 --> 00:22:56.480
Our code is working, we can mass load OBJ files,

00:22:56.480 --> 00:22:59.600
and we can select individual objects to reload. I will leave it as an exercise

00:22:59.600 --> 00:23:02.840
for you to make a mass reload operator

00:23:02.840 --> 00:23:06.080
that iterates over all the objects, or maybe only all the selected objects.

00:23:06.080 --> 00:23:09.240
I'll leave that up to you. So this is it for this episode

00:23:09.240 --> 00:23:12.000
of Scripting for Artists. This is not the last time

00:23:12.000 --> 00:23:14.480
we'll talk about Custom Properties,

00:23:14.480 --> 00:23:17.120
but I think this video has gone on long enough already. If you have any questions

00:23:17.120 --> 00:23:19.720
but I think this video or comments, leave them below,

00:23:19.720 --> 00:23:20.120
and I will see you soon.