Making File-using Operators obey Object, Action, Settings

I’m trying to make my import plugin obey the ‘Object, Action, Settings’ UI model - that is, the import should happen with default settings (after choosing a file), but the property panel should remain in the Tool area to allow one to tweak the settings and reimport with those new settings.

There is a very nice tutorial for this here

However I don’t understand what is happening well enough to adapt this to the case where one of the properties needs to be a file picked by the file selector. My current code for the import operator has:

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

  def execute(self, context):
    self.action(context)
    return {"FINISHED"}

where ‘action’ does the work of making a new object based on the current properties from the tool panel and leaving the object selected. With this code, choosing the operator from the menu causes the modal file selector to pop up, and when you push the import button on that, it does all of the work and the tool panel properties go away.

In one attempt to imitate the above tutorial, I put the filepath property (a string property, with subtype = ‘FILE_PATH’) into the draw method - then it shows up with a little file selector box you can use to select the file. Then I changed the invoke code to:

  def invoke(self, context, event):
    self.action(context)
    return {"FINISHED"}

This sort of works. Now when you pick the menu entry, it does nothing except put up the properties in the tool panel. If I then choose a file for the filepath property, nothing happens - why? If I make any change at all to another parameter, then it does as expected: the import happens and the panel stays up so that if I change another parameter it reimports with the new parameters, as desired.

I have two questions/problems:

  • Why does nothing happen when you just choose a file? What changes in the property panel cause the operator’s execute function to be called again?
  • It is clunky not to have the menu operation go straight to the file selector window. How can I have that happen, yet chain into code that leaves the panel up for further tweaking? What is it about returning ‘RUNNING_MODAL’ from invoke after adding file_select to the window manager that causes the desired behavior of the property panel hanging around not to work any more?
  • Does anyone know of any example code that accomplishes what I am trying to do - that is, go straight to a file selector when an operator is first invoked but then leave the property panel up so that the operator will be re-executed every time the user changes a property?

You can have settings show up in the file selector to be chosen by the user at the same time as the file…somehow.

I believe the obj importer does this.

Thanks for the response Uncle Entity, but that’s not what I want to do - that was already happening. What I’d like is for the settings panel to stay there after the file selector is dismissed and the first import happens, so that the user has a chance to interactively try different settings (that effect, for example, the quality of the curve approximation). I had a user request for this, and I also think it is the right thing to do. I’ve just added options for extruding and beveling that make such a thing even more useful to have interactive control over.

i have on idea

this depend where you put your panel
if you put it in the properties bar (vertical right bar in 3dview) it does’t disappear


bl_space_type = "VIEW_3D"
bl_region_type = "UI"

Could you put an operator call to your operator in the code after the first import… hmm actually an operator call as a return value.

Hi Howard,

I’ve got the same problem. Using a string property like

    sculpt_map = StringProperty(name='Sculpt Map',
        subtype='FILE_PATH')

in an operator, the string isn’t set on first selection of a file - it still shows as “” from python. If you adjust another property or set the filename again, then the string is updated to match what was selected in the file dialog.

I’m leaning towards this being a bug, I just haven’t got around to doing an isolated test script to include with a bug report :frowning:

What I’d like is for the settings panel to stay there after the file selector is dismissed and the first import happens, so that the user has a chance to interactively try different settings (that effect, for example, the quality of the curve approximation).

Something like this?

http://wiki.blender.org/index.php/Extensions:2.5/Py/Scripts/Import-Export/NASA_IMG_Importer

Look here:
http://wiki.blender.org/index.php/Extensions:2.5/Py/Scripts/Import-Export/NASA_IMG_Importer#UI_Explanation

Ciao
VB

That’s certainly not what I need. We should have had a file dialog to choose the initial file. Then something like your example shows but without the “Import” button as the operator display. At this point the execute part of the operator should have already run. So the imported mesh (if that’s what we are doing) would be there. We could now change the settings to alter the mesh (regenerated through execute after each change) or even select a different image.

I’ve got to say, that importers technique of stuffing temp variables into the scene doesn’t look like an approach I’d want either. The variables should be self contained in the operator.

Here’s code that shows the problem. Hit space over 3d view and type “test” to find them :wink:

import bpy


class TestModalFilePath(bpy.types.Operator):
    bl_idname = "mesh.test_modal_file_path"
    bl_label = "Test Modal File Path Operator"
    bl_options = {'REGISTER', 'UNDO'}

    filename = bpy.props.StringProperty(
        name='Some file', subtype='FILE_PATH')
    number = bpy.props.IntProperty(name='Some number')
    label_text = "Operator is not shown on return to 3D view"

    def draw(self, context):
        self.layout.prop(self, 'filename')
        self.layout.prop(self, 'number')
        self.layout.label(self.label_text)

    def execute(self, context):
        self.label_text = "%s : %s" % (self.number, self.filename)
        return {'FINISHED'}
    
    def invoke(self, context, event):
        wm = context.window_manager
        wm.fileselect_add(self)
        return {"RUNNING_MODAL"}


