Imran's personal blog

July 23, 2022

Blender Python Stuff

Filed under: Uncategorized — ipeerbhai @ 9:40 pm

I’ve been learning Blender. It’s probably just my newb skill level, but the python API is wonky to me. These notes will hopefully help me remember weird stuff. There’s a lot of weird stuff.

Background

Blender has a pretty complex API that seems like an afterthought to the program. Examples of this are in armatures/bones and sculpting at the least. The API is also not consistent — like it was written by different people over time. For example, You can’t just do armature.bones[5].select_set(True) like you can with mesh items. You also can’t do what you want in a sane way like armature.bones[5].link(newBone) — which would make more sense to a programmer. The Blender API is an afterthought to the UI, and it shows all over the place. Sculpting is another example where the API just presumes a viewport and pen. You can sculpt as a programmer — but then you’re manipulating the UI as if you were an artist. You can’t just do something sane like:

brush = … GetBrush(Brushes.Sculpt.Snake)
brush.position = [x, y, z] ## World cooridinates
brush.rotation = [thetaX, thetaY, thetaZ] ## radian rotation around X, Y, Z axis relative to brush center point.
brush.scale = 50 ## diameter of brush circle
brush.TargetObject = myMesh
brush.Stroke(pressureLevel=4, startPosition = brush.FindNearestVertex(), displacement=[x, y, z]

Compared to something like Pixar’s USD API — which seems to have put the programmer first ( but requires the programmer know a lot about concepts from 3d animation ), it’s downright confusing.

Also, Blender presumes pens, not mice, in places, and treats the mouse as a poor quality pen.

Enough grousing — let’s get to listing the API to do things I seem to always forget.

Selecting Objects

Objects in blender have 2 different data format existences, which then span across different modes of existence, called a “context”. For example, a mesh object will exist as both a “scene” data type and a “data” data type . The “data” instance is a child type of the scene type. You can’t call API functions that expect a data object with a scene object — even though they’re they both represent the same physical thing in Blender. It’s a real pain, because both data types have a .name member that you can use to identify the object — but they aren’t the same. The scene object name is the outliner name, while the data name is unique to the blend file. Blender will automatically update the data name if you try and collide it — like if there’s globally already a bone with the data name “leftArm”, then blender will add “.001” to the data name, but not the scene name. When calling search functions for an object by name, this can cause a lot problems, because each search can return a valid but incorrect type for what you want to do. This type system is just a bad choice, and Blender should do away with it. A single data type per object, with a global name and an outline name as properties. Something like Mesh.GlobalName = “Person1.LeftArm”, Mesh.Name = “LeftArm”.

Anyway — back to selection. Here’s a list.

  • To get the scene object use object =bpy.context.scene.objects.get(‘ObjectSceneName’)
  • bpy.context.scene is the same as bpy.data.scenes[index] in some cases, and sometimes is the same as the reference to the data blocks, and may be the better choice to use most times as bpy.context is a variable type and can change members on the fly.
  • Depending on context ( aka, what’s selected, what mode you’re in, what window is loaded, etc ), you can get different types back. The Scene object has a .type member you can query, while the data object does not ( it should — different authors problem visible here! ). See if the .type member is available — if so, you have a scene object and not a data object!

Extruding a single bone at the end of an Armature

When I tried this, I hit so many walls, including extruding a single bone on the tail of *every* bone of the armature, because Armature.bones[index].select_tail = True will select the tail of every bone in the Armature ( even though an index was given. Oh, and it doesn’t work in Edit mode, only if you’re in object mode, do the selection, then enter edit mode. To actually select just the tail of just a specific bone via index, it’s a bit of a hassle. Here’s the basic idea.

  • Start in object mode;
  • Select nothing.
  • Select the Armature
  • Go to edit mode.
  • Select nothing.
  • Get the bone as data object you want
  • Go to object mode.
  • Select the tail in object mode.
  • Go back to edit mode to apply the selection.
  • Extrude move with an orientation matrix

Yes, I’m serious, that’s how you select the tail of a specific bone reliably in Blender. Did I mention the API is nuts? If I wanted to select a Vertex in a mesh, I would use Vertex.select_set(True) in edit mode — but bones, also children members of a higher level object, don’t have a select_set function in edit mode. You can see that the selection system was written by different people at different times, and you can see it was an afterthought right here.

Here’s some sample code from an experiment I have, that seems to work.

## Deselect all, enter object mode
bpy.ops.object.select_all(action='DESELECT')
bpy.ops.object.mode_set()

## select the armature and get a reference to the scene object for it.
armature = bpy.data.scenes[0].objects.get('Armature')
armature.select_set(True)

## Change to edit mode and select nothing.
bpy.ops.object.editmode_toggle()
bpy.ops.armature.select_all(action='DESELECT')

## Get the bone I want to extrude another bone from, and select the tail by edit mode foolishness (IMHO, this is a bug).
lastBone = armature.data.bones[len(armature.data.bones)-1]
bpy.ops.object.editmode_toggle()
lastBone.select_tail = True
bpy.ops.object.editmode_toggle()

## extrude a bone 1 unit vertically constrained on Z from this selected tail
bpy.ops.armature.extrude_move(ARMATURE_OT_extrude={"forked":False}, TRANSFORM_OT_translate={"value":(0, 0, 1), "orient_axis_ortho":'X', "orient_type":'GLOBAL', "orient_matrix":((1, 0, 0), (0, 1, 0), (0, 0, 1)), "orient_matrix_type":'GLOBAL', "constraint_axis":(False, False, True), "mirror":False, "use_proportional_edit":False, "proportional_edit_falloff":'SMOOTH', "proportional_size":1, "use_proportional_connected":False, "use_proportional_projected":False, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "gpencil_strokes":False, "cursor_transform":False, "texture_space":False, "remove_on_cancel":False, "view2d_edge_pan":False, "release_confirm":False, "use_accurate":False, "use_automerge_and_split":False})

## this extruded bone is now the active object, capture a reference to it before I lose it.
newBone = bpy.context.active_bone


Another issue is bone selection.

Selecting bones in functions

You cannot store a bone object in python reliably in blender. If I do this:

bone = armature.bones[“someBone”]

then update the armature, the bone reference I have may move on me. This is likely a bug in Blender’s internals, and how they manage bone updates. This one was a real doozy to figure out, as it made no sense. I literally watched the bone change after a call to bpy.ops.object.editmode_toggle(). I wouldn’t have believed it otherwise. As a work-around, save the name like so:

boneName = armature.bones[“someBone”].name

You can then use armature.bones[boneName] in functions where you would want to use the bone object.

Adding Pose Constraints to a Bone:

The UI adds pose constraints to a bone by a very complex method. Turns out there’s a simpler method.

  1. The Armature scene object (not the data object) has a .pose member ( I’ll call it scene.X and data.X respectively in this post)
  2. The .pose member has an array of bones ( the same array indices/names as the data.bones array ).
  3. Each bone has an array of .constraints on it. You can call.new on it. Here’s an example:
    1. ikConstraint = armatureAsSceneType.pose.bones[‘Bone.001’].constraints.new(“IK”)
    2. the return of .new operator is the instance of the constraint object, not a status code like other new operators in Blender’s API (remember, there is no consistency in the API. sometimes you get a status, and sometimes you get an object. When do you get each? Who knows. Go uses a tuple convention of <object, error> and stays consistent with it — Blender should also.)
  4. Constraint objects have simple property accessors. You can simply set them. But, you need to know their type. Here’s what I know by example:
    1. ikConstraint.target = armatureAsSceneType ## scene data type
    2. ikConstraint.subtarget = ikBone.name ## string
    3. ikConstraint.use_stretch = False ## boolean
  5. I’m not sure you need to be in Pose mode to create/edit bone constraints. The API looks like it would work in any mode, but many things in Blender will give you internal errors if you call an API in the wrong mode. If you get context.poll() errors, try pose mode.

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a comment

Create a free website or blog at WordPress.com.