WIP script, mesh edit and bones

I am working on a script (2.41 ver) to allow me to create parented bones in mesh edit mode. The idea is for easy and precise placement of bones via the crosshair by highlighting vertices then ‘Shift S’ - ‘Cursor -> Selection’ - run script. So far everything works, but I am new to python programming was wondering if it is possible to access the ‘Shift S’ - ‘Cursor -> Selection’ part from python to streamline the work.

Updated 8-8-06:

The script is working, if anyone is willing to test it.

To use:

  1. Have a mesh an armature and the script.
  2. Load and run the script (alt+p in text window).
  3. Select the armature and press the ‘Get’ button for the ‘Armature’.
  4. Select the mesh and enter Edit Mode.
  5. Press the ‘Armature EditMode’ toggle button to ON. (This will remember the old object and editmode, then get the armature in editmode)
  6. Select a Bone on the armature and press the ‘Get’ button for the ‘Target Bone’. (A popup menu will appear asking if you want to start a new chain or continue with the old one, select ‘Start a new chain’)
    7)Press the ‘Armature EditMode’ toggle button to OFF. (This will Return to the old object and editmode)
  7. Select some verts and press ‘Add Bone To Verts’ OR left click a point on the 3dView and press ‘Add Bone’.

AddBone.py
Be a few moments, Too many characters
Code:

 
import Blender
from Blender.Mathutils import *
from Blender import Draw, BGL
sbone = Draw.Create("Bone")
armature = Draw.Create("Armature")
sstate = 0
toggle = Draw.Create(0)
togState = ''
def cursorToVert():
    scn= Blender.Scene.GetCurrent()
    ob = scn.getActiveObject()
    if not ob or ob.getType() != 'Mesh':
  Draw.PupMenu("Please select a mesh.") 
  return
    editmode = Blender.Window.EditMode()
    if editmode:
  Blender.Window.EditMode(0)
    mesh = ob.getData(mesh=1)
    vert_sel= [v.co for v in mesh.verts if v.sel]
    if not vert_sel:
  Draw.PupMenu("No verts selected")
  if editmode:
   Blender.Window.EditMode(1)
  return
    vert_multi= 1.0/len(vert_sel)
    relative_location= reduce(lambda a,b: (a+b), vert_sel) * vert_multi
    relative_location.resize4D()
    absolute_location = relative_location*ob.matrixWorld
    Blender.Window.SetCursorPos(absolute_location[0:3])
    Blender.Window.Redraw()
    if editmode:
  Blender.Window.EditMode(1)
  return(1)
def CursorPosition2ArmatureAxis_Translation():
 global armature
 ArmPos = Blender.Object.Get(armature.val).getLocation('worldspace')
 CurPos = Blender.Window.GetCursorPos()
 CurPosRelative2ArmPos = Vector(CurPos)-Vector(ArmPos)
 ArmObject = Blender.Object.Get(armature.val)
 ArmMatrixCopy = Matrix(ArmObject.getMatrix())
 return(CurPosRelative2ArmPos*ArmMatrixCopy.invert())
def Bone2VertCaller():
 in_editmode = Blender.Window.EditMode()
 object = Blender.Scene.GetCurrent().getActiveObject()
 if not object or object.getType() != 'Mesh':
  Draw.PupMenu("Please select a mesh.")
  return
 elif in_editmode:
  if cursorToVert():
   add_bone(1)
 else: 
  question = Draw.PupMenu("OK?%t|Must be in Editmode, Enter Editmode? %x1")
  if question == 1:
   Blender.Window.EditMode(1)
   
def ArmatureSelect():
 object = Blender.Scene.GetCurrent().getActiveObject()
 if not object or object.getType() != 'Armature':
  Draw.PupMenu("Please select an armature.")
  return 
 armature.val = Blender.Object.GetSelected()[0].getName()
def add_bone(AD):
 global armature, bone, sstate
 in_editmode = Blender.Window.EditMode()
 scnobjs = Blender.Scene.GetCurrent().getChildren()
 for obj in scnobjs:
  if armature.val == obj.name:
   armatureName = obj.getData(1)
   
 arm = Blender.Armature.Get(armatureName)
 if not arm:
  Draw.PupMenu("'add_bone(AD)' Armature not in scene")
  return
 boneName = sbone.val
 parentBone = sbone.val
 bone_name_list = []
 i = 0
 for name, bone in arm.bones.items():
  bone_name_list.append(name)
 while boneName in bone_name_list:
  parentBone = boneName
  i=i+1
  boneName = sbone.val
  boneName = boneName + '.' + str(i)
 if sstate:
  parentBone = sbone.val
  sstate = 0
 if in_editmode: 
  Blender.Window.EditMode(0)
 arm.makeEditable()
 if AD:
  eb = Blender.Armature.Editbone()
  eb.roll = 10
  eb.parent = arm.bones[parentBone]
  eb.tail = CursorPosition2ArmatureAxis_Translation()
  eb.options = [Blender.Armature.CONNECTED]
  arm.bones[boneName] = eb
 else:
  if parentBone == sbone.val:
   Draw.PupMenu("cannot delete target bone")
  else:
   del arm.bones[parentBone]
 arm.update()
 if in_editmode:
  Blender.Window.EditMode(1)
 else:
  Blender.Window.EditMode(1)
  Blender.Window.EditMode(0)
 return
Selection = ''
OldSelection = ''
OldEditmode = 0
def toggleArmature():
 global Selection, OldSelection, OldEditmode
 in_editmode = Blender.Window.EditMode()
 Selection = Blender.Object.GetSelected()[0]
 if toggle.val:
  OldSelection = Selection.name[:]
  if in_editmode:
   OldEditmode = 1
   Blender.Window.EditMode(0)
   Selection.select(0)
   object = Blender.Object.Get(armature.val)
   object.select(1)
   Blender.Window.EditMode(1)
  else:
   Selection.select(0)
   object = Blender.Object.Get(armature.val)
   object.select(1)
   Blender.Window.EditMode(1) 
 
 else:
  if OldSelection:
   Blender.Window.EditMode(0)
   object = Blender.Object.Get(armature.val)
   object.select(0)
   object = Blender.Object.Get(OldSelection)
   object.select(1)
   Blender.Window.EditMode(OldEditmode)
   OldSelection = ''
   Selection = ''
   OldEditmode = 0
def getBone():
 global sstate
 object = Blender.Scene.GetCurrent().getActiveObject()
 if not object or object.getType() != 'Armature':
  Draw.PupMenu("Please select an armature.")
  return(sbone.val)
 in_editmode = Blender.Window.EditMode()
 if in_editmode:
  arm = Blender.Armature.Get(armature.val)
  Blender.Window.EditMode(0)
  Blender.Window.EditMode(1)
  question = Draw.PupMenu("Start a new chain?%x1|Continue with last one. %x2")
  if question == 1:
   sstate = 1
  else:
   sstate = 0
  for bone in arm.bones.values():
   for opt in bone.options:
    if 'BONE_SELECTED' == opt.name:
     boneSelName = bone.name
     return(boneSelName)
 else:
  Draw.PupMenu("Must be in edit mode")
  return(sbone.val)
evtNoEvt      = 0
evtTestButton  = 1
evtAddBone      = 2 
evtAddToSelected    = 3 
evtDeleteBone     = 4 
evtArmature   = 5 
evtBone    = 6 
evtGetArmature  = 7  
evtGetBone   = 8
evtToggleArmEditM = 9
def gui():
 global evtNoEvt, evtAddBone, evtAddToSelected, evtDeleteBone, evtArmature
 global evtBone, evtGetArmature, evtToggleArmEditM
 global armature, sbone, get_bone, toggle, togState
 BGL.glRasterPos2i(10,250)
 Draw.Text("Press ESC to quit.")
 #
 BGL.glRasterPos2i(45,215)
 Draw.Text('Armature = %s' % armature.val)
 Draw.Button("Get", evtGetArmature, 10, 210, 30, 18, "Get selected armature")
 toggle = Draw.Toggle("Armature EditMode", evtToggleArmEditM, 10, 185, 160, 18, toggle.val, "Toggle 'armature in editmode' ON or OFF")
 if toggle.val: togState = "ON"
 else: togState = "OFF"
 BGL.glRasterPos2i(180,190)
 Draw.Text("%s" % togState)
 #
 BGL.glRasterPos2i(45,165)
 Draw.Text('Target Bone = %s' % sbone.val)
 get_bone = Draw.Button("Get", evtGetBone, 10, 160, 30, 18, "Select target bone in armature")
 
 Draw.Button("Add Bone", evtAddBone, 10, 110, 160, 18, "A push button")
 BGL.glRasterPos2i(200,115)
 Draw.Text("Add a new bone - starting at the end of last bone in target chain")
 BGL.glRasterPos2i(200,100)
 Draw.Text("and ending at cursor position.")
 # 
 Draw.Button("Add Bone To Verts", evtAddToSelected, 10, 60, 160, 18, "A push button")
 BGL.glRasterPos2i(200,65)
 Draw.Text("Set cursor to selected vertices and add a bone ending at cursor")
 BGL.glRasterPos2i(200,50)
 Draw.Text("Must select mesh and be in editmode to work.")
 #
 Draw.Button("Delete Bone", evtDeleteBone, 10, 10, 160, 18, "A push button")
 BGL.glRasterPos2i(200,15)
 Draw.Text("Delete last bone in target chain EXEPT the target.")
 
def event(evt, val): 
 if evt == Draw.ESCKEY:
  stop = Draw.PupMenu("OK?%t|Stop script %x1")
  if stop == 1:
   Draw.Exit()
   return
 
def buttonEvt(evt):
 global evtNoEvt, evtAddBone, evtAddToSelected, evtDeleteBone, evtArmature
 global evtBone, evtGetArmature, evtToggleArmEditM
 global armature, bone, toggle, togState
 if evt == evtAddBone:
  add_bone(1)
 elif evt == evtAddToSelected:
  Bone2VertCaller()
 elif evt == evtDeleteBone:
  add_bone(0)
 elif evt == evtArmature:
  pass
 elif evt == evtGetArmature:
  ArmatureSelect()
 elif evt == evtBone:
  pass
 elif evt == evtGetBone:
  sbone.val = getBone()
 elif evt == evtToggleArmEditM:
  toggleArmature()
 elif evt == evtNoEvt:
  print "No event."
 if evt:
  Draw.Redraw()
Draw.Register(gui, event, buttonEvt)


You can’t access it directly so you’ll have to write it.
In mock-up code it would be like:

location = selection.location
cursor.location = location

Thanks for the quick reply. I thought as much. Guess some more studying on python math won’t hurt me.

This should do the trick:

import Blender

def cursorToVert():
    if not Blender.Object.GetSelected(): return
    ob = Blender.Object.GetSelected()[0]
    if ob.getType() != 'Mesh': return
    editmode = Blender.Window.EditMode()
    if editmode: Blender.Window.EditMode(0)
    mesh = ob.getData(mesh=1)
    if not mesh.verts.selected(): return
    selected = mesh.verts.selected()[0]
    relative_location = mesh.verts[selected].co
    matrix = ob.getMatrix('worldspace')
    translation = matrix.translationPart()
    absolute_location = relative_location*matrix+translation
    Blender.Window.SetCursorPos(absolute_location)
    Blender.Window.Redraw()
    if editmode: Blender.Window.EditMode(1)
cursorToVert()

And a breakdown per line:
1: Importing blender, so we can access its modules.
3: Starting the function.
4: Check if there is an object selected, if not quit the function. (Additionally you could print an error here. Same goes for all other lines with the ‘return’ command)
5: Get the first selected object.
6: Check if the object is a mesh.
7: Retrieve the current mode (editmode, or objectmode).
8: If we’re currently in editmode go to objectmode. (This
is needed to retrieve the selected vertices.)
9: Get the object data as Mesh (I’ve heard it’s faster than NMesh).
10: If no vertices are selected quit the function. (To be completely neat you should also return back to editmode again, if the user was there at the start of the script.)
11: Get the first selected vertex.
12: Get the location of the vertex inside the mesh. So this is the local location.
13: Get the rotation and size of the object, taking into account vertex parents, tracking, etc.
14: Translation of the object in worldspace. So how much the object center is away from 0,0,0.
15: Calculating the position of the vertex in worldspace.
16: Set the 3D cursor to the postion of the vertex.
17: Redraw the 3D-view windows.
18: If we were in editmode before the script started, return to that.
19: Execute the function that we just described.

I seem to remember that there was also an easier way to calculate the postion of a vert in worldspace, but I can’t think of it right now. I could also be mistaken of course. Anyway, this should work.

It seems that you need this :

Probably use Crouch’s script.

JMS’s, will make a new copy of all the mesh data just to get a vert location </broken record>

JMS’s scripts have been around for a while some of them need updating,
no slight on his work tho.

But it does not imitate ‘Shift S’ - ‘Cursor -> Selection’ . Did you test the script ? or perhaps you never use the ‘Shift S’ - ‘Cursor -> Selection’ before ?

And …? why is it a problem ? a script working on original data could destroy them . Is it better ?

Perhaps you should look at them closer .

Thanks Crouch for the example, I’m testing it now… Tested both scripts, Jms’s did the cursor selection I needed, and yours helped me to understand how the mesh/vert functions work. Thanks to both of you. If I can integrate it, I’ll try to post the script here.

Quick Reply to JMS,
Your script worksm thats good Crouch’s needs some work? - Is it that you want the median point, as Shift+S Cur->Sel does? Thats no Big deal to add in.

Copying the mesh is only a problem with high poly meshes, its probably more of a problem for me then others because it makes things slow with 1000’s of meshes or high poly meshes.

  • “A script working on original data could distroy them”
    Nonsense, just dont do operations on the mesh that modify it- easy!
    And Im surprised hearing this from someone who uses NMesh.PutRaw - One of BPythons most evil functrions (Overwrites meshes without asking)

Maybe I should looks at the scripts? - sure, I skim read scripts and look for problems without using so I can have time for work ;).
But assuming crouches works as expected its faster and cleraner.

