3

I have seen this post Convert bones to meshes but i need just box (or cylinder), not a piramyd at every bone, can someone help me please?

2 Answers2

6

This is an update of lemon's code on the question Convert bones to meshes. Run the script and it will pop a menu asking if you want Pyramid, Tapered, or Box shapes. Pyramid is what the code did originally, tapered uses the bone envelope, and Box takes the average.

This image is "Box": enter image description here

# from https://blender.stackexchange.com/a/75049/33589

import bpy import mathutils from mathutils import Vector from math import *

class ArmatureMenu(bpy.types.Menu): bl_label = "Mesh 2 Armature Menu" bl_idname = "OBJECT_MT_Mesh_From_Armature"

def draw(self, context):
    layout = self.layout
    layout.operator("wm.mesh_from_armature", text="Pyramid").mesh_type = 'Pyramid' # from here
    layout.operator("wm.mesh_from_armature", text="Tapered").mesh_type = 'Tapered' # from here
    layout.operator("wm.mesh_from_armature", text="Box").mesh_type = 'Box' # from here

def CreateMesh(self, meshType):

obj = bpy.context.active_object

if obj == None:
    self.report({"ERROR"}, "No selection" )
elif obj.type != 'ARMATURE':
    self.report({"ERROR"}, "Armature expected" )
else:
    processArmature( bpy.context, obj, meshType = meshType )

#Create the base object from the armature def meshFromArmature( arm ): name = arm.name + "_mesh" meshData = bpy.data.meshes.new( name + "Data" ) meshObj = bpy.data.objects.new( name, meshData ) meshObj.matrix_world = arm.matrix_world.copy() return meshObj

#Create the bone geometry (vertices and faces) def boneGeometry( l1, l2, x, z, baseSize, l1Size, l2Size, base, meshType ):

if meshType == 'Tapered':
    print(meshType)
    x1 = x * baseSize * l1Size 
    z1 = z * baseSize * l1Size

    x2 = x * baseSize * l2Size 
    z2 = z * baseSize * l2Size
elif meshType == 'Box':
    print(meshType)
    lSize = (l1Size + l2Size) / 2
    x1 = x * baseSize * lSize 
    z1 = z * baseSize * lSize

    x2 = x * baseSize * lSize 
    z2 = z * baseSize * lSize

else: # default to Pyramid
    print(meshType)
    x1 = x * baseSize * l1Size 
    z1 = z * baseSize * l1Size

    x2 = Vector( (0, 0, 0) )
    z2 = Vector( (0, 0, 0) )

verts = [
    l1 - x1 + z1,
    l1 + x1 + z1,
    l1 - x1 - z1,
    l1 + x1 - z1,
    l2 - x2 + z2,
    l2 + x2 + z2,
    l2 - x2 - z2,
    l2 + x2 - z2
    ] 

faces = [
    (base+3, base+1, base+0, base+2),
    (base+6, base+4, base+5, base+7),
    (base+4, base+0, base+1, base+5),
    (base+7, base+3, base+2, base+6),
    (base+5, base+1, base+3, base+7),
    (base+6, base+2, base+0, base+4)
    ]

return verts, faces

#Process the armature, goes through its bones and creates the mesh def processArmature(context, arm, genVertexGroups = True, meshType = 'Pyramid'): print("processing armature {0} {1}".format(arm.name, meshType) )

#Creates the mesh object
meshObj = meshFromArmature( arm )
context.collection.objects.link( meshObj )

verts = []
edges = []
faces = []
vertexGroups = {}

bpy.ops.object.mode_set(mode='EDIT')

try:
    #Goes through each bone
    for editBone in [b for b in arm.data.edit_bones if b.use_deform]:
        boneName = editBone.name
        # print( boneName )
        poseBone = arm.pose.bones[boneName]

        #Gets edit bone informations
        editBoneHead = editBone.head
        editBoneTail = editBone.tail
        editBoneVector = editBoneTail - editBoneHead
        editBoneSize = editBoneVector.dot( editBoneVector )
        editBoneRoll = editBone.roll
        editBoneX = editBone.x_axis
        editBoneZ = editBone.z_axis
        editBoneHeadRadius = editBone.head_radius
        editBoneTailRadius = editBone.tail_radius

        #Creates the mesh data for the bone
        baseIndex = len(verts)
        baseSize = sqrt( editBoneSize )
        newVerts, newFaces = boneGeometry( editBoneHead, editBoneTail, editBoneX, editBoneZ, baseSize, editBoneHeadRadius, editBoneTailRadius, baseIndex, meshType )

        verts.extend( newVerts )
        faces.extend( newFaces )

        #Creates the weights for the vertex groups
        vertexGroups[boneName] = [(x, 1.0) for x in range(baseIndex, len(verts))]

    #Assigns the geometry to the mesh
    meshObj.data.from_pydata(verts, edges, faces)

