Wednesday, April 10, 2024

Storyboard concept

Title: The Dog and the Elephant










Happy Sketching!

Friday, March 29, 2024

modifier and bones python class doodles in Blender

some python examples i wrote for working with data transfer modifier for copying uv's and renaming of bones of an armature.  tested in Blender 3.6.  there may be bugs so please modify use at your own risk.


#tested in Blender 3.6

import bpy

class UVCopier(object):
	"""this class is responsible for copying uvs from one mesh to another.
	it assumes all meshes have identical topology
	"""
	def __init__(self, source=None, destinations=[]):
		"""
		@param source (str) data object name for source mesh with uvs
		@param destination (list of str) data object names for destination meshes with identical topoloyg to source
		"""
		assert(source, "requires source mesh")
		assert(destinations, "requires one or more destination meshes")
		self._source = source
		self._destinations = destinations

	def doIt(self):
		"""
		"""
		print("uv copying")

		#transfer uv from one mesh to another
		destinations = self._destinations
		source = self._source
		for dest in destinations:
			self.transferUV(sourceMesh=source, destinationMesh=dest)

	def transferUV(self, sourceMesh=None, destinationMesh=None):
		"""transfer uv from one mesh to another
		@param sourceMesh (str) source mesh data object name
		@param destinationMesh (str) destination mesh data object name
		"""
		assert(sourceMesh in bpy.data.objects and destinationMesh in bpy.data.objects, "requires data object names for source and destination meshes")

		#create the modifier on destination
		mod = bpy.data.objects[destinationMesh].modifiers.new("uvMod", "DATA_TRANSFER")
		mod.object = bpy.data.objects[sourceMesh]
		mod.mix_mode = 'REPLACE'
		mod.use_loop_data = True
		mod.data_types_loops = {'UV'}
		mod.loop_mapping = 'TOPOLOGY'

		#apply the modifier on destination
		bpy.data.objects[destinationMesh].select_set(True)
		bpy.context.view_layer.objects.active = bpy.data.objects[destinationMesh]
		bpy.ops.object.modifier_apply(modifier="uvMod")

"""#this was what was done manually in a test scene in Blender. before filling in the class details
mod = bpy.data.objects['Cube.002'].modifiers.new("uvMod", "DATA_TRANSFER")
mod.object = bpy.data.objects['srcMesh']
mod.mix_mode = 'REPLACE'
mod.use_loop_data = True
mod.data_types_loops = {'UV'}
mod.loop_mapping = 'TOPOLOGY'
bpy.data.objects['Cube.002'].select_set(True)
bpy.context.view_layer.objects.active = bpy.data.objects['Cube.002']
bpy.ops.object.modifier_apply(modifier="uvMod")

"""


#import sys
#import imp
#sys.path.append(r"C:\Users\Nathaniel\Documents\blender_dev\src\snippets")
"""
import uv_snippets as uv
imp.reload(uv)
obj = uv.UVCopier(source="srcMesh", destinations=["Cube.001", "Cube.002"])
obj.doIt()
"""		
import string
import itertools
import bpy
import logging
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG) #without this info logs wouldnt show in console