class TestFilePath(bpy.types.Operator):
    bl_idname = "mesh.test_file_path"
    bl_label = "Test File Path Operator"
    bl_options = {'REGISTER', 'UNDO'}

    filename = bpy.props.StringProperty(
        name='Some file', subtype='FILE_PATH')
    number = bpy.props.IntProperty(name='Some number')
    label_text = ""

    def draw(self, context):
        self.layout.prop(self, 'filename')
        self.layout.prop(self, 'number')
        self.layout.label(self.label_text)
        self.layout.label("filename is not updated")
        self.layout.label("when set from icon")
        self.layout.label("adjust number to show changes")

    def execute(self, context):
        self.label_text = "%s : %s" % (self.number, self.filename)
        return {'FINISHED'}


def register():
    bpy.utils.register_class(TestFilePath)
    bpy.utils.register_class(TestModalFilePath)


def unregister():
    bpy.utils.unregister_class(TestFilePath)
    bpy.utils.unregister_class(TestModalFilePath)

if __name__ == "__main__":
    register()


Besides highlighting another problem, the “Test File Path Operator” example shows what we are expecting to see after selecting a file in “Test Modal File Path Operator”.

So are we being dumb or finding bugs?

Something like this? (I don’t like but perhaps it work)

import bpy
imported=False
def Importer(File):
    global imported
    #Code to import data
    imported=True #Only if the import is ok else imported=False    
    print("** Imported **")
 
class Img_Importer(bpy.types.Panel):
    bl_space_type = "VIEW_3D"
    bl_region_type = "TOOL_PROPS"
    bl_label = "YOUR LABEL"
 
    def __init__(self):
        typ = bpy.types.Scene
        var = bpy.props
        typ.FromLat = var.FloatProperty(description="From Latitude", min=-90.0, max=90.0, precision=3)
        typ.ToLat = var.FloatProperty(description="To Latitude", min=-90.0, max=90.0, precision=3)
        typ.fpath = var.StringProperty(name="Import File ", description="Select your img file", subtype="FILE_PATH", default="")
        try:
            if not imported and bpy.context.scene['fpath'] != "":
                Importer(bpy.context.scene['fpath']) #Here you call the proc to import the data
        except:
            pass
 
        if imported:
            #check parameter on UI and do your task
            print("Executed")
    def draw(self, context):
        layout = self.layout
        if not imported: 
            layout.prop(context.scene, "fpath")
        else:
            col = layout.column()
            split = col.split(align=True)
            split.prop(context.scene, "FromLat", "Northernmost Lat.")
            split.prop(context.scene, "ToLat", "Southernmost Lat.")
# registering the script
def register():
    bpy.utils.register_module(__name__)
def unregister():
    bpy.utils.unregister_module(__name__)
if __name__ == "__main__":
    register()

I don’t understand why you are avoiding doing it as an Operator. If everything worked as expected, then the TestModalFilePath class would be all we need.

The only reason TestFilePath is there is to show a second problem that we don’t get to see because the modal operator doesn’t get shown in the last used operator panel like we’d expect.

Either there’s a bug in my Python code from doing something stupid, or there’s a bug in Blender preventing it working correctly…

Rewriting it a different way doesn’t solve either problem.

Edit: I’ve logged them as bug reports now Icon Problem, Modal Problem

Thank you for tracking this down, testing, and filing bugs, Domino.

Domino you are making two errors in your code

  1. for label you must use text=‘something’
    layout.label(text=’…’)
    2)if you want a property to be updated corretly you must use RNA properties also for your “label_text” property
    label_text = bpy.props.StringProperty(name=‘labelText’)
    in your draw function you can also use number and filename for your label
    layout.label(text=str(self.number)+self.filename)

uilayout.label takes two parameters text and icon, besides using them named, you can also use them positionally. so layout.label(“This works”, “INFO”) is also possible.

label_text is just to show what’s happening in the execute function. In a real operator I’d be working on the object, changing it’s mesh rather than changing a text value for display. It’s not a property, it’s just a python variable so it’s perfectly valid to use it like this.

Just tried it and returning the operator call works fine. Set up an operator (eg bl_name=“addon.import_file”) that invokes the fileselector in the execute method have



if somecondition :
    return bpy.ops.addon.import_file('INVOKE_DEFAULT')
return {'FINISHED'}

you will still be in the ;file select until “somecondition” is met. I think that is what you were after.

No, we want to come out of the file select, but stay in the operator. Potentially we may recall the file select by clicking the dialog icon next to our filename.

Oh Ok in that case, also put a condition in your invoke method

Now I’m assuming you are talking about the modal problem, not the icon one. There’s isolated test code for the modal problem here: http://projects.blender.org/tracker/download.php/9/498/26619/15530/test_modal_file_path.py

Could you explain against that code exactly what changes you made to get it work?

Domino,

I would set up one operator to do this. The change I would make is to get rid of using the filetype subclass and use layout.operator(“myoperatorname”,icon=“FOLDER”).someprop = somepropvalue

to call it. You can then invoke the windowmanager filebrowser in the invoke method , draw your own import panel in the filebrowser , execute whatever based on the filename and then return the operator.