except:
    bpy.ops.object.mode_set(mode='OBJECT')
else:
    bpy.ops.object.mode_set(mode='OBJECT')

#Assigns the vertex groups
if genVertexGroups:
    for name, vertexGroup in vertexGroups.items():
        groupObject = meshObj.vertex_groups.new(name=name)
        for (index, weight) in vertexGroup:
            groupObject.add([index], weight, 'REPLACE')

#Creates the armature modifier
modifier = meshObj.modifiers.new('ArmatureMod', 'ARMATURE')
modifier.object = arm
modifier.use_bone_envelopes = False
modifier.use_vertex_groups = True

meshObj.data.update()

return meshObj

class MeshFromArmatureOperator(bpy.types.Operator): bl_idname = "wm.mesh_from_armature" bl_label = "MeshFromArmatureOperator"

mesh_type : bpy.props.StringProperty(name="mesh_type")

def execute(self, context):
    print('The mesh type is', self.mesh_type)
    CreateMesh(self, self.mesh_type)
    return {'FINISHED'}

def register(): bpy.utils.register_class( ArmatureMenu ) bpy.utils.register_class( MeshFromArmatureOperator )

def unregister(): bpy.utils.unregister_class( ArmatureMenu ) bpy.utils.unregister_class( MeshFromArmatureOperator )

if name == "main": register()

# The menu can also be called from scripts
bpy.ops.wm.call_menu(name='OBJECT_MT_Mesh_From_Armature')

Ron Jensen
  • 3,421
  • 1
  • 9
  • 27
0

This is an update of Ron Jensen's answer (which is itself, as Ron points out, "an update of lemon's code on the question Convert bones to meshes").

This update was to rework some of the GUI code since I was having some issues on my end with using it to properly choose the mesh type (i.e., Pyramid, Tapered, or Box). I've changed the GUI to be a pop-up dialog that uses EnumProperty to pick which mesh type to use, and I've removed the ArmatureMenu class since I've changed the GUI to instead be called from the MeshFromArmatureOperator class when it is invoked (so now only 1 class has to be registered instead of 2).

Please note that I've only tested this in Blender 3.3.1 (which requires enabling "Developer Extras" in "Blender Preferences" -> "Interface" to be able to run "MeshFromArmatureOperator" from the F3 "Menu Search" window), so I can't vouch for compatibility with other versions.

# Built from:
# - https://blender.stackexchange.com/a/190354
# - https://blender.stackexchange.com/a/75049/33589

import bpy import mathutils from mathutils import Vector from math import *

def CreateMesh(self, meshType):

obj = bpy.context.active_object

if obj == None:
    self.report({"ERROR"}, "No selection" )
elif obj.type != 'ARMATURE':
    self.report({"ERROR"}, "Armature expected" )
else:
    processArmature( bpy.context, obj, meshType = meshType )

#Create the base object from the armature def meshFromArmature( arm ): name = arm.name + "_mesh" meshData = bpy.data.meshes.new( name + "Data" ) meshObj = bpy.data.objects.new( name, meshData ) meshObj.matrix_world = arm.matrix_world.copy() return meshObj

#Create the bone geometry (vertices and faces) def boneGeometry( l1, l2, x, z, baseSize, l1Size, l2Size, base, meshType ):

if meshType == 'Tapered':
    print(meshType)
    x1 = x * baseSize * l1Size 
    z1 = z * baseSize * l1Size

    x2 = x * baseSize * l2Size 
    z2 = z * baseSize * l2Size
elif meshType == 'Box':
    print(meshType)
    lSize = (l1Size + l2Size) / 2
    x1 = x * baseSize * lSize 
    z1 = z * baseSize * lSize

    x2 = x * baseSize * lSize 
    z2 = z * baseSize * lSize

else: # default to Pyramid
    print(meshType)
    x1 = x * baseSize * l1Size 
    z1 = z * baseSize * l1Size

    x2 = Vector( (0, 0, 0) )
    z2 = Vector( (0, 0, 0) )

