Janet's Notes

Miscellaneous Articles Trying to be Useful


Project maintained by JanetRossini

Continuing Import

If I recall from yesterday, I made a bit more progress but didn’t write it up. I tried about 97 times to set the starting folder,. Today I want to consolidate that gain.

Yesterday I worked quite often based on advice from ChatGPT, which tells a more credible-sounding story than most of the Internet. It’s just that it only seems to know what it’s doing. Often I can spot when it’s clearly wrong, but when it’s almost right, but not right, it can take some time to figure out whether Ive misunderstood, or it has.

And, with simple questions, it’s actually right fairly often. These things are dangerous: in an area where I was not expert and where I had no way to test what I was told, it would be easy to take its advice as true, since it sounds like the true things that experts would say.

It’s a parrot. It doesn’t know what it’s saying: it’s just parroting words and phrases and snippets that it has found amid the volumes of material it has read.

Anyway, along the way I decide not to use the ImportHelper, because I was unable to get the file browser that comes up to start on the folder I wanted. Somehow I found out that you can do the job without ImportHelper and here is my new version of the file import:

class RCG_OT_importfromfile(bpy.types.Operator):
    "Add object from file"
    bl_idname = "rcg.importfromfile"
    bl_label = "Import"
    bl_options = {"REGISTER", "UNDO"}

    filename_ext = '*.blend'
    filter_glob: StringProperty(default="*.blend", options={'HIDDEN'})
    filepath: bpy.props.StringProperty(subtype="FILE_PATH")
    directory: bpy.props.StringProperty(subtype="DIR_PATH")

    @classmethod
    def poll(cls, context):
        return context.mode == "OBJECT"

    def invoke(self, context, event):
        self.directory = project_data_path()
        context.window_manager.fileselect_add(self)
        return {"RUNNING_MODAL"}

    def execute(self, context):
        file_path = self.filepath
        directory = os.path.join(file_path, 'Object')
        _path, file_with_ext = os.path.split(file_path)
        filename, _ext = os.path.splitext(file_with_ext)
        self.report({"INFO"}, "adding " + file_path)
        bpy.ops.wm.append(filepath=file_path,
                          directory=directory,
                          filename=filename)
        item = bpy.data.objects[filename]
        item.select_set(state=True, view_layer=bpy.context.view_layer)
        bpy.context.view_layer.objects.active = item
        return {"FINISHED"}

I think I can explain how this works, though I cannot explain why they do it this way.

The poll method, as always, specifies whether Blender’s state is such that the operator can proceed. In our case, it has to be in ‘OBJECT’ mode.

The invoke method is called when the button is clicked, or whatever triggers the operator. In our case, we intend to open a file selector, what I call a file browser. This is code we have not seen before in this series, because ImportHelper hides this operation from us by way of “helping”. Because we’re doing it explicitly, we are able to set up the directory member, which we had to declare above. I think that ImportHelper declares it also, but it’s squirreled away. Once we have set directory, we open the file selector and return ‘RUNNING_MODAL’. Why? Well, because we want to wait for the file selection. Somewhere inside Blender, it knows not to carry on until the selector has terminated. Then it calls execute.

At that point, in execute, self-filepath will be set to the file path we chose in the browser, which in our case will be a ‘.bland’ file such as ‘track.blend’. Our convention on these files is that you are to append the object whose name is the same as the name of the file, in the directory ‘Object’, in the file.

I do not fully understand the Blender file structure. (That is a profound understatement!) I just know that that sequence will load the desired object, because the files are set up so that that works. I just work here, y’know.

I must mention project_data_path(). It looks like this, today:

def project_data_path():
    home = os.path.expanduser('~')
    working = os.path.join(home, 'PycharmProjects', 'coaster', 'coasterobjects')
    return working

We are running our roller coaster generator from the script window, but we want to make it an add-on. It turns out that add-ons and scripts in the script window run with different working folders. In the case of text window ones, they run with the user root at the folder. When running in a text window, we want to default to take the data from the project, and that’s the only case we handle today So we compute the user-based path to the project, down to the folder that contains the ‘.blend’ files, namely ‘coasterobjects’.

I believe that this much works, and for today I think we’ll just verify that, trim out the specialized buttons, as requested, and maybe do an experiment to find out how to tell whether we are running as an add-on and if so, what our working folder is.

Down to work.

I open Blender, load the script and create all four of the objects in the folder. Works as intended. Now let’s see about the menu.

I comment out a few items, in case they want to go back. (Yes, I know they are in Git. I just like to keep them close for a while.) We have this layout.

object menu

Commit, ready for DS to check it out.

Getting Info

I think the only other thing I’d like to do this morning is find out a bit about how to tell whether we are running as an add-on and what the working folder will be in that case. I can do the latter with my existing simple add-on file.

First experiment was just to display the result of os.getcwd, the working folder, and that comes back as ‘/’, not what I had in mind. I will have to ask around.

This code in the add-on returns a likely folder deep inside Blender’s support data:

addon_dir = os.path.dirname(__file__)

What will that display when not an add-on? Let’s find out.

    def execute(self, context):
        addon_dir = os.path.dirname(__file__)
        self.report({"INFO"}, "file path " + addon_dir)
        ...

That reports back with just ‘/’, so there’s our information. If we get ‘/’ from that call we’re not an add-on, otherwise we are an add-on and we can use that info as the base for finding our files.

I’ll save that for another session, perhaps this afternoon. For now, I’ll take a well-deserved break.