Sunday, November 14, 2010

Blender Python Operator API

The classic __init__ method is not used when defining a custom bpy operator. Instead you will define class-level attributes using the types in bpy.props: BoolProperty, StringProperty, FloatProperty, IntProperty, etc. Ignore that the values returned by bpy.props types are always a tuple with the first item a pointer to the property function just-called, and the second item a dictionary that contains the arguments you passed to the property function.

bpy.types




All widgets and operators dynamically appear in the bpy.types listing object. Care must be taken to give your operator a unique class name, and a unique 'bl_idname'. By not giving a unique name you can override blender's default behavior or interface. For example lets override the INFO menu with extra buttons.



class INFO_HT_header(bpy.types.Header):
bl_space_type = 'INFO'
def draw(self, context):
layout = self.layout
wm = context.window_manager
window = context.window
scene = context.scene
rd = scene.render
layout.operator("wm.window_fullscreen_toggle", icon='FULLSCREEN_ENTER', text="")
#layout.operator("ogre_export", text="Ogre")
row = layout.row(align=True)
sub = row.row(align=True)
sub.menu("INFO_MT_instances")
sub.menu("INFO_MT_groups")
sub.menu("INFO_MT_actors")
sub.menu("INFO_MT_dynamics")

if True: #context.area.show_menus:
sub.menu("INFO_MT_file")
sub.menu("INFO_MT_add")
if rd.use_game_engine: sub.menu("INFO_MT_game")
else: sub.menu("INFO_MT_render")
layout.separator()
if window.screen.show_fullscreen:
layout.operator("screen.back_to_previous", icon='SCREEN_BACK', text="Back to Previous")
layout.separator()
else:
layout.template_ID(context.window, "screen", new="screen.new", unlink="screen.delete")

layout.separator()
layout.template_running_jobs()
layout.template_reports_banner()

layout.separator()
if rd.has_multiple_engines:
layout.prop(rd, "engine", text="")
layout.template_header()
if context.area.show_menus:
layout.template_ID(context.screen, "scene", new="scene.new", unlink="scene.delete")
layout.label(text=scene.statistics())
layout.menu( "INFO_MT_help" )
else:
screen = context.screen
row = layout.row(align=True)
row.operator("screen.frame_jump", text="", icon='REW').end = False
row.operator("screen.keyframe_jump", text="", icon='PREV_KEYFRAME').next = False
if not screen.is_animation_playing:
row.operator("screen.animation_play", text="", icon='PLAY_REVERSE').reverse = True
row.operator("screen.animation_play", text="", icon='PLAY')
else:
sub = row.row()
sub.scale_x = 2.0
sub.operator("screen.animation_play", text="", icon='PAUSE')
row.operator("screen.keyframe_jump", text="", icon='NEXT_KEYFRAME').next = True
row.operator("screen.frame_jump", text="", icon='FF').end = True

row = layout.row(align=True)
if not scene.use_preview_range:
row.prop(scene, "frame_start", text="Start")
row.prop(scene, "frame_end", text="End")
else:
row.prop(scene, "frame_preview_start", text="Start")
row.prop(scene, "frame_preview_end", text="End")

layout.prop(scene, "frame_current", text="")


def gather_instances():
instances = {}
for ob in bpy.data.objects:
if ob.data and ob.data.users > 1:
if ob.data not in instances: instances[ ob.data ] = []
instances[ ob.data ].append( ob )
return instances

def select_instances( context, name ):
for ob in bpy.data.objects: ob.select = False
ob = bpy.data.objects[ name ]
if ob.data:
inst = gather_instances()
for ob in inst[ ob.data ]: ob.select = True
bpy.context.scene.objects.active = ob

def select_group( context, name, options={} ):
for ob in bpy.data.objects: ob.select = False
for grp in bpy.data.groups:
if grp.name == name:
bpy.context.scene.objects.active = grp.objects[0]
for ob in grp.objects: ob.select = True

class INFO_MT_instances(bpy.types.Menu):
bl_label = "Instances"
def draw(self, context):
layout = self.layout
inst = gather_instances()
for data in inst:
ob = inst[data][0]
op = layout.operator("select_instances", text=ob.name) # operator has no variable for button name?
op.mystring = ob.name
layout.separator()

class INFO_MT_instance(bpy.types.Operator):
'''select instance group'''
bl_idname = "select_instances"
bl_label = "Select Instance Group"
bl_options = {'REGISTER', 'UNDO'}
mystring= StringProperty(name="MyString", description="...", maxlen=1024, default="my string")
@classmethod
def poll(cls, context): return True
def invoke(self, context, event):
print( 'invoke select_instances op', event )
select_instances( context, self.mystring )
return {'FINISHED'}

class INFO_MT_groups(bpy.types.Menu):
bl_label = "Groups"
def draw(self, context):
layout = self.layout
for group in bpy.data.groups:
op = layout.operator("select_group", text=group.name) # operator no variable for button name?
op.mystring = group.name
layout.separator()

class INFO_MT_group(bpy.types.Operator):
'''select group'''
bl_idname = "select_group"
bl_label = "Select Group"
bl_options = {'REGISTER', 'UNDO'}
mystring= StringProperty(name="MyString", description="...", maxlen=1024, default="my string")
@classmethod
def poll(cls, context): return True
def invoke(self, context, event):
select_group( context, self.mystring )
return {'FINISHED'}

class INFO_MT_actors(bpy.types.Menu):
bl_label = "Actors"
def draw(self, context):
layout = self.layout
for ob in bpy.data.objects:
if ob.game.use_actor:
op = layout.operator("select_actor", text=ob.name)
op.mystring = ob.name
layout.separator()

class INFO_MT_actor(bpy.types.Operator):
'''select actor'''
bl_idname = "select_actor"
bl_label = "Select Actor"
bl_options = {'REGISTER', 'UNDO'} # Options for this panel type
mystring= StringProperty(name="MyString", description="...", maxlen=1024, default="my string")
@classmethod
def poll(cls, context): return True
def invoke(self, context, event):
bpy.data.objects[self.mystring].select = True
return {'FINISHED'}

class INFO_MT_dynamics(bpy.types.Menu):
bl_label = "Dynamics"
def draw(self, context):
layout = self.layout
for ob in bpy.data.objects:
if ob.game.physics_type in 'DYNAMIC SOFT_BODY RIGID_BODY'.split():
op = layout.operator("select_dynamic", text=ob.name)
op.mystring = ob.name
layout.separator()

class INFO_MT_dynamic(bpy.types.Operator):
'''select dynamic'''
bl_idname = "select_dynamic"
bl_label = "Select Dynamic"
bl_options = {'REGISTER', 'UNDO'} # Options for this panel type
mystring= StringProperty(name="MyString", description="...", maxlen=1024, default="my string")
@classmethod
def poll(cls, context): return True
def invoke(self, context, event):
bpy.data.objects[self.mystring].select = True
return {'FINISHED'}