Advertisement
Guest User

Untitled

a guest
May 4th, 2018
1,004
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 28.99 KB | None | 0 0
  1. # Rbx Animations Blender Addon
  2. # Written by Den_S/@DennisRBLX
  3. #
  4. # For your information:
  5. # Armature is assumed to have the identity matrix(!!!)
  6. # When creating a rig, bones are first created in a way they were in the original rig data,
  7. # the resulting matrices are stored as base matrices.
  8. # Then, bone tails are moved to be in a more intuitive position (helps IK etc too)
  9. # This transformation is thus undone when exporting
  10. # Blender also uses a Z-up/-Y-forward coord system, so this results in more transformations
  11. # Transform <=> Original **world space** CFrame, should match the associate mesh base matrix, Transform1 <=> C1
  12. # The meshes are imported in a certain order. Mesh names are restored using attached metadata.
  13. # Rig data is also encoded in this metdata.
  14. #
  15. # Communication:
  16. # To blender: A bunch of extra meshes whose names encode metadata (they are numbered, the contents are together encoded in base32)
  17. # From blender: Base64-encoded string (after compression)
  18.  
  19. import bpy, math, re, json, bpy_extras
  20. from itertools import chain
  21. from mathutils import Vector, Matrix
  22. import zlib
  23. import base64
  24. from bpy_extras.io_utils import ImportHelper
  25. from bpy.props import *
  26.  
  27. transform_to_blender = bpy_extras.io_utils.axis_conversion(from_forward='Z', from_up='Y', to_forward='-Y', to_up='Z').to_4x4() # transformation matrix from Y-up to Z-up
  28. identity_cf = [0,0,0,1,0,0,0,1,0,0,0,1] # identity CF components matrix
  29. cf_round = False # round cframes before exporting? (reduce size)
  30. cf_round_fac = 4 # round to how many decimals?
  31.  
  32. # y-up cf -> y-up mat
  33. def cf_to_mat(cf):
  34. mat = Matrix.Translation((cf[0], cf[1], cf[2]))
  35. mat[0][0:3] = (cf[3], cf[4], cf[5])
  36. mat[1][0:3] = (cf[6], cf[7], cf[8])
  37. mat[2][0:3] = (cf[9], cf[10], cf[11])
  38. return mat
  39.  
  40. # y-up mat -> y-up cf
  41. def mat_to_cf(mat):
  42. r_mat = [mat[0][3], mat[1][3], mat[2][3],
  43. mat[0][0], mat[0][1], mat[0][2],
  44. mat[1][0], mat[1][1], mat[1][2],
  45. mat[2][0], mat[2][1], mat[2][2]
  46. ]
  47. return r_mat
  48.  
  49. # links the passed object to the bone with the transformation equal to the current(!) transformation between the bone and object
  50. def link_object_to_bone_rigid(obj, ao, bone):
  51. # remove existing
  52. for constraint in [c for c in obj.constraints if c.type == 'CHILD_OF']:
  53. obj.constraints.remove(constraint)
  54.  
  55. # create new
  56. constraint = obj.constraints.new(type = 'CHILD_OF')
  57. constraint.target = ao
  58. constraint.subtarget = bone.name
  59. constraint.inverse_matrix = (ao.matrix_world * bone.matrix).inverted()
  60.  
  61. # serializes the current bone state to a dict
  62. def serialize_animation_state(ao):
  63. state = {}
  64. for bone in ao.pose.bones:
  65. if 'is_transformable' in bone.bone:
  66. # original matrices, straight from the import cfs
  67. # this is always the true baseline
  68. orig_mat = Matrix(bone.bone['transform'])
  69. orig_mat_tr1 = Matrix(bone.bone['transform1'])
  70. parent_orig_mat = Matrix(bone.parent.bone['transform'])
  71. parent_orig_mat_tr1 = Matrix(bone.parent.bone['transform1'])
  72.  
  73. # get the bone neutral transform
  74. extr_transform = Matrix(bone.bone['nicetransform']).inverted()
  75. parent_extr_transform = Matrix(bone.parent.bone['nicetransform']).inverted()
  76.  
  77. # z-up -> y-up transform matrix
  78. back_trans = transform_to_blender.inverted()
  79.  
  80. # get the real bone transform
  81. cur_obj_transform = back_trans * (bone.matrix * extr_transform)
  82. parent_obj_transform = back_trans * (bone.parent.matrix * parent_extr_transform)
  83.  
  84. # compute neutrals after applying C1/transform1
  85. orig_base_mat = back_trans * (orig_mat * orig_mat_tr1)
  86. parent_orig_base_mat = back_trans * (parent_orig_mat * parent_orig_mat_tr1)
  87.  
  88. # compute y-up bone transform (transformation between C0 and C1)
  89. orig_transform = parent_orig_base_mat.inverted() * orig_base_mat
  90. cur_transform = parent_obj_transform.inverted() * cur_obj_transform
  91. bone_transform = orig_transform.inverted() * cur_transform
  92.  
  93. statel = mat_to_cf(bone_transform)
  94. if cf_round:
  95. statel = list(map(lambda x: round(x, cf_round_fac), statel)) # compresses result
  96.  
  97. # flatten, compresses the resulting json too
  98. for i in range(len(statel)):
  99. if int(statel[i]) == statel[i]:
  100. statel[i] = int(statel[i])
  101.  
  102. # only store if not identity, compresses the resulting json
  103. if statel != identity_cf:
  104. state[bone.name] = statel
  105.  
  106. return state
  107.  
  108. # removes all IK stuff from a bone
  109. def remove_ik_config(ao, tail_bone):
  110. to_clear = []
  111. for constraint in [c for c in tail_bone.constraints if c.type == 'IK']:
  112. if constraint.target and constraint.subtarget:
  113. to_clear.append((constraint.target, constraint.subtarget))
  114. if constraint.pole_target and constraint.pole_subtarget:
  115. to_clear.append((constraint.pole_target, constraint.pole_subtarget))
  116.  
  117. tail_bone.constraints.remove(constraint)
  118.  
  119. bpy.ops.object.mode_set(mode='EDIT')
  120.  
  121. for util_bone in to_clear:
  122. util_bone[0].data.edit_bones.remove(util_bone[0].data.edit_bones[util_bone[1]])
  123.  
  124. bpy.ops.object.mode_set(mode='POSE')
  125.  
  126. # created IK bones and constraints for a given chain
  127. def create_ik_config(ao, tail_bone, chain_count, create_pose_bone, lock_tail):
  128. lock_tail = False # not implemented
  129.  
  130. bpy.ops.object.mode_set(mode='EDIT')
  131.  
  132. amt = ao.data
  133. ik_target_bone = tail_bone if not lock_tail else tail_bone.parent
  134.  
  135. ik_target_bone_name = ik_target_bone.name
  136. ik_name = "{}-IKTarget".format(ik_target_bone_name)
  137. ik_name_pole = "{}-IKPole".format(ik_target_bone_name)
  138.  
  139. ik_bone = amt.edit_bones.new(ik_name)
  140. ik_bone.head = ik_target_bone.tail
  141. ik_bone.tail = (Matrix.Translation(ik_bone.head) * ik_target_bone.matrix.to_3x3().to_4x4()) * Vector((0, 0, -.5))
  142. ik_bone.bbone_x *= 1.5
  143. ik_bone.bbone_z *= 1.5
  144.  
  145. ik_pole = None
  146. if create_pose_bone:
  147. pos_low = tail_bone.tail
  148. pos_high = tail_bone.parent_recursive[chain_count-2].head
  149. pos_avg = (pos_low + pos_high) * .5
  150. dist = (pos_low - pos_high).length
  151.  
  152. basal_bone = tail_bone
  153. for i in range(1, chain_count):
  154. if basal_bone.parent:
  155. basal_bone = basal_bone.parent
  156.  
  157. basal_mat = basal_bone.bone.matrix_local
  158.  
  159. ik_pole = amt.edit_bones.new(ik_name_pole)
  160. ik_pole.head = basal_mat * Vector((0, 0, dist * -.25))
  161. ik_pole.tail = basal_mat * Vector((0, 0, dist * -.25 - .3))
  162. ik_pole.bbone_x *= .5
  163. ik_pole.bbone_z *= .5
  164.  
  165. bpy.ops.object.mode_set(mode='POSE')
  166.  
  167. pose_bone = ao.pose.bones[ik_target_bone_name]
  168. constraint = pose_bone.constraints.new(type = 'IK')
  169. constraint.target = ao
  170. constraint.subtarget = ik_name
  171. if create_pose_bone:
  172. constraint.pole_target = ao
  173. constraint.pole_subtarget = ik_name_pole
  174. constraint.pole_angle = math.pi * -.5
  175. constraint.chain_count = chain_count
  176.  
  177. # loads a (child) rig bone
  178. def load_rigbone(ao, rigging_type, rigsubdef, parent_bone):
  179. amt = ao.data
  180. bone = amt.edit_bones.new(rigsubdef['jname'])
  181.  
  182. mat = cf_to_mat(rigsubdef['transform'])
  183. bone["transform"] = mat
  184. bone_dir = (transform_to_blender*mat).to_3x3().to_4x4() * Vector((0, 0, 1))
  185.  
  186. if 'jointtransform0' not in rigsubdef:
  187. # Rig root
  188. bone.head = (transform_to_blender*mat).to_translation()
  189. bone.tail = (transform_to_blender*mat) * Vector((0, .01, 0))
  190. bone["transform0"] = Matrix()
  191. bone["transform1"] = Matrix()
  192. bone['nicetransform'] = Matrix()
  193. bone.align_roll(bone_dir)
  194. bone.hide_select = True
  195. pre_mat = bone.matrix
  196. else:
  197. mat0 = cf_to_mat(rigsubdef['jointtransform0'])
  198. mat1 = cf_to_mat(rigsubdef['jointtransform1'])
  199. bone["transform0"] = mat0
  200. bone["transform1"] = mat1
  201. bone["is_transformable"] = True
  202.  
  203. bone.parent = parent_bone
  204. o_trans = transform_to_blender*(mat*mat1)
  205. bone.head = o_trans.to_translation()
  206. real_tail = o_trans * Vector((0, .25, 0))
  207.  
  208. neutral_pos = (transform_to_blender*mat).to_translation()
  209. bone.tail = real_tail
  210. bone.align_roll(bone_dir)
  211.  
  212. # store neutral matrix
  213. pre_mat = bone.matrix
  214.  
  215. if rigging_type != 'RAW': # If so, apply some transform
  216. if len(rigsubdef['children']) == 1:
  217. nextmat = cf_to_mat(rigsubdef['children'][0]['transform'])
  218. nextmat1 = cf_to_mat(rigsubdef['children'][0]['jointtransform1'])
  219. next_joint_pos = (transform_to_blender*(nextmat*nextmat1)).to_translation()
  220.  
  221. if rigging_type == 'CONNECT': # Instantly connect
  222. bone.tail = next_joint_pos
  223. else:
  224. axis = 'y'
  225. if rigging_type == 'LOCAL_AXIS_EXTEND': # Allow non-Y too
  226. invtrf = pre_mat.inverted() * next_joint_pos
  227. bestdist = abs(invtrf.y)
  228. for paxis in ['x', 'z']:
  229. dist = abs(getattr(invtrf, paxis))
  230. if dist > bestdist:
  231. bestdist = dist
  232. axis = paxis
  233.  
  234. next_connect_to_parent = True
  235.  
  236. ppd_nr_dir = real_tail - bone.head
  237. ppd_nr_dir.normalize()
  238. proj = ppd_nr_dir.dot(next_joint_pos - bone.head)
  239. vis_world_root = ppd_nr_dir * proj
  240. bone.tail = bone.head + vis_world_root
  241.  
  242. else:
  243. bone.tail = bone.head + (bone.head - neutral_pos) * -2
  244.  
  245. if (bone.tail - bone.head).length < .01:
  246. # just reset, no "nice" config can be found
  247. bone.tail = real_tail
  248. bone.align_roll(bone_dir)
  249.  
  250. # fix roll
  251. bone.align_roll(bone_dir)
  252.  
  253. post_mat = bone.matrix
  254.  
  255. # this value stores the transform between the "proper" matrix and the "nice" matrix where bones are oriented in a more friendly way
  256. bone['nicetransform'] = pre_mat.inverted() * post_mat
  257.  
  258. # link objects to bone
  259. for aux in rigsubdef['aux']:
  260. if aux and aux in bpy.data.objects:
  261. obj = bpy.data.objects[aux]
  262. link_object_to_bone_rigid(obj, ao, bone)
  263.  
  264. # handle child bones
  265. for child in rigsubdef['children']:
  266. load_rigbone(ao, rigging_type, child, bone)
  267.  
  268. # renames parts to whatever the metadata defines, mostly just for user-friendlyness (not required)
  269. def autoname_parts(partnames, basename):
  270. indexmatcher = re.compile(basename + '(\d+)(\.\d+)?', re.IGNORECASE)
  271. for object in bpy.data.objects:
  272. match = indexmatcher.match(object.name.lower())
  273. if match:
  274. index = int(match.group(1))
  275. object.name = partnames[-index]
  276.  
  277. # removes existing rig if it exists, then builds a new one using the stored metadata
  278. def create_rig(rigging_type):
  279. bpy.ops.object.mode_set(mode='OBJECT')
  280. if '__Rig' in bpy.data.objects:
  281. bpy.data.objects['__Rig'].select = True
  282. bpy.ops.object.delete()
  283.  
  284. meta_loaded = json.loads(bpy.data.objects['__RigMeta']['RigMeta'])
  285.  
  286. bpy.ops.object.add(type='ARMATURE', enter_editmode=True, location=(0,0,0))
  287. ao = bpy.context.object
  288. ao.show_x_ray = True
  289. ao.name = '__Rig'
  290. amt = ao.data
  291. amt.name = '__RigArm'
  292. amt.show_axes = True
  293. amt.show_names = True
  294.  
  295. bpy.ops.object.mode_set(mode='EDIT')
  296. load_rigbone(ao, rigging_type, meta_loaded['rig'], None)
  297.  
  298. bpy.ops.object.mode_set(mode='OBJECT')
  299.  
  300.  
  301. # export the entire animation to the clipboard (serialized), returns animation time
  302. def serialize():
  303. ao = bpy.data.objects['__Rig']
  304. ctx = bpy.context
  305. bake_jump = ctx.scene.frame_step
  306.  
  307. collected = []
  308. frames = ctx.scene.frame_end+1 - ctx.scene.frame_start
  309. cur_frame = ctx.scene.frame_current
  310. for i in range(ctx.scene.frame_start, ctx.scene.frame_end+1, bake_jump):
  311. ctx.scene.frame_set(i)
  312. ctx.scene.update()
  313.  
  314. state = serialize_animation_state(ao)
  315. collected.append({'t': (i - ctx.scene.frame_start) / ctx.scene.render.fps, 'kf': state})
  316.  
  317. ctx.scene.frame_set(cur_frame)
  318.  
  319. result = {
  320. 't': (frames-1) / ctx.scene.render.fps,
  321. 'kfs': collected
  322. }
  323.  
  324. return result
  325.  
  326. def copy_anim_state_bone(target, source, bone):
  327. # get transform mat of the bone in the source ao
  328. bpy.context.scene.objects.active = source
  329. t_mat = source.pose.bones[bone.name].matrix
  330.  
  331. bpy.context.scene.objects.active = target
  332.  
  333. # root bone transform is ignored, this is carried to child bones (keeps HRP static)
  334. if bone.parent:
  335. # apply transform w.r.t. the current parent bone transform
  336. r_mat = bone.bone.matrix_local
  337. p_mat = bone.parent.matrix
  338. p_r_mat = bone.parent.bone.matrix_local
  339. bone.matrix_basis = (p_r_mat.inverted() * r_mat).inverted() * (p_mat.inverted() * t_mat)
  340.  
  341. # update properties (hacky :p)
  342. bpy.ops.anim.keyframe_insert()
  343. bpy.context.scene.frame_set(bpy.context.scene.frame_current)
  344.  
  345. # now apply on children (which use the parents transform)
  346. for ch in bone.children:
  347. copy_anim_state_bone(target, source, ch)
  348.  
  349. def copy_anim_state(target, source):
  350. # to pose mode
  351. bpy.context.scene.objects.active = source
  352. bpy.ops.object.mode_set(mode='POSE')
  353.  
  354. bpy.context.scene.objects.active = target
  355. bpy.ops.object.mode_set(mode='POSE')
  356.  
  357. root = target.pose.bones['HumanoidRootPart']
  358.  
  359. for i in range(bpy.context.scene.frame_start, bpy.context.scene.frame_end+1):
  360. bpy.context.scene.frame_set(i)
  361. copy_anim_state_bone(target, source, root)
  362. bpy.ops.anim.keyframe_insert()
  363.  
  364. def prepare_for_kf_map():
  365. # clear anim data from target rig
  366. bpy.data.objects['__Rig'].animation_data_clear()
  367.  
  368. # select all pose bones in the target rig (simply generate kfs for everything)
  369. bpy.context.scene.objects.active = bpy.data.objects['__Rig']
  370. bpy.ops.object.mode_set(mode='POSE')
  371. for bone in bpy.data.objects['__Rig'].pose.bones:
  372. bone.bone.select = not not bone.parent
  373.  
  374. def get_mapping_error_bones(target, source):
  375. return [bone.name for bone in target.data.bones if bone.name not in [bone2.name for bone2 in source.data.bones]]
  376.  
  377. # apply ao transforms to the root PoseBone
  378. # + clear ao animation tracks (root only, not Pose anim data) + reset ao transform to identity
  379. def apply_ao_transform(ao):
  380. bpy.context.scene.objects.active = ao
  381. bpy.ops.object.mode_set(mode='POSE')
  382.  
  383. # select only root bones
  384. for bone in ao.pose.bones:
  385. bone.bone.select = not bone.parent
  386.  
  387. for root in [bone for bone in ao.pose.bones if not bone.parent]:
  388. # collect initial root matrices (if they do not exist yet, this will prevent interpolation from keyframes that are being set in the next loop)
  389. root_matrix_at = {}
  390. for i in range(bpy.context.scene.frame_start, bpy.context.scene.frame_end+1):
  391. bpy.context.scene.frame_set(i)
  392. root_matrix_at[i] = root.matrix.copy()
  393.  
  394. # apply world space transform to root bone
  395. for i in range(bpy.context.scene.frame_start, bpy.context.scene.frame_end+1):
  396. bpy.context.scene.frame_set(i)
  397. root.matrix = ao.matrix_world * root_matrix_at[i]
  398. bpy.ops.anim.keyframe_insert()
  399.  
  400. # clear non-pose fcurves
  401. fcurves = ao.animation_data.action.fcurves
  402. for c in [c for c in fcurves if not c.data_path.startswith('pose')]:
  403. fcurves.remove(c)
  404.  
  405. # reset ao transform
  406. ao.matrix_basis = Matrix.Identity(4)
  407. bpy.context.scene.update()
  408.  
  409. ## UI/OPERATOR STUFF ##
  410.  
  411. class OBJECT_OT_ImportModel(bpy.types.Operator, ImportHelper):
  412. bl_label = "Import rig data (.obj)"
  413. bl_idname = "object.rbxanims_importmodel"
  414. bl_description = "Import rig data (.obj)"
  415.  
  416. filename_ext = ".obj"
  417. filter_glob = bpy.props.StringProperty(default="*.obj", options={'HIDDEN'})
  418. filepath = bpy.props.StringProperty(name="File Path", maxlen=1024, default="")
  419.  
  420. def execute(self, context):
  421. # clear objects first
  422. for obj in bpy.data.objects:
  423. obj.select = obj.type == 'MESH' or obj.type == 'ARMATURE' or obj.name.startswith('__RigMeta')
  424. bpy.ops.object.delete()
  425.  
  426. bpy.ops.import_scene.obj(filepath=self.properties.filepath)
  427.  
  428. # Extract meta...
  429. encodedmeta = ''
  430. partial = {}
  431. for obj in bpy.data.objects:
  432. match = re.search(r'^Meta(\d+)q1(.*?)q1\d*(\.\d+)?$', obj.name)
  433. if match:
  434. partial[int(match.group(1))] = match.group(2)
  435.  
  436. obj.select = not not match
  437. bpy.ops.object.delete() # delete meta objects
  438.  
  439. for i in range(1, len(partial)+1):
  440. encodedmeta += partial[i]
  441. encodedmeta = encodedmeta.replace('0', '=')
  442. meta = base64.b32decode(encodedmeta, True).decode('utf-8')
  443.  
  444. # store meta in an empty
  445. bpy.ops.object.add(type='EMPTY', location=(0,0,0))
  446. ob = bpy.context.object
  447. ob.name = '__RigMeta'
  448. ob['RigMeta'] = meta
  449.  
  450. meta_loaded = json.loads(meta)
  451. autoname_parts(meta_loaded['parts'], meta_loaded['rigName'])
  452.  
  453. return {'FINISHED'}
  454.  
  455. def invoke(self, context, event):
  456. context.window_manager.fileselect_add(self)
  457. return {'RUNNING_MODAL'}
  458.  
  459. class OBJECT_OT_GenRig(bpy.types.Operator):
  460. bl_label = "Generate rig"
  461. bl_idname = "object.rbxanims_genrig"
  462. bl_description = "Generate rig"
  463.  
  464. pr_rigging_type = bpy.props.EnumProperty(items=[
  465. ('RAW', 'Nodes only', ''),
  466. ('LOCAL_AXIS_EXTEND', 'Local axis aligned bones', ''),
  467. ('LOCAL_YAXIS_EXTEND', 'Local Y-axis aligned bones', ''),
  468. ('CONNECT', 'Connect', '')
  469. ], name="Rigging type");
  470.  
  471. @classmethod
  472. def poll(cls, context):
  473. meta_obj = bpy.data.objects.get('__RigMeta')
  474. return meta_obj and 'RigMeta' in meta_obj
  475.  
  476. def execute(self, context):
  477. create_rig(self.pr_rigging_type)
  478. self.report({'INFO'}, "Rig rebuilt.")
  479. return {'FINISHED'}
  480.  
  481. def invoke(self, context, event):
  482. self.pr_rigging_type = 'LOCAL_YAXIS_EXTEND'
  483.  
  484. wm = context.window_manager
  485. return wm.invoke_props_dialog(self)
  486.  
  487. class OBJECT_OT_GenIK(bpy.types.Operator):
  488. bl_label = "Generate IK"
  489. bl_idname = "object.rbxanims_genik"
  490. bl_description = "Generate IK"
  491.  
  492. pr_chain_count = bpy.props.IntProperty(name = "Chain count (0 = to root)", min=0)
  493. pr_create_pose_bone = bpy.props.BoolProperty(name = "Create pose bone")
  494. pr_lock_tail_bone = bpy.props.BoolProperty(name = "Lock final bone orientation")
  495.  
  496. @classmethod
  497. def poll(cls, context):
  498. premise = context.active_object and context.active_object.mode == 'POSE'
  499. premise = premise and context.active_object and context.active_object.type == 'ARMATURE'
  500. return context.active_object and context.active_object.mode == 'POSE' and len([x for x in context.active_object.pose.bones if x.bone.select]) > 0
  501.  
  502. def execute(self, context):
  503.  
  504. to_apply = [b for b in context.active_object.pose.bones if b.bone.select]
  505.  
  506. for bone in to_apply:
  507. create_ik_config(context.active_object, bone, self.pr_chain_count, self.pr_create_pose_bone, self.pr_lock_tail_bone)
  508.  
  509. return {'FINISHED'}
  510.  
  511. def invoke(self, context, event):
  512. to_apply = [b for b in context.active_object.pose.bones if b.bone.select]
  513. if len(to_apply) == 0:
  514. return {'FINISHED'}
  515.  
  516. rec_chain_len = 1
  517. no_loop_mech = set()
  518. itr = to_apply[0].bone
  519. while itr and itr.parent and len(itr.parent.children) == 1 and itr not in no_loop_mech:
  520. rec_chain_len += 1
  521. no_loop_mech.add(itr)
  522. itr = itr.parent
  523.  
  524. self.pr_chain_count = rec_chain_len
  525. self.pr_create_pose_bone = False
  526. self.pr_lock_tail_bone = False
  527.  
  528. wm = context.window_manager
  529. return wm.invoke_props_dialog(self)
  530.  
  531. class OBJECT_OT_RemoveIK(bpy.types.Operator):
  532. bl_label = "Remove IK"
  533. bl_idname = "object.rbxanims_removeik"
  534. bl_description = "Remove IK"
  535.  
  536. @classmethod
  537. def poll(cls, context):
  538. premise = context.active_object and context.active_object.mode == 'POSE'
  539. premise = premise and context.active_object
  540. return context.active_object and context.active_object.mode == 'POSE' and len([x for x in context.active_object.pose.bones if x.bone.select]) > 0
  541.  
  542. def execute(self, context):
  543. to_apply = [b for b in context.active_object.pose.bones if b.bone.select]
  544.  
  545. for bone in to_apply:
  546. remove_ik_config(context.active_object, bone)
  547.  
  548. return {'FINISHED'}
  549.  
  550. class OBJECT_OT_ImportFbxAnimation(bpy.types.Operator, ImportHelper):
  551. bl_label = "Import animation data (.fbx)"
  552. bl_idname = "object.rbxanims_importfbxanimation"
  553. bl_description = "Import animation data (.fbx) --- FBX file should contain an armature, which will be mapped onto the generated rig by bone names."
  554.  
  555. filename_ext = ".fbx"
  556. filter_glob = bpy.props.StringProperty(default="*.fbx", options={'HIDDEN'})
  557. filepath = bpy.props.StringProperty(name="File Path", maxlen=1024, default="")
  558.  
  559. @classmethod
  560. def poll(cls, context):
  561. return bpy.data.objects.get('__Rig')
  562.  
  563. def execute(self, context):
  564. # check active keying set
  565. if not bpy.context.scene.keying_sets.active:
  566. self.report({'ERROR'}, 'There is no active keying set, this is required.')
  567. return {'FINISHED'}
  568.  
  569. # import and keep track of what is imported
  570. objnames_before_import = [x.name for x in bpy.data.objects]
  571. bpy.ops.import_scene.fbx(filepath=self.properties.filepath)
  572. objnames_imported = [x.name for x in bpy.data.objects if x.name not in objnames_before_import]
  573.  
  574. def clear_imported():
  575. bpy.ops.object.mode_set(mode='OBJECT')
  576. for obj in bpy.data.objects:
  577. obj.select = obj.name in objnames_imported
  578. bpy.ops.object.delete()
  579.  
  580. # check that there's only 1 armature
  581. armatures_imported = [x for x in bpy.data.objects if x.type == 'ARMATURE' and x.name in objnames_imported]
  582. if len(armatures_imported) != 1:
  583. self.report({'ERROR'}, 'Imported file contains {:d} armatures, expected 1.'.format(len(armatures_imported)))
  584. clear_imported()
  585. return {'FINISHED'}
  586.  
  587. ao_imp = armatures_imported[0]
  588.  
  589. err_mappings = get_mapping_error_bones(bpy.data.objects['__Rig'], ao_imp)
  590. if len(err_mappings) > 0:
  591. self.report({'ERROR'}, 'Cannot map rig, the following bones are missing from the source rig: {}.'.format(', '.join(err_mappings)))
  592. clear_imported()
  593. return {'FINISHED'}
  594.  
  595. bpy.context.scene.objects.active = ao_imp
  596.  
  597. # check that the ao contains anim data
  598. if not ao_imp.animation_data or not ao_imp.animation_data.action or not ao_imp.animation_data.action.fcurves:
  599. self.report({'ERROR'}, 'Imported armature contains no animation data.')
  600. clear_imported()
  601. return {'FINISHED'}
  602.  
  603. # get keyframes + boundary timestamps
  604. fcurves = ao_imp.animation_data.action.fcurves
  605. kp_frames = []
  606. for key in fcurves:
  607. kp_frames += [kp.co.x for kp in key.keyframe_points]
  608. if len(kp_frames) <= 0:
  609. self.report({'ERROR'}, 'Imported armature contains no keyframes.')
  610. clear_imported()
  611. return {'FINISHED'}
  612.  
  613. # set frame range
  614. bpy.context.scene.frame_start = math.floor(min(kp_frames))
  615. bpy.context.scene.frame_end = math.ceil(max(kp_frames))
  616.  
  617. # for the imported rig, apply ao transforms
  618. apply_ao_transform(ao_imp)
  619.  
  620. prepare_for_kf_map()
  621.  
  622. # actually copy state
  623. copy_anim_state(bpy.data.objects['__Rig'], ao_imp)
  624.  
  625. clear_imported()
  626. return {'FINISHED'}
  627.  
  628. def invoke(self, context, event):
  629. context.window_manager.fileselect_add(self)
  630. return {'RUNNING_MODAL'}
  631.  
  632. class OBJECT_OT_ApplyTransform(bpy.types.Operator):
  633. bl_label = "Apply armature object transform to the root bone for each keyframe"
  634. bl_idname = "object.rbxanims_applytransform"
  635. bl_description = "Apply armature object transform to the root bone for each keyframe -- Must set a proper frame range first!"
  636.  
  637. @classmethod
  638. def poll(cls, context):
  639. grig = bpy.data.objects.get('__Rig')
  640. return grig and bpy.context.active_object and bpy.context.active_object.animation_data
  641.  
  642. def execute(self, context):
  643. if not bpy.context.scene.keying_sets.active:
  644. self.report({'ERROR'}, 'There is no active keying set, this is required.')
  645. return {'FINISHED'}
  646.  
  647. apply_ao_transform(bpy.context.scene.objects.active)
  648.  
  649. return {'FINISHED'}
  650.  
  651. class OBJECT_OT_MapKeyframes(bpy.types.Operator):
  652. bl_label = "Map keyframes by bone name"
  653. bl_idname = "object.rbxanims_mapkeyframes"
  654. bl_description = "Map keyframes by bone name --- From a selected armature, maps data (using a new keyframe per frame) onto the generated rig by name. Set frame ranges first!"
  655.  
  656. @classmethod
  657. def poll(cls, context):
  658. grig = bpy.data.objects.get('__Rig')
  659. return grig and bpy.context.active_object and bpy.context.active_object != grig
  660.  
  661. def execute(self, context):
  662. if not bpy.context.scene.keying_sets.active:
  663. self.report({'ERROR'}, 'There is no active keying set, this is required.')
  664. return {'FINISHED'}
  665.  
  666. ao_imp = bpy.context.scene.objects.active
  667.  
  668. err_mappings = get_mapping_error_bones(bpy.data.objects['__Rig'], ao_imp)
  669. if len(err_mappings) > 0:
  670. self.report({'ERROR'}, 'Cannot map rig, the following bones are missing from the source rig: {}.'.format(', '.join(err_mappings)))
  671. return {'FINISHED'}
  672.  
  673. prepare_for_kf_map()
  674.  
  675. copy_anim_state(bpy.data.objects['__Rig'], ao_imp)
  676.  
  677. return {'FINISHED'}
  678.  
  679. class OBJECT_OT_Bake(bpy.types.Operator):
  680. bl_label = "Bake"
  681. bl_idname = "object.rbxanims_bake"
  682. bl_description = "Bake animation for export"
  683.  
  684. def execute(self, context):
  685. serialized = serialize()
  686. encoded = json.dumps(serialized, separators=(',',':'))
  687. bpy.context.window_manager.clipboard = (base64.b64encode(zlib.compress(encoded.encode(), 9))).decode('utf-8')
  688. self.report({'INFO'}, 'Baked animation data exported to the system clipboard ({:d} keyframes, {:.2f} seconds).'.format(len(serialized['kfs']), serialized['t']))
  689. return {'FINISHED'}
  690.  
  691. class OBJECT_PT_RbxAnimations(bpy.types.Panel):
  692. bl_label = "Rbx Animations"
  693. bl_idname = "OBJECT_PT_RbxAnimations"
  694. bl_category = "Animation"
  695. bl_space_type = 'VIEW_3D'
  696. bl_region_type = 'TOOLS'
  697.  
  698. def draw(self, context):
  699. layout = self.layout
  700. obj = context.object
  701.  
  702. layout.label(text="Import:")
  703. layout.operator("object.rbxanims_importmodel", text="Import model")
  704. layout.operator("object.rbxanims_genrig", text="Rebuild rig")
  705. layout.label(text="Rigging:")
  706. layout.operator("object.rbxanims_genik", text="Create IK constraints")
  707. layout.operator("object.rbxanims_removeik", text="Remove IK constraints")
  708. layout.label(text="Animation import:")
  709. layout.operator("object.rbxanims_importfbxanimation", text="Import FBX")
  710. layout.operator("object.rbxanims_mapkeyframes", text="Map keyframes by bone name")
  711. layout.operator("object.rbxanims_applytransform", text="Apply armature transform")
  712. layout.label(text="Export:")
  713. layout.operator("object.rbxanims_bake", text="Export animation", icon='RENDER_ANIMATION')
  714.  
  715. bl_info = {"name": "Rbx Animations", "category": "Animation"}
  716.  
  717. def register():
  718. bpy.utils.register_module(__name__)
  719.  
  720. def unregister():
  721. bpy.utils.unregister_module(__name__)
  722.  
  723. if __name__ == "__main__":
  724. register()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement