From BlenderWiki

Jump to: navigation, search

Operator

Operators so far work well as tools, but are not as usable for python scripting, or even for nodes if they are ever added (this is not a 2.5 target). For python it is more useful to specify directly the objects to work on, rather than selecting them or putting them in the context. As a solution, operators should specify which data they work on as properties. A secondary function can then connect values from the context to these operators properties.

Decoupling Context

For example the "make parent" operator would specify two RNA properties:

  • objects: RNA collection of Object pointers
  • parent: RNA pointer to an Object

When using the operator as a tool, the context would be mapped like this:

  • selected objects -> objects
  • active object -> parent

In macros the same system would be used, in python however you can directly specify the objects:

objects = [main.objects[0], main.objects[1]]
parent = main.objects[2]
 
make_parent(objects, parent)

Optionally a more object oriented API could be made, by specifying the property that has the operator as a member function.

object.make_parent(parent)

We'll start with the above method though. Additionally we can support output properties. For adding an object, it would be good to return the new object. Hence each operator should have a list of inputs and outputs.

Limits

In principle we can specify a lot as inputs and outputs, for example the vertex selection to operate on. However, it's OK to still simply pass the mesh and leave such things for the future, since it would require too much work.

Also, some of the context we can assume to be there. This includes the main and scene context probably, unless the operation is explicitly on the scene for example.

Settings and Preferences

Many operators also depend on user preferences, space settings or scene settings. For python it may be better to have them all as operator properties, since otherwise what the operator does is not as reliable. This adds a fair amount of properties, but may be necessary.

Implementation

The context adaption function should either become another callback, or could be detected in the exec or invoke functions. An extra callback may be OK since it can be shared between many similar operators along with the poll function.

How exactly such RNA collections with pointers are implemented and who owns the memory is still unsure, currently it can still use always use a ListBase like Context does. If we ever add vertex collections as input this would be quite inefficient, so another system would be useful here, for example using custom iterator functions or retrieving a raw pointer.

Python Registration

