This is a long post with a working and commented code example to add a parametric object to a scene. Code at the end, full code as a single file at my site http://www.swineworld.org/blender/blender25/25example.py (because this code is so long it doesn’t show up in the post if i add it as a whole :().
This is based on the discussion in http://blenderartists.org/forum/showthread.php?p=1454863 but I added the following:
-
a class to add an extra menu item in the menu at the top of the 3d windows (actually that is at the bottom of the info or user preferences window), this is horrendously ugly but needed as long as we don’t have a script registration system. see comments below.
-
a way to use properties (things you define for all objects but that may have different values for individual object).
-
an operator that changes mesh geometry by manipulating vertices directly (i.e without using operators)
Once this script is run you will have an extra menu item at the top of your screen that has a single entry called ‘Gear’. If you click that an empty mesh object will be created. Now because we have defined a BoolProperty geartype every object will have that property but its default is False. Only if we define an empty mesh via our menu, we set this property to True.
Now in the the properties window there will be a panel called ‘Gears’ if and only if the property geartype is True for the active object. We check that with the poll() method.
The panel consists of little more than a button to select the number of teeth (another property we defined) and a pushbutton that replaces the geometry of the mesh.
This all might seem nice, but it is wrong on many levels:
-
adding a menu entry that way is not only ugly, it prevents adding more menu items because it replaces a menu instead of adding an entry. This a minor concern as no doubt some script registration mechanism will appear in the near future,
-
what I really wanted to have is a way to add popup menus where I can ask the user for parameters and generate the mesh accordingly. But popups are not there yet (note that all existing parametric meshes like cylinders and such just drop in a default one). What is now implemented with properties is a kludge: It works but properties are supposed to be animatable whereas parameters for meshes shouldn’t (well maybe, but we’d have to hook up to a frame change event and I have no idea how to do that yet)
-
properties are also a poor surrogate for subclasses: what I really want is a subclass of Mesh, with its own instance variables. It is possible to subclass Mesh but there is now way to register is as an Blender Object type (registering gives an error).
-
add_geometry() is (for me) a strange way to manipulate the vertices. I really would like Mesh.verts (and .faces and .edges) to have a richer interface with extend() and append() methods. Besides add_geometry() seems to fail silently if not in Object mode (I’ll check if can make that one reproducible and submit it as a bug)
Nevertheless, already a lot can be accomplished using the current implementation of the API and moreover it seems reasonably fast and quite stable.
# this part defines a properties panel in the object context
# the properties it uses are defined at the end of this file for every
# object.
class ObjectButtonsPanel(bpy.types.Panel):
__space_type__ = "PROPERTIES"
__region_type__ = "WINDOW"
__context__ = "object"
class OBJECT_PT_gears(ObjectButtonsPanel):
__label__ = "Gears"
def draw_header(self, context):
layout = self.layout
layout.itemL(text="", icon='ICON_PHYSICS')
def draw(self, context):
"""
Draw an integer selector and a push button
"""
layout = self.layout
layout.itemO("gears", text="Replace Mesh")
layout.itemR(context.active_object,"teeth")
def poll(self,context):
# we use the geartype property to see if we should display anything at all
return context.active_object.geartype
This is the actual operator that modifes the mesh.
from math import cos,sin,pi
class OBJECT_OT_gears(bpy.types.Operator):
"""
Replace mesh data with a Gear of the specifed number of teeth.
I know this is nowhere near a gear but the object of this exercise is to add geometry to
a mesh object, not to make realistic gears.
"""
__idname__ = "gears"
def invoke(self, context, event):
# actually, we should check a lot of context here, for now we just catch exceptions
try:
# we are going to mangle the mesh data instead of replacing it wholesale
me=context.active_object.data
# we used the teeth property here we defined on every object
n=context.active_object.teeth
# we wont be calculating any edges: we will use mesh.update()
nverts= n*4
nfaces= n*2
# we want to restore the mode later. We will have to use operators
# because context.mode is read only. And note: what is called EDIT_MESH
# in context,mode is called EDIT in mode_set()
savemode=context.mode
# modifying geometry (like deleting everything) is an editmode op
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.delete(type='ALL')
# but adding (blank) geometry is oddly enough a objectmode op
# note that addgeometry adds new vertices all with coords (0,0,0)
bpy.ops.object.mode_set(mode='OBJECT')
me.add_geometry(nverts,0,nfaces)
for i in range(n): # calculate vertices
x1,y1 = cos(i*2*pi/n),sin(i*2*pi/n)
x2,y2 = 0.5*cos((i+0.5)*2*pi/n),0.5*sin((i+0.5)*2*pi/n)
me.verts[i*4 ].co=(x1,y1,0)
me.verts[i*4+1].co=(x2,y2,0)
me.verts[i*4+2].co=(x2,y2,1)
me.verts[i*4+3].co=(x1,y1,1)
for i in range(n-1): # calculate faces
me.faces[i*2 ].verts=[ i*4+j for j in range(4)]
me.faces[i*2+1].verts=[ i*4+j for j in (1,2,7,4)]
# last face is an exception to get edge winding right
i=n-1
me.faces[i*2 ].verts=[ i*4+j for j in range(4)]
me.faces[i*2+1].verts=[ i*4+2,i*4+1,0,3 ]
# this should calculate the edges for us
me.update()
print([ tuple(e.verts) for e in me.faces])
# restore previous mode
if savemode=='EDIT_MESH':
bpy.ops.object.mode_set(mode='EDIT')
elif savemode=='OBJECT':
bpy.ops.object.mode_set(mode='OBJECT')
except Exception as e:
print(e)
return('FINISHED',)
This part is were we redfine the menu and define properties
# we redefine and (later) reregister the user preferences header just to add an extra menu
# entry. This a crude way but needed because there is yet no way to add a menu item dynmically
class INFO_HT_header(bpy.types.Header):
__space_type__ = "INFO"
def draw(self, context):
layout = self.layout
st = context.space_data
scene = context.scene
rd = scene.render_data
row = layout.row(align=True)
row.template_header()
if context.area.show_menus:
sub = row.row(align=True)
sub.itemM("INFO_MT_file")
sub.itemM("INFO_MT_add")
sub.itemM("INFO_MT_addextra")
if rd.use_game_engine:
sub.itemM("INFO_MT_game")
else:
sub.itemM("INFO_MT_render")
sub.itemM("INFO_MT_help")
layout.template_ID(context.window, "screen", new="screen.new", unlink="screen.delete")
layout.template_ID(context.screen, "scene", new="scene.new", unlink="scene.delete")
if rd.multiple_engines:
layout.itemR(rd, "engine", text="")
layout.itemS()
layout.template_operator_search()
layout.template_running_jobs()
layout.itemL(text=scene.statistics())
# our extra menu with just one entry: to add a Gear meshobject
class INFO_MT_addextra(bpy.types.Menu):
__space_type__ = "INFO"
__label__ = "AddExtra"
def draw(self, context):
layout = self.layout
layout.operator_context = "EXEC_SCREEN"
layout.itemO("OBJECT_OT_addgear", text="Gear", icon='ICON_OUTLINER_OB_MESH')
class OBJECT_OT_addgear(bpy.types.Operator):
__idname__ = "OBJECT_OT_addgear"
def execute(self, context):
#this adds an empty mesh
bpy.ops.object.object_add(type = "MESH")
ob = context.active_object
# both Object and Mesh are called Gear. Blender takes care of uniqueness
ob.name="Gear"
ob.data.name="Gear"
# geartype is a property we define later on. It is False by default so
# only our Gear objects defined by the methos we are in here have it set to True
ob.geartype=True
return('FINISHED',)
# define new properties, all Objects have them (or the default value if they don't actually have them
# the geartype property is used in absence of proper subclassing
bpy.types.Object.BoolProperty( attr="geartype", name="Geartype", description="This is a Gear", default=False)
bpy.types.Object.IntProperty( attr="teeth",
name="Number of teeth",
description="Number of teeth on gear",
min=4,max=30,default=12)
# finally register all types and operators
bpy.ops.add(OBJECT_OT_addgear)
bpy.types.register(INFO_HT_header)
bpy.types.register(INFO_MT_addextra)
bpy.ops.add(OBJECT_OT_gears)
bpy.types.register(OBJECT_PT_gears)