verts = [
    l1 - x1 + z1,
    l1 + x1 + z1,
    l1 - x1 - z1,
    l1 + x1 - z1,
    l2 - x2 + z2,
    l2 + x2 + z2,
    l2 - x2 - z2,
    l2 + x2 - z2
    ] 

faces = [
    (base+3, base+1, base+0, base+2),
    (base+6, base+4, base+5, base+7),
    (base+4, base+0, base+1, base+5),
    (base+7, base+3, base+2, base+6),
    (base+5, base+1, base+3, base+7),
    (base+6, base+2, base+0, base+4)
    ]

return verts, faces

#Process the armature, goes through its bones and creates the mesh def processArmature(context, arm, genVertexGroups = True, meshType = 'Pyramid'): print("processing armature {0} {1}".format(arm.name, meshType) )

#Creates the mesh object
meshObj = meshFromArmature( arm )
context.collection.objects.link( meshObj )

verts = []
edges = []
faces = []
vertexGroups = {}

bpy.ops.object.mode_set(mode='EDIT')

try:
    #Goes through each bone
    for editBone in [b for b in arm.data.edit_bones if b.use_deform]:
        boneName = editBone.name
        # print( boneName )
        poseBone = arm.pose.bones[boneName]

        #Gets edit bone informations
        editBoneHead = editBone.head
        editBoneTail = editBone.tail
        editBoneVector = editBoneTail - editBoneHead
        editBoneSize = editBoneVector.dot( editBoneVector )
        editBoneRoll = editBone.roll
        editBoneX = editBone.x_axis
        editBoneZ = editBone.z_axis
        editBoneHeadRadius = editBone.head_radius
        editBoneTailRadius = editBone.tail_radius

        #Creates the mesh data for the bone
        baseIndex = len(verts)
        baseSize = sqrt( editBoneSize )
        newVerts, newFaces = boneGeometry( editBoneHead, editBoneTail, editBoneX, editBoneZ, baseSize, editBoneHeadRadius, editBoneTailRadius, baseIndex, meshType )

        verts.extend( newVerts )
        faces.extend( newFaces )

        #Creates the weights for the vertex groups
        vertexGroups[boneName] = [(x, 1.0) for x in range(baseIndex, len(verts))]

    #Assigns the geometry to the mesh
    meshObj.data.from_pydata(verts, edges, faces)

except:
    bpy.ops.object.mode_set(mode='OBJECT')
else:
    bpy.ops.object.mode_set(mode='OBJECT')

#Assigns the vertex groups
if genVertexGroups:
    for name, vertexGroup in vertexGroups.items():
        groupObject = meshObj.vertex_groups.new(name=name)
        for (index, weight) in vertexGroup:
            groupObject.add([index], weight, 'REPLACE')

#Creates the armature modifier
modifier = meshObj.modifiers.new('ArmatureMod', 'ARMATURE')
modifier.object = arm
modifier.use_bone_envelopes = False
modifier.use_vertex_groups = True

meshObj.data.update()

return meshObj

class MeshFromArmatureOperator(bpy.types.Operator): bl_idname = "wm.mesh_from_armature" bl_label = "MeshFromArmatureOperator"

mesh_type_items = [
    ("Pyramid", "Pyramid", ""),
    ("Tapered", "Tapered", ""),
    ("Box", "Box", ""),
]
mesh_type: bpy.props.EnumProperty(items=mesh_type_items, name="Mesh Type")

def execute(self, context):
    print('The mesh type is', self.mesh_type)
    CreateMesh(self, self.mesh_type)
    return {'FINISHED'}

def invoke(self, context, event):
    wm = context.window_manager
    return wm.invoke_props_dialog(self)

def register(): bpy.utils.register_class( MeshFromArmatureOperator )

def unregister(): bpy.utils.unregister_class( MeshFromArmatureOperator )

if name == "main": register()

# The menu can also be called from scripts
bpy.ops.wm.mesh_from_armature('INVOKE_DEFAULT')

JPLC
  • 3
  • 2
  • JPLC thanks! the script work fine BUT is there a way to get the meshed bones to look exactly like the armature, no scaling – Sneeky Chick Nov 14 '23 at 17:48
  • @SneekyChick someone would have to re-work the function boneGeometry() to build a more complicated shape. – Ron Jensen Jan 05 '24 at 21:50