Blender 2.4x uses a script header to register menues however this wont expand well to fit the number of uses Blender 2.5 has for registering...

  • Importers
  • Exporters
  • Operators
  • Python Spaces
  • Python UI (buttons window panels, tool UI's)
  • Python Operators
  • Keybindings

Here is how we have agreed python can register the above items in Blender

Instead of parsing headers, scripts are imported which intern runs registration functions (for the above types) which run from the global name space.

UI/Operator scripts should be possible to easily be redistributed and installed.

Script Cache

Caching isn't needed immediately but should eventually be added to avoid running too many scripts on start-up.

Importing up to 100's of modules can be slow, this can be improved by only doing this for the first run. Blender can then write a cache file that includes...

    • Script name and mtime
    • Defined Keymaps/Operators/Importers etc... see above list.
  • The cache can be used to know which script to import when any of the registrable types are called.
    So an operator from the cache can be registered but not have its Python function slots filled in until its first called, where it will import the module and fill in the python pointer functions.

UI Integration

UI Regions (menu, panel, editor, area) registers their identifier on startup. Identifiers are unique path-like strings such as "View3D/Menu/Mesh", "Header/Image"

UI_Register_Region("identifier");

Other code (python, plugins, ...) can register UI definition callbacks for specific identifiers. The registered functions are used to add buttons/menu entries to their respective regions.

UI_Register_Callback("identifier", function);

During drawing phase, UI functions call their respective callbacks so Python can add items in menus, panels in areas, ...

// UI code for "identifier" section
 
UI_Execute_Callbacks("identifier");


Special *subregion* can be defined for UI regions that are repeated many time (think per-bone UI buttons) in which case that "item" is passed in the context (and predefined by the call. "current_bone" or whatnot).

Python API

Operator Prototype Code

class OBJECT_OT_make_parent(Operator):
	name = "Make Parent"
	objects = CollectionProperty(name="Objects", type=Object)
	parent = PointerProperty(name="Parent", type=Object)
 
	def poll(self, context):
		if len(context.selected_objects) < 1:
			return report_warning("No Selected Objects")
 
		if not context.active_object:
			return report_warning("No Active Object")
 
		return True
 
	def user(self, context):
		self.objects = context.selected_objects
		self.parent = context.active_object
 
	def exec(self, context):
		for ob in self.objects:
			ob.parent = self.parent
 
		return Operator.FINISHED
 
WM.operator_append(OBJECT_OT_make_parent)

The above is relatively long, but is required for generating UI, documentation, calling both as a tool and python function, etc. For a quick operator it would still be possible to write it in compact way:

class OBJECT_OT_make_parent(Operator):
	def exec(self, context):
		for ob in context.selected_objects:
			ob.parent = context.active_object
 
		return Operator.FINISHED
 
WM.operator_append(OBJECT_OT_make_parent)

Operator Example Code

This example runs svn, however there are some differences with the prototype above.

  • rather then having arbitrarily named props in the class define props, use a list that always has the same name. This also gives properties an order.
  • Operator is not in the global namespace currently use bpy.types.Operator

Todo

  • returning 'FINISHED' should be replaced with Operator.FINISHED
  • context needs to be wrapped (with rna?)
  • Only float, int and bool properties can be added so far.
  • user() function


class SCRIPT_OT_MyOperator(bpy.types.Operator):
	'''
	Operator documentatuon text, will be used for the operator tooltip and python docs.
	'''
	__idname__ = 'script.myoperator'
	__label__ = 'My Operator'
 
	# List of operator properties, the attributes will be assigned
	# to the class instance from the operator settings before calling.
	__props__ = [
	 FloatProperty(attr="setting_1", name="Example 1", default=10.0, min=0, max=10, description="Add info here"),
	 IntProperty(attr="setting_2", default=2),
	 BoolProperty(attr="toggle", default=True)
	]
 
	def execute(self, context):
		print("MyOperator.exec - props")
		print(self.setting_1)
		print(self.setting_2)
		print(self.toggle)
		return('FINISHED',)
 
	def invoke(self, context, event):
		print("Invoke")
		print(event)
		return('FINISHED',)
 
	def poll(self, context): # poll isnt working yet
		return True
 
# add the python class as an operator
bpy.ops.add(SCRIPT_OT_MyOperator)
 
# run the operator from python (just like a C operator)
bpy.ops.script.myoperator(setting_1=0.5,setting_2=100,toggle=False)
 
# remove the operator (not needed, just to show you can)
bpy.ops.remove('script.myoperator')

Defining RNA

It should also be possible to define RNA properties from python, to modify Blender types. The easiest way to do it would be directly on the type, if python allows it.

Object.shading_rate = FloatProperty(name="Shading Rate", range=(0.01, 100.0))

Further it would also be useful to subclass Blender types eventually, for example:

class LuxMaterial(Material):
	name = "LuxRender Material"
	brdf = EnumProperty(name="BRDF", items=("Phong", "Lambert"))

These would then look just like native Blender properties and types from the outside, allowing UI and documentation to be generated, or used in other scripts again.

UI Example

Work in progress prototype - subtype blender types to define UI panels and menus

class my_custom_panel(bpyui.PanelType): # this name is used by python as a reference
	name = "Some Panel" # display name. we may not need this?
	def draw(self, context):
		ob = context.scene.object.active
 
		# context push/pop?
 
		row = []
		row.append(ob.draw_type)
		row.append((ob.draw_size, 'SLIDER'))	
		self.addRow(row)
 
		# 'SLIDER' -> bpyui.SLIDER; how to override button name, icon, description, need dictionary to avoid ambiguity?
		# could do both
		# OK, by allowing both dictionaries and lists?
 
		col = []
		col.append((bpyop.meshes.transform, {"mode":'rotation'}, bpyop.INVOKE))
		col.append((bpyop.meshes.remove_doubles, {"tolerance":0.0001},  bpyop.EXEC))
		if ob.type == 'Empty':
			col.append(ob.empty_draw_type)
		else:
			col.append(ob.my_generic_property)
 
		self.addColumn(col, 3)
 
 
	def poll(self, context):
		if not context.scene.object.active:
			return False
		return True
 
 
bpyui.registerUI(my_custom_panel, "spaces.buttons.object")
# use actual types instead of string? . BUT how will blender reference this internally?
# maybe...
# bpyui.spaces.buttons.object.register(my_custom_panel) # irritating PyObject creation for each lookup,
# 
# alternately registration could be automatic just by subclassing however explicit 
 
# how is this different from defining RNA properties on pytypes?
# - I guess, just need to see some examples
 
 
class VIEW3D_MT_extras(bpyui.Menu): # do we need this name convention??? is it a uniquer name/id? brecht: it should be unique, don't really care about name convention as long as it is consistent with operators, etc, but think this one is fine
	name = "Menu Name" # display in the UI
 
	def draw(self, context):
		ob = context.scene.object.active
 
		menu = []
		row.append(ob.dupliGroup) # bool menu toggle
		row.append(bpyop.meshes.remove_doubles)
		row.append(bpyui.SEPARATOR)
		row.append((bpyop.meshes.add_mesh, {'tolerance':0.01}, bpyop.EXEC))
		self.add(menu)
 
	def poll(self, context):
		if not context.scene.object.active:
			return False
		return True
 
		# return context.scene.object.active != None
 
bpyiu.registerUI(myMenu, "spaces.view3d.header.extras") #