analitics

Pages

Tuesday, June 25, 2024

Blender 3D and python scripting - part 028.

A simple script for Blender 4.0.0 version with Bright Pencil material, see the output image:
This is the source code:
import bpy
import random

# fix an active object before changing mode
if bpy.context.view_layer.objects.active is None:
    # If there is no active object, set the first object in the scene as active
    if len(bpy.context.scene.objects) > 0:
        bpy.context.view_layer.objects.active = bpy.context.scene.objects[0]
    else:
        print("There are no objects in the scene to set as active.")

# set the mode
bpy.ops.object.mode_set(mode='OBJECT')

# set camera from the scene
cam_ob = bpy.context.scene.camera

# if is a camera in the scene and set it as active object
if cam_ob is None:
    print("There is no camera in the scene.")
elif cam_ob.type == 'CAMERA':
    # Set the camera as active object
    bpy.context.view_layer.objects.active = cam_ob
    print("The camera has been set as active object.")
else:
    print(f"The object {cam_ob.type} is set as a camera, but it is not of type 'CAMERA'.")

# set data for the ink brush
gpencil = bpy.data.grease_pencils.new("Bright Pencil")

# make material for the ink brush
if "Bright Material" in bpy.data.materials.keys():
    gp_mat = bpy.data.materials["Bright Material"]
else:
    gp_mat = bpy.data.materials.new("Bright Material")

if not gp_mat.is_grease_pencil:
    bpy.data.materials.create_gpencil_data(gp_mat)
    bpy.context.object.active_material.grease_pencil.color = (0, 0, 1, 1)


# set the object for the ink brush 
if "Bright Pencil" not in bpy.data.objects:
    gp_data = bpy.data.objects.new("Bright Pencil", gpencil)
    bpy.context.scene.collection.objects.link(gp_data)
# if it does not already exist in the scene
else:
    gp_data = bpy.data.objects["Bright Pencil"]

# assign material for drawing 
if gp_mat.name not in gp_data.data.materials:
    gp_data.data.materials.append(gp_mat)

# define a function to create random lines
def create_random_strokes(num_strokes, max_width):
    # Set the active object and mode
    bpy.context.view_layer.objects.active = gp_data
    bpy.ops.object.mode_set(mode='PAINT_GPENCIL')

    # get or create layer and set it as active
    if gpencil.layers and gpencil.layers.active:
        layer = gpencil.layers.active
    else:
        layer = gpencil.layers.new('my_test_layer', set_active=True)
    # set layer as active
    gpencil.layers.active = layer

    # get or create frame and set it as active using change_frame() method
    if layer.frames and layer.active_frame:
        frame = layer.active_frame
    else:
        frame = layer.frames.new(1)

    for _ in range(num_strokes):
        stroke = frame.strokes.new()
        stroke.line_width = int(random.uniform(1.0, max_width))
        stroke.points.add(count=3)
        for point in stroke.points:
            point.co = (random.uniform(-1.0, 1.0), random.uniform(-1.0, 1.0), 0.0)

# this function with desired parameters
create_random_strokes(num_strokes=10, max_width=16.0)

# return to original mode
bpy.ops.object.mode_set(mode='OBJECT')

Wednesday, June 5, 2024

News : JAX in 100 Seconds by Fireship!

JAX is a Python library similar to NumPy for scientific computing and linear algebra, but designed to run on accelerators like Cuda-based GPUs and Google's TPUs.

Blender 3D and python scripting - part 027.

Is more easy to create your player character based on the armature.
The next step is to add more animation and export to your game engine ...
Based on this issue - blender.stackexchange website, I tested to today and works very well:
Let's see the source code:
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')
The result of this running script using a mixamo animation is this: