def make_armature(self, parent_node, armature): gltf_node = { 'name': armature.name, 'children': [], } gltf_joints = {} for bone_name, bone in armature.data.bones.items(): bone_matrix = get_bone_matrix(bone, armature) gltf_joint = { 'name': bone_name, 'children': [], 'rotation': quat_to_list(bone_matrix.to_quaternion()), 'scale': list(bone_matrix.to_scale()), 'translation': list(bone_matrix.to_translation()), } if bone.parent: self._add_child(gltf_joints[bone.parent.name], gltf_joint) else: self._add_child(gltf_node, gltf_joint) gltf_joints[bone_name] = gltf_joint self._setup_node(gltf_node, armature) self._add_child(parent_node, gltf_node) return gltf_node
def make_armature(self, parent_node, armature): channel = self._buffer.add_channel({ 'componentType': spec.TYPE_FLOAT, 'type': 'MAT4', 'extras': { 'reference': 'inverseBindMatrices', }, }) gltf_armature = { 'name': armature.name, 'children': [], } gltf_skin = { 'name': armature.name, 'joints': [], 'inverseBindMatrices': channel['bufferView'], } self._root['skins'].append(gltf_skin) if self._pose_freeze: set_active_object(armature) # disconnect bones with Mode('EDIT'): for bone_name, bone in armature.data.edit_bones.items(): bone.use_connect = False # reset bones rotation with Mode('EDIT'): for bone_name, bone in armature.data.edit_bones.items(): bone.roll = 0 bone.length = 10 # pos_matrix = mathutils.Matrix.Translation(bone.matrix.to_translation()) # rot_matrix = bone.matrix.to_quaternion().to_matrix() # bone.transform(pos_matrix.inverted()) # reset origin # bone.transform(rot_matrix.inverted()) # reset rotation # bone.transform(pos_matrix) # restore origin bone.tail = bone.head + mathutils.Vector( (0, bone.length, 0)) bone.roll = 0 # create joint nodes gltf_joints = {} for bone_name, bone in armature.data.bones.items(): bone_matrix = self._transform(get_bone_matrix(bone, armature)) if self._pose_freeze: bone_matrix = self._freeze(bone_matrix) gltf_joint = { 'name': bone_name, 'children': [], 'rotation': quat_to_list(bone_matrix.to_quaternion()), 'scale': list(bone_matrix.to_scale()), 'translation': list(bone_matrix.to_translation()), # 'matrix': matrix_to_list(bone_matrix), } gltf_joints[bone_name] = gltf_joint # add joints to skin for bone_name in sorted(gltf_joints.keys()): bone = armature.data.bones[bone_name] if bone.parent: # attach joint to parent joint self._add_child(gltf_joints[bone.parent.name], gltf_joints[bone.name]) else: # attach joint to armature # gltf_skin['skeleton'] = len(self._root['nodes']) - 1 self._add_child(gltf_armature, gltf_joints[bone.name]) ib_matrix = self._transform(get_inverse_bind_matrix( bone, armature)) if self._pose_freeze: ib_matrix = self._freeze(ib_matrix) self._buffer.write(gltf_skin['inverseBindMatrices'], *matrix_to_list(ib_matrix)) gltf_skin['joints'].append(len(self._root['nodes']) - 1) self._setup_node(gltf_armature, armature) self._add_child(parent_node, gltf_armature) # no meshes or animation only if (not list(filter(is_object_visible, armature.children)) or self._export_type == 'animation'): gltf_child_node = { 'name': '{}_EMPTY'.format(armature.name), 'skin': len(self._root['skins']) - 1, } self._add_child(gltf_armature, gltf_child_node) return gltf_armature
def _setup_node(self, node, obj=None, can_merge=False): if obj is None: return armature = get_armature(obj) obj_matrix = self._transform(get_object_matrix(obj, armature=armature)) # get custom object properties obj_props = get_object_properties(obj) if not can_merge and not armature: if self._geom_scale == 1: node.update({ 'rotation': quat_to_list(obj_matrix.to_quaternion()), 'scale': list(obj_matrix.to_scale()), 'translation': list(obj_matrix.to_translation()), # 'matrix': matrix_to_list(obj_matrix), }) else: x, y, z = list(obj_matrix.to_translation()) x *= self._geom_scale y *= self._geom_scale z *= self._geom_scale node.update({ 'rotation': quat_to_list(obj_matrix.to_quaternion()), 'scale': list(obj_matrix.to_scale()), 'translation': [x, y, z], }) # setup collisions if not can_merge and is_collision( obj) and obj_props.get('type') != 'Portal': collision = {} node['extensions'] = { 'BLENDER_physics': collision, } # collision shape shape = { 'shapeType': obj.rigid_body.collision_shape, 'boundingBox': [ obj.dimensions[i] / obj_matrix.to_scale()[i] * self._geom_scale for i in range(3) ], } if obj.rigid_body.collision_shape == 'MESH': shape['mesh'] = node.pop('mesh') collision['collisionShapes'] = [shape] collision['static'] = obj.rigid_body.type == 'PASSIVE' # don't actually collide (ghost) if (not obj.collision or not obj.collision.use): collision['intangible'] = True if self._set_origin: obj.select_set(state=True) set_active_object(obj) x1, y1, z1 = obj.location # set origin to the center of bounds bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='BOUNDS') x2, y2, z2 = obj.location if 'extras' not in node: node['extras'] = {} node['extras']['origin'] = [x2 - x1, y2 - y1, z2 - z1] # setup custom properties with tags if (obj_props or can_merge) and 'extras' not in node: node['extras'] = {} for k, v in obj_props.items(): if node['extras'].get(k): # tag exists tag = node['extras'].get(k) if type(v) in (tuple, list, dict): tag = json.dumps(v) else: tag = '{}'.format(v) node['extras'][k] = tag # if can_merge and 'type' not in obj_props: if can_merge: node['extras']['type'] = 'Merged'
def _setup_node(self, node, obj=None, can_merge=False): if obj is None: return armature = get_armature(obj) obj_matrix = get_object_matrix(obj, armature=armature) # get custom object properties obj_props = get_object_properties(obj) if not can_merge and not armature: if self._geom_scale == 1: node.update({ 'rotation': quat_to_list(obj_matrix.to_quaternion()), 'scale': list(obj_matrix.to_scale()), 'translation': list(obj_matrix.to_translation()), }) else: x, y, z = list(obj_matrix.to_translation()) x *= self._geom_scale y *= self._geom_scale z *= self._geom_scale node.update({ 'rotation': quat_to_list(obj_matrix.to_quaternion()), 'scale': list(obj_matrix.to_scale()), 'translation': [x, y, z], }) # setup collisions if not can_merge and is_collision(obj) and obj_props.get('type') != 'Portal': collision = {} node['extensions'] = { 'BLENDER_physics': collision, } # collision shape shape = { 'shapeType': obj.rigid_body.collision_shape, 'boundingBox': [ obj.dimensions[i] / obj_matrix.to_scale()[i] for i in range(3) ], } if obj.rigid_body.collision_shape == 'MESH': shape['mesh'] = node.pop('mesh') collision['collisionShapes'] = [shape] collision['static'] = obj.rigid_body.type == 'PASSIVE' # don't actually collide (ghost) if (not obj.collision or not obj.collision.use): collision['intangible'] = True # setup custom properties with tags if (obj_props or can_merge) and 'extras' not in node: node['extras'] = {} for k, v in obj_props.items(): if node['extras'].get(k): # tag exists tag = node['extras'].get(k) if type(v) in (tuple, list, dict): tag = json.dumps(v) else: tag = '{}'.format(v) node['extras'][k] = tag if can_merge and 'type' not in obj_props: node['extras']['type'] = 'Merged'
def make_action(self, node, armature): gltf_armature = self.make_armature(node, armature) self._setup_node(gltf_armature, armature) self._add_child(self._root, gltf_armature) gltf_skin = self._make_skin(armature, armature) gltf_node = { 'name': 'ARMATURE', 'children': [], 'skin': len(self._root['skins']) - 1, } # self._setup_node(gltf_node, armature) self._add_child(gltf_armature, gltf_node) # <-- animation gltf_animation = { 'name': self._action or 'GLTF_ANIMATION', 'channels': [], 'samplers': [], } # time or animation frame channel = self._buffer.add_channel({ 'componentType': spec.TYPE_FLOAT, 'type': 'SCALAR', 'extra': { 'reference': 'input', }, }) input_id = channel['bufferView'] # setup bones gltf_channels = {} gltf_samplers = [] for bone_name, bone in armature.data.bones.items(): gltf_joint = None for i, child in enumerate(self._root['nodes']): if child['name'] == bone_name: gltf_joint = i break gltf_target = {} if gltf_joint is not None: gltf_target['node'] = gltf_joint for path in ('rotation', 'scale', 'translation'): gltf_samplers.append(self._make_sampler(path, input_id)) gltf_channel = { 'sampler': len(gltf_samplers) - 1, 'target': copy.copy(gltf_target), } gltf_channel['target']['path'] = path gltf_channels['{}/{}'.format(bone_name, path)] = gltf_channel gltf_animation['channels'] = list(gltf_channels.values()) gltf_animation['samplers'] = gltf_samplers # set animation data frame_start = bpy.context.scene.frame_start frame_end = bpy.context.scene.frame_end if self._action: if self._action in bpy.data.actions: action = bpy.data.actions[self._action] frame_start, frame_end = action.frame_range frame = float(frame_start) frame_int = None input_ = 0 while frame <= frame_end: # switch frame if frame_int != math.floor(frame): frame_int = math.floor(frame) bpy.context.scene.frame_current = frame_int bpy.context.scene.frame_set(frame_int) if isinstance(self._speed_scale, collections.Callable): speed_scale = self._speed_scale(frame_int) else: speed_scale = self._speed_scale # switch subframe if speed_scale != 1: bpy.context.scene.frame_subframe = frame - frame_int # write bone matrices for bone_name, bone in armature.pose.bones.items(): bone_matrix = get_bone_matrix(bone, armature) rotation = quat_to_list(bone_matrix.to_quaternion()) scale = list(bone_matrix.to_scale()) translation = list(bone_matrix.to_translation()) gltf_channel = gltf_channels['{}/{}'.format( bone_name, 'rotation')] gltf_sampler = gltf_samplers[gltf_channel['sampler']] self._buffer.write(gltf_sampler['output'], *rotation) gltf_channel = gltf_channels['{}/{}'.format( bone_name, 'scale')] gltf_sampler = gltf_samplers[gltf_channel['sampler']] self._buffer.write(gltf_sampler['output'], *scale) gltf_channel = gltf_channels['{}/{}'.format( bone_name, 'translation')] gltf_sampler = gltf_samplers[gltf_channel['sampler']] self._buffer.write(gltf_sampler['output'], *translation) self._buffer.write(gltf_sampler['input'], input_) # advance to the next frame frame += speed_scale input_ += 1 # animation --> self._root['animations'].append(gltf_animation)