1) The dialog is supposed to start with the default settings. A new operator instance is created as you open it, and the default values are taken from the property definitions. The redo panel works differently, it revokes what the last operator did, but keeps the values in the UI and re-executes with new settings.
One could say, the redo panel relates to the last operator, whereas your dialog is part of a new operator.
2) Operators remember their settings to allow for redo / repeat functionality, see Operator Design.
The operator log is available to python:
bpy.context.window_manager.operators
You could read the value used for a translation like Operator.properties.value
. But I wouldn't recommend to iterate the log in reverse direction until you find an instance of your operator, just to read the last used settings.
How about this generic solution instead, which stores the settings at python class level:
Use a static variable (dictionary) to store the operator settings on execute()
, and load them on invoke()
into self.properties
. These two methods can be defined together with the static dict as separate class, and be mixed-in into the operator class:
import bpy
class OperatorSettings:
# Static member
_settings = {}
def save_settings(self):
for d in dir(self.properties):
if d in {'bl_rna', 'rna_type'}:
continue
try:
self.__class__._settings[d] = self.properties[d]
except KeyError:
# catches __doc__ etc.
continue
def load_settings(self):
# what exception could occur here??
for d in self.__class__._settings:
self.properties[d] = self.__class__._settings[d]
class DATA_PT_test(bpy.types.Panel):
bl_label = "Test Panel"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "data"
def draw(self, context):
self.layout.operator("object.test_invoke")
class OBJECT_OT_test_invoke(bpy.types.Operator, OperatorSettings):
bl_idname = "object.test_invoke"
bl_label = "Test"
bl_options = {'REGISTER'}
width = bpy.props.IntProperty(name="Width",
default=10, min=0, max=100)
height = bpy.props.IntProperty(name="Height",
default=20, min=0, max=100)
def execute(self, context):
self.report({'INFO'}, "w:{}, h:{}".format(self.width, self.height))
self.save_settings()
return {'FINISHED'}
def invoke(self, context, event):
self.load_settings()
wm = context.window_manager
return wm.invoke_props_dialog(self)
def register():
bpy.utils.register_module(__name__)
def unregister():
bpy.utils.unregister_module(__name__)
if __name__ == "__main__":
register()
Should work for base types (int, float, str), but will probably fail for CollectionProperty
as it is. But should be possible if you implement (de-)serialization functions for such propert types.
If you add non-property class variables to the operator, make sure to exclude them in save_settings()
, or add a type check for all members - see the bl_rna
property for that:
self.properties.bl_rna.properties['...'].type # e.g. 'INT'
BTW: I removed 'UNDO'
from bl_options
'cause every change in the redo panel causes a re-execution of the operator. This is not the case for the dialog, the operator is run once on confirmation. Such dialogs should be used if an operator takes a long time to finish, to avoid irresponsive UI. So it's probably not supposed to have the redo functionality enabled.
3) There's another approach:
Register global properties (e.g. on bpy.types.Object
or bpy.types.WindowManager
) and add them to a panel. Let the user set them before (s)he clicks an Execute button. Pass the global properties to the operator. Global properties are remembered, so it works similar to the redo panel.
Example:
import bpy
class PrintSomeString(bpy.types.Operator):
bl_label = "Print Some String"
bl_idname = "object.print_some_string"
bl_options = {'REGISTER', 'UNDO'}
print_this = bpy.props.StringProperty(name="Print this")
def execute(self, context):
self.report({'INFO'}, self.print_this)
return {'FINISHED'}
class HelloWorldPanel(bpy.types.Panel):
"""Creates a Panel in the Object properties window"""
bl_label = "Hello World Panel"
bl_idname = "OBJECT_PT_hello"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "object"
def draw(self, context):
layout = self.layout
layout.prop(context.window_manager, "some_string")
props = layout.operator(PrintSomeString.bl_idname)
props.print_this = context.window_manager.some_string
props = layout.operator(PrintSomeString.bl_idname, text="Print welcome message")
props.print_this = "Welcome to Blender!"
def register():
bpy.utils.register_module(__name__)
bpy.types.WindowManager.some_string = bpy.props.StringProperty(name="Some String")
def unregister():
bpy.utils.unregister_module(__name__)
del bpy.types.WindowManager.some_string
if __name__ == "__main__":
register()
Paste into Text Editor and Run Script. See Object tab in Properties Editor.
'PRESET'
tobl_options
and use Restore Defaults in the preset dropdown that will now appear in the Redo panel. $\endgroup$draw()
like this:layout.operator("wm.operator_defaults")
$\endgroup$