So as not to be a prat that always cryt’s without doing anything… heres Crouches working a little better.


import Blender # Importing blender, so we can access its modules.

def cursorToVert(): # Starting the function.
    scn= Blender.Scene.GetCurrent() # Get the current scene so we can get its active object
    ob = scn.getActiveObject() # Get the active object
    if not ob or ob.getType() != 'Mesh': return # If we have no octive object or its not a mesh then bail out
    editmode = Blender.Window.EditMode()
    if editmode: Blender.Window.EditMode(0)
    mesh = ob.getData(mesh=1)
    vert_sel= [v.co for v in mesh.verts if v.sel]
    if not vert_sel: return
    vert_multi= 1.0/len(vert_sel) # invert the length so we can multiply the vectors
    relative_location= reduce(lambda a,b: (a+b), vert_sel) * vert_multi
    absolute_location = relative_location*ob.matrixWorld
    Blender.Window.SetCursorPos(absolute_location)
    Blender.Window.Redraw()
    if editmode: Blender.Window.EditMode(1)
cursorToVert()

PS, Crouch, seperating the translation part from the matrix wasnt necessary

You know what ? That’s exactely what i do : i do not use the Mesh module .

It seems that you are not looking at the good script (not the first time, same think happened with the axiscopy script one a few days ago) because there is no putraw in this one …

It is perhaps time to change your old x86 for at least a good pentium with a correct graphic card . Try to use also something else than a 16 kbytes RAM cartrige too .
:slight_smile:

Just a question : did you test your script before to post it ? It seems that it does not work on Win XP …

Reply to jms’s baiting.

If you use NMesh to make sure you dont modify any data then you realy need a better understanding of the api- things like getting vertex locations or selection states obviously arnt going to destroy mesh.

As for the script not working, I tested and it works for me-

As for needing a better computer, well - in my experience a faster computer is only a short term solution for bad programming.
Badly coded python can be in order of magnitude slower then well optimized scripts.

for the record I have an amd3500 with 4gig ram and a deicient NVidia with 256meg of ram. Ill upgrade to pci express, quad core etc at some point, but buying new hardware all the time isnt worth while.
Some of my scenes had more then 80’000 objects, so scripts that deal with these objects needed to be fairly optmimzed.

We have not the same interpretation for this . For me a badly coded script is a script that returns false results . So, I repeat : did you really test your script ? It seems that the cursor is not correctly located on displaced objects (tested on my 2.42 compil’ and the official 2.41) .

(Not with the official blender 2.42 either …)

the script doesn’t work properly.
if you move the mesh from the origin, the cursor snap to the value of the local coordinate of the vertex but not to the absolute coordinate.
it’s clearly visible with the pannels.

this matrix function never works very well but nobody cares cos this part of the api is very loosy for the moment. i prefer use the object.mat.

fixed- your right, a silly problem- not sure if you can blame the api for this-
(Ill look into changing it for next release)
You need a 4d vector to multiply it with a 4x4 matrix.
see the changes below.


import Blender # Importing blender, so we can access its modules.

def cursorToVert(): # Starting the function.
    scn= Blender.Scene.GetCurrent() # Get the current scene so we can get its active object
    ob = scn.getActiveObject() # Get the active object
    if not ob or ob.getType() != 'Mesh': return # If we have no octive object or its not a mesh then bail out
    editmode = Blender.Window.EditMode()
    if editmode: Blender.Window.EditMode(0)
    mesh = ob.getData(mesh=1)
    vert_sel= [v.co for v in mesh.verts if v.sel]
    if not vert_sel: return
    vert_multi= 1.0/len(vert_sel) # invert the length so we can multiply the vectors
    relative_location= reduce(lambda a,b: (a+b), vert_sel) * vert_multi
    relative_location.resize4D()
    absolute_location = relative_location*ob.matrixWorld
    Blender.Window.SetCursorPos(absolute_location[0:3])
    Blender.Window.Redraw()
    if editmode: Blender.Window.EditMode(1)
cursorToVert()

Cambo, you are right, I was looking for the median of the verts. Crouch’s script works, but wasn’t exactly what I had intended. Both yours and Jms’s scripts do the median placement. I am greatfull to all of you for the examples. I have been studying all three scripts.

Glad you got an answer(s) even if that means me and JMS bickering!

JMS, just bench marked out scripts for a mesh with 663558 selected verts

after removing the :: at the end of line line 9 it worked but was printing a lot.
got to 80 seconds before I canceled it,
after removing the prints it compleated in 12.218035936 seconds

Crouch’s modified script (with my changes above) changes finished in 0.85318303108 seconds

Cryts on JMS’s script
The worldspace location is being worked out for every selected vert with slow python matrix/vert mat/vector multiplication.
Why not find the median point in mesh space then frind the worldspace location of that? - saves potentually 1000’s of operations.