class BoneNamer(object):
	"""class to handle naming of bones using letters. useful for long bone chains
	has api for renaming bones by world position or by hierarchy
	"""
	def __init__(self, armature='', prefix='part'):
		"""
		@param armature (str) name of armature data object name
		@param prefix (str) prefix to use for bone names
		"""
		assert armature, "requires armature"
		
		self._armature = armature
		self._prefix = prefix

	def nameByHierarchy(self, root, useSingleLetter=True, suffix="L"):
		"""names entire hierarchy starting from root bone
		(todo: support an endbone so could stop naming after a certain point)
		@param root (str) name of root bone
		@param useSingleLetter (bool) whether to use single or multiple letter naming		
		@param suffix (str) suffix to use for name ex: L,R or C
		"""
		bones = self.getBoneHierarchy(root)

		if (len(bones) > 25) and useSingleLetter:
			logger.warning("cannot rename more than 25 things with single letter naming")
			return False

		prefix = self._prefix
		#name bones using a letter
		for i in range(0, len(bones)):
			bone = bones[i]
			newNameLetter = self._getLetterName(i, useSingleLetter=useSingleLetter) #todo add support for non single letter
			newName = "{prefix}_{letter}.{suffix}".format(prefix=prefix, letter=newNameLetter, suffix=suffix)
				
			#name bone
			self._nameBone(bone, newName)


	def nameByWorldPosition(self, bones=[], axis="x", direction="+", useSingleLetter=True, suffix="L"):
		"""
		rename bone chain by world axis ex: useful for naming a chain ex: arm_a.L arm_b.L ...
		@param bones (list of str) bones to rename
		@param axis (str) world axis to use for position based naming
		@param direction (str either x,y,z) which axis to use for position based naming
		@param useSingleLetter (bool) whether to use single letter naming
		@param suffix (str) which suffix is bone - could be empty string if didnt want a suffix

		"""
		assert bones, "requires bones to name"

		if (len(bones) > 25) and useSingleLetter:
			logger.warning("cannot rename more than 25 things with single letter naming")
			return False

		#populate dict with world position to use
		posDict = {}
		for bone in bones:
			pos = self._getWorldTranslateByAxis(bone, axis)
			posDict[pos] = bone

		#in sorted order default start at smallest position - rename bones
		direction="+"
		posListSorted = sorted(posDict.items()) #todo allow reverse for direction -

		prefix = self._prefix
		for i in range(0, len(posListSorted)):
			bone = posListSorted[i][1]
			newNameLetter = self._getLetterName(i, useSingleLetter=useSingleLetter)
			newName = "{prefix}_{letter}.{suffix}".format(prefix=prefix, letter=newNameLetter, suffix=suffix)
			#name bone
			self._nameBone(bone, newName)

	def _nameBone(self, bone, name):
		"""give provided bone the given name
		@param bone (str) bone name
		@param name (str) new name for bone
		"""
		armature = self._armature
		self._enterEditMode()
		bpy.data.objects[armature].data.edit_bones[bone].name = name
		return True

	def getBoneHierarchy(self, root):
		"""get roots bone hierarchy
		"""
		rootBone = root
		armature = self._armature

		boneChain = []
		self._enterEditMode()
		bpy.ops.armature.select_all(action='DESELECT')
		bpy.data.objects[armature].data.edit_bones[rootBone].select_head=True
		bpy.ops.armature.select_linked()
		boneChain = [bone.name for bone in bpy.data.objects[armature].data.edit_bones if bone.select_head]

		logger.info(boneChain)
		
		return boneChain	

	def _getWorldTranslateByAxis(self, bone, axis):
		"""get world translate of bone by axis - returns a double
		@param axis (str either x,y or z) for axis to get translation in
		"""	
		assert bone, "requires a bone provided that exists in armature"
		result = None
		armature = self._armature
		axisIndex = ["x", "y", "z"].index(axis.lower())
		self._enterEditMode()
		result =  bpy.data.objects[armature].data.edit_bones[bone].matrix.translation[axisIndex]
		return result

	def _enterEditMode(self):
		armature = self._armature
		bpy.context.view_layer.objects.active = bpy.data.objects[armature]
		bpy.ops.object.mode_set(mode='EDIT')	

	def _getLetterName(self, i, useSingleLetter=True):
		"""
		@param i (int) index for letter
		"""
		alphabets = [let for let in string.ascii_lowercase] #list of a-z
		if useSingleLetter:
			return alphabets[i]

		#use letter triple
		multipleLetters = [''.join(j) for j in itertools.product(alphabets, repeat=3)]
		return multipleLetters[i]




#import sys
#import imp
#tested in Blender 3.6.5
#sys.path.append(r"C:\Users\Nathaniel\Documents\blender_dev\src\snippets")
"""
import bone_snippets as bs #name of python file is bone_snippets.py
imp.reload(bs)

bln = bs.BoneNamer(armature="Armature", prefix="spine")
bln.nameByWorldPosition(bones=["Bone", "Bone.002", "Bone.003", "Bone.004"], axis="z", direction="+", suffix="C") #should get spine_a.L spine_b.L ...

#named using hierarchy way
bln = bs.BoneNamer(armature="Armature", prefix="spine")
bln.nameByHierarchy("Bone", suffix="C")
"""



#inspired by
#https://stackoverflow.com/questions/9001509/how-do-i-sort-a-dictionary-by-key
#https://stackoverflow.com/questions/3190122/python-how-to-print-range-a-z
#https://stackoverflow.com/questions/7074051/what-is-the-best-way-to-generate-all-possible-three-letter-strings

Happy Sketching!

Wednesday, March 13, 2024

rigging doodle in Blender 3.6

this is a work in progress rig i created in Blender 3.6. it currently is missing fingers and reverse foot.  it has ik/fk switching for arms and legs.  this is very work in progress so there may be bugs with the rig.

please modify use at your own risk.

blend file


Happy Sketching!