Last active
July 4, 2023 17:50
-
-
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 )
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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