Skip to content

Instantly share code, notes, and snippets.

@pgtwitter
Last active July 4, 2023 17:50
Show Gist options
  • Save pgtwitter/74406aad60fd97b73089c98ddb94d231 to your computer and use it in GitHub Desktop.
Save pgtwitter/74406aad60fd97b73089c98ddb94d231 to your computer and use it in GitHub Desktop.
Armature設定ツール(ベジェカーブ選択,実行)(参考動画 https://www.youtube.com/watch?v=BKtVuqnCbK0 , https://www.youtube.com/watch?v=0c6yGBhpd9s )
import bpy
import mathutils
import math
bl_info = {
'name': 'Physics and FK Armature',
'author': 'p_g_',
'version': (0, 2),
'blender': (3, 6, 0),
'location': 'VIEW 3D > Tool(Sidebar)',
'description': 'Setting Physics and FK Armature',
'doc_url': 'https://gist.github.com/pgtwitter/74406aad60fd97b73089c98ddb94d231',
'category': '3D VIEW',
}
class PFA_Panel(bpy.types.Panel):
bl_label = 'Physics and FK Armature Panel'
bl_idname = f'OBJECT_PT_{__name__}_VIEW3D'
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Tool'
def draw(self, context):
layout = self.layout
e2mn_props = context.scene.e2mn_props
# prefs = context.preferences.addons[__name__].preferences
box = layout.box()
row = box.row()
row.operator('pfa.demo',
text='Create Curve for DEMO', icon='CURVE_BEZCURVE')
box = layout.box()
row = box.row()
row.operator('pfa.setpfa',
text='Set Physics and FK Bones', icon='ARMATURE_DATA')
box = layout.box()
row = box.row()
row.operator('pfa.setsplineik',
text='Set Spline IK Bones', icon='ARMATURE_DATA')
def settingPFA(inputCurveObj, type):
def closestPointOnCurveFromPoint(ps, q):
# https://gist.github.com/pgtwitter/d8f981362fa68e6e9c14cb3f1aaa6f68
def f(p, i, t):
return p[0][i]*(1-t)**3+p[1][i]*3*((1-t)**2)*t+p[2][i]*3*(1-t)*t**2+p[3][i]*t**3
def _d(p, q):
return (p[0]-q[0])**2+(p[1]-q[1])**2+(p[2]-q[2])**2
def solve_sub(s, e, p, q):
d = (e-s)/5.0
rs = [(d*i+s, _d((f(p, 0, d*i+s), f(p, 1, d*i+s), f(p, 2, d*i+s)), q))
for i in range(6)]
rs = sorted(rs, key=lambda x: x[1])
ts = sorted((rs[0][0], rs[1][0], rs[2][0]))
if ts[-1]-ts[0] < 1E-6:
return rs[0][0], rs[0][0], rs[0][1]
return ts[0], ts[-1], rs[0][1]
def solve(p, q):
st, et, d = 0, 1, 1E10
for i in range(100):
st, et, d = solve_sub(st, et, p, q)
if et-st < 1E-6:
break
return (et + st) / 2.0, d
ans = []
for idx, p in enumerate(ps):
t, d = solve(p, q)
ans.append((idx, t, (f(p, 0, t), f(p, 1, t)), d))
sortedR0 = sorted(ans, key=lambda x: x[-1])
return sortedR0[0] # idx, t, sp, d
def curve2points(curveObj):
bpy.ops.object.mode_set(mode='OBJECT')
ret = []
matrix = curveObj.matrix_world
spline = curveObj.data.splines[0]
ru = spline.resolution_u
for idx in range(len(spline.bezier_points)-1):
p0 = spline.bezier_points[idx]
p1 = spline.bezier_points[idx+1]
ps = mathutils.geometry.interpolate_bezier(
matrix @ p0.co,
matrix @ p0.handle_right,
matrix @ p1.handle_left,
matrix @ p1.co,
ru)
if (idx == 0):
ret.extend(ps)
else:
ret.extend(ps[1:])
return ret
def points2borns(armatureObj, points, ext, useDeform):
bpy.context.view_layer.objects.active = armatureObj
armature = armatureObj.data
bpy.ops.object.mode_set(mode='EDIT')
parentBone = None
n = len(armature.edit_bones)
names = []
for idx in range(len(points)-1):
if n == 1 and idx == 0:
bone = armature.edit_bones[idx]
else:
bone = armature.edit_bones.new('Bone')
bone.name = f'{armature.name}_{ext}.{idx:03d}'
names.append(bone.name)
bone.use_deform = useDeform
bone.head = points[idx].copy()
bone.tail = points[idx+1].copy()
if parentBone is not None:
bone.parent = parentBone
bone.use_connect = True
parentBone = bone
bpy.ops.object.mode_set(mode='OBJECT')
return names
def setLayers(target, lidx):
for idx in range(2):
for idx in range(32):
target.layers[idx] = True if idx == lidx else False
def mkBonesWithPoints(points, armatureObj, ext, useDeform, l):
bpy.context.view_layer.objects.active = armatureObj
names = points2borns(armatureObj, points, ext, useDeform)
bpy.context.view_layer.objects.active = armatureObj
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.armature.select_all(action='DESELECT')
for name in names:
ebone = armatureObj.data.edit_bones[name]
setLayers(ebone, l)
bpy.ops.object.mode_set(mode='OBJECT')
return names
def armatureWithCurve(curveObj):
bpy.ops.object.mode_set(mode='OBJECT')
points = curve2points(curveObj)
bpy.ops.object.select_all(action='DESELECT')
bpy.ops.object.armature_add()
armatureObj = bpy.context.object
armatureObj.name = f'{curveObj.name}_ARM'
armatureObj.data.name = armatureObj.name
bpy.context.view_layer.objects.active = armatureObj
namesFK = mkBonesWithPoints(points, armatureObj, 'FK', True, 19)
namesPh = mkBonesWithPoints(points, armatureObj, 'physics', False, 27)
return armatureObj, points, namesFK, namesPh
def makeMeshWithCurvePoints(points, name):
bpy.ops.mesh.primitive_plane_add()
meshObj = bpy.context.object
meshObj.name = name
mesh = meshObj.data
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.object.mode_set(mode='OBJECT')
for i in range(1, len(mesh.vertices)):
mesh.vertices[i].select = True
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.delete(type='VERT')
bpy.ops.object.mode_set(mode='OBJECT')
mesh.vertices[0].co = points[0]
for i in range(1, len(points)):
mesh.vertices.add(1)
mesh.vertices[i].co = points[i]
mesh.edges.add(1)
mesh.edges[i-1].vertices = (i-1, i)
mesh.update()
bpy.ops.object.mode_set(mode='OBJECT')
bpy.context.view_layer.objects.active = meshObj
bpy.ops.object.convert(target='CURVE')
meshObj.data.bevel_depth = 0.04
meshObj.data.bevel_resolution = 0
bpy.ops.object.convert(target='MESH')
meshObj = bpy.context.object
meshObj.name = f'{name}_phyObj'
meshObj.data.name = meshObj.name
return meshObj
def setWeight(mesh, weight, ls, idx):
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.object.mode_set(mode='OBJECT')
for offset in range(4):
mesh.vertices[ls[idx*4+offset][0]].select = True
bpy.ops.object.mode_set(mode='EDIT')
bpy.context.scene.tool_settings.vertex_group_weight = weight
bpy.ops.object.vertex_group_assign()
bpy.ops.object.mode_set(mode='OBJECT')
def setClothMod(curveObjOrig, meshObj):
mesh = meshObj.data
bpy.ops.object.vertex_group_add()
vg = meshObj.vertex_groups[-1]
vg.name = f'{meshObj.name}.000'
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.mesh.select_mode(type='VERT')
bpy.ops.object.mode_set(mode='OBJECT')
matrix = curveObjOrig.matrix_world
spline = curveObjOrig.data.splines[0]
bezirs = []
for idx in range(len(spline.bezier_points)-1):
p0 = spline.bezier_points[idx]
p1 = spline.bezier_points[idx+1]
bezirs.append([
matrix @ p0.co,
matrix @ p0.handle_right,
matrix @ p1.handle_left,
matrix @ p1.co])
meshMatrix = meshObj.matrix_world
ls = []
for iidx, v in enumerate(mesh.vertices):
q = (meshMatrix @ v.co)
idx, t, r, d = closestPointOnCurveFromPoint(bezirs, q)
ls.append((iidx, idx+t))
ls = sorted(ls, key=lambda x: x[1])
bpy.ops.object.mode_set(mode='OBJECT')
for idx in range(int(len(ls)/4)):
setWeight(mesh, (1.0 if idx == 0 else 0.2), ls, idx)
bpy.ops.object.mode_set(mode='EDIT')
# bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.modifier_add(type='CLOTH')
clothMod = meshObj.modifiers[-1]
clothMod.settings.vertex_group_mass = vg.name
return ls
def setDUMPED_TRACK(meshObj, armatureObj, ls, namesPh):
mesh = meshObj.data
armature = armatureObj.data
vgs = []
for idx in range(1, int(len(ls)/4)):
bpy.ops.object.vertex_group_add()
vg = meshObj.vertex_groups[-1]
vg.name = f'{meshObj.name}.{idx:03d}'
vgs.append(vg)
setWeight(mesh, 1.0, ls, idx)
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active = armatureObj
bpy.ops.object.mode_set(mode='POSE')
setLayers(armature, 27)
for idx, name in enumerate(namesPh): # Ph
armature.bones.active = armature.bones[name]
bpy.ops.pose.constraint_add(type='DAMPED_TRACK')
dampTrackM = bpy.context.active_pose_bone.constraints[-1]
dampTrackM.target = meshObj
dampTrackM.subtarget = vgs[idx].name
bpy.ops.object.mode_set(mode='OBJECT')
def setCOPY_ROTATION(armatureObj, namesFK, namesPh):
armature = armatureObj.data
bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active = armatureObj
bpy.ops.object.mode_set(mode='POSE')
setLayers(armature, 19)
for idx, nameFK in enumerate(namesFK): # FK
armature.bones.active = armature.bones[nameFK]
bpy.ops.pose.constraint_add(type='COPY_ROTATION')
copyRotationM = bpy.context.active_pose_bone.constraints[-1]
copyRotationM.target = armatureObj
copyRotationM.subtarget = namesPh[idx] # Ph
copyRotationM.mix_mode = 'BEFORE'
copyRotationM.target_space = 'LOCAL'
copyRotationM.owner_space = 'LOCAL'
bpy.ops.object.mode_set(mode='OBJECT')
def setParentBone(armatureObj, meshObj, points, namesFK, namesPh):
bpy.context.view_layer.objects.active = armatureObj
armature = armatureObj.data
bpy.ops.object.mode_set(mode='EDIT')
ebone = armature.edit_bones.new('Bone')
name = f'{armature.name}_parent'
ebone.name = name
ebone.head = points[0].copy()
ebone.tail = points[1].copy()
ebone.length = ebone.length / 2
setLayers(ebone, 19)
armature.edit_bones[namesFK[0]].parent = ebone
armature.edit_bones[namesPh[0]].parent = ebone
bpy.ops.object.mode_set(mode='OBJECT')
bpy.context.view_layer.objects.active = meshObj
bpy.ops.object.constraint_add(type='CHILD_OF')
childOfM = bpy.context.object.constraints[-1]
childOfM.target = armatureObj
childOfM.subtarget = name
bpy.ops.constraint.childof_set_inverse(constraint='Child Of', owner='OBJECT')
def mkSplineIK(curveObj):
def curve2straightBones(curveObj):
points = curve2points(curveObj)
bpy.ops.object.mode_set(mode='OBJECT')
spline = curveObj.data.splines[0]
ru = spline.resolution_u
delta = (points[-1]-points[0])
sPoints = [delta * float(idx)/float(ru) + points[0] for idx in range(ru+1)]
bpy.ops.object.armature_add()
armatureObj = bpy.context.object
armatureObj.name = f'{curveObj.name}_SpIK'
armatureObj.data.name = armatureObj.name
armature = armatureObj.data
namesSpIK = points2borns(armatureObj, sPoints, 'Bone', True)
bpy.ops.object.mode_set(mode='OBJECT')
return armatureObj, sPoints, namesSpIK
def points2spline(sPoints):
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.curve.primitive_nurbs_path_add()
pathObj = bpy.context.object
pathObj.name = f'{curveObj.name}_spline'
pathSpline = pathObj.data.splines[0]
pathPoints = pathSpline.points
pathPoints[0].co = (sPoints[0].x, sPoints[0].y, sPoints[0].z, 1.0)
pathPoints[-1].co = (sPoints[-1].x, sPoints[-1].y, sPoints[-1].z, 1.0)
for p in pathPoints:
p.select = True
pathPoints[0].select = False
pathPoints[-1].select = False
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.curve.delete(type='VERT')
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
pathSpline = pathObj.data.splines[0]
pathPoints = pathSpline.points
pathPoints[0].select = True
pathPoints[-1].select = True
bpy.ops.curve.subdivide()
pathSpline.order_u = 3
bpy.ops.object.mode_set(mode='OBJECT')
return pathObj
def setSPLINE_IK(armatureObj, pathObj, namesSpIK):
armature = armatureObj.data
bpy.ops.object.mode_set(mode='OBJECT')
bpy.context.view_layer.objects.active = armatureObj
bpy.ops.object.mode_set(mode='POSE')
armature.bones.active = armature.bones[namesSpIK[-1]]
bpy.ops.pose.constraint_add(type='SPLINE_IK')
splineIKM = bpy.context.active_pose_bone.constraints[-1]
splineIKM.target = pathObj
splineIKM.chain_count = len(namesSpIK)
bpy.ops.object.mode_set(mode='OBJECT')
def setHOOK(armatureObj, pathObj, sPoints, namesSpIK):
armature = armatureObj.data
namesSpCTRL = []
bpy.ops.object.mode_set(mode='EDIT')
for idx, pos in enumerate([0, math.ceil(len(namesSpIK)/2.0), -2]):
h = sPoints[pos]
t = sPoints[pos+1]
b = armature.edit_bones.new('Bone')
b.head = h.copy()
b.tail = t.copy()
b.name = f'{armature.name}_CTRL.{idx:03d}'
namesSpCTRL.append(b.name)
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active = pathObj
pathObj.select_set(True)
for idx, bname in enumerate(namesSpCTRL):
bpy.ops.object.modifier_add(type='HOOK')
hook = pathObj.modifiers[-1]
bpy.ops.object.modifier_set_active(modifier=hook.name)
hook.object = armatureObj
hook.subtarget = bname
bpy.ops.object.mode_set(mode='EDIT')
for iidx, p in enumerate(pathObj.data.splines[0].points):
p.select = True if idx == iidx else False
bpy.ops.object.hook_assign(modifier=(hook.name))
bpy.ops.object.mode_set(mode='OBJECT')
return namesSpCTRL
armatureObj, sPoints, namesSpIK = curve2straightBones(curveObj)
pathObj = points2spline(sPoints)
setSPLINE_IK(armatureObj, pathObj, namesSpIK)
namesSpCTRL = setHOOK(armatureObj, pathObj, sPoints, namesSpIK)
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.mesh.primitive_cube_add()
cube = bpy.context.object
cube.name = f'{pathObj.name}_CTRL_shape'
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.delete(type='ONLY_FACE')
bpy.ops.object.mode_set(mode='OBJECT')
for name in namesSpCTRL:
armatureObj.pose.bones[name].custom_shape = cube
armatureObj.pose.bones[name].custom_shape_scale_xyz = (0.5, 0.5, 0.5)
bpy.ops.object.mode_set(mode='OBJECT')
cube.hide_set(True)
cube.hide_render = True
if type == 0:
t = bpy.context.scene.frame_current
bpy.context.scene.frame_current = 1
armatureObj, points, namesFK, namesPh = armatureWithCurve(inputCurveObj)
meshObj = makeMeshWithCurvePoints(points, inputCurveObj.name)
ls = setClothMod(inputCurveObj, meshObj)
setDUMPED_TRACK(meshObj, armatureObj, ls, namesPh)
setCOPY_ROTATION(armatureObj, namesFK, namesPh)
setParentBone(armatureObj, meshObj, points, namesFK, namesPh)
bpy.context.scene.frame_current = t
if type == 1:
mkSplineIK(inputCurveObj)
return True
def demo(name='inputCurve'):
def clear():
for c in bpy.data.curves:
bpy.data.curves.remove(c)
for a in bpy.data.armatures:
bpy.data.armatures.remove(a)
for m in bpy.data.meshes:
bpy.data.meshes.remove(m)
clear()
bpy.ops.curve.primitive_bezier_curve_add()
curveObj = bpy.context.object
curveObj.name = name
curve = curveObj.data
curve.name = curveObj.name
# curve.resolution_u = 12
if False:
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.curve.select_all(action='DESELECT')
point = curve.splines[0].bezier_points[0]
point.select_control_point = True
point.select_left_handle = True
point.select_right_handle = True
bpy.ops.curve.extrude(mode='TRANSLATION')
point = curve.splines[0].bezier_points[0]
point.co.x += -1
point.handle_left.x += -1
point.handle_right.x += -1
bpy.ops.object.mode_set(mode='OBJECT')
curveObj.rotation_euler = (0, 3.1415926/2.0, 0)
return curveObj
def setPFA(operator, context):
if not isinstance(context.object.data, bpy.types.Curve):
return False
return settingPFA(context.object, 0)
def setSplineIK(operator, context):
if not isinstance(context.object.data, bpy.types.Curve):
return False
return settingPFA(context.object, 1)
class PFA_ObjectOperator_setSplineIK(bpy.types.Operator):
'''Set Spline IK Bones'''
bl_idname = 'pfa.setsplineik'
bl_label = 'Set Spline IK Bones'
@ classmethod
def poll(cls, context):
if (context.object in context.selected_objects and isinstance(context.object.data, bpy.types.Curve)):
return True
return False
def execute(self, context):
return {'FINISHED'} if setSplineIK(self, context) else {'CANCELLED'}
class PFA_ObjectOperator_setPFA(bpy.types.Operator):
'''Set Physics and FK Bones'''
bl_idname = 'pfa.setpfa'
bl_label = 'Set Physics and FK Bones'
@ classmethod
def poll(cls, context):
if (context.object in context.selected_objects and isinstance(context.object.data, bpy.types.Curve)):
return True
return False
def execute(self, context):
return {'FINISHED'} if setPFA(self, context) else {'CANCELLED'}
class PFA_ObjectOperator_demo(bpy.types.Operator):
'''Create Curve for DEMO'''
bl_idname = 'pfa.demo'
bl_label = 'Create Curve for DEMO'
@ classmethod
def poll(cls, context):
return True
def execute(self, context):
obj = demo()
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
obj.select_set(True)
return {'FINISHED'}
def register():
bpy.utils.register_class(PFA_ObjectOperator_setSplineIK)
bpy.utils.register_class(PFA_ObjectOperator_setPFA)
bpy.utils.register_class(PFA_ObjectOperator_demo)
bpy.utils.register_class(PFA_Panel)
def unregister():
bpy.utils.unregister_class(PFA_Panel)
bpy.utils.unregister_class(PFA_ObjectOperator_demo)
bpy.utils.unregister_class(PFA_ObjectOperator_setPFA)
bpy.utils.unregister_class(PFA_ObjectOperator_setSplineIK)
if __name__ == '__main__':
register()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment