Skip to content

Commit

Permalink
joint angles almost working
Browse files Browse the repository at this point in the history
  • Loading branch information
jonmatthis committed Nov 4, 2023
1 parent 6b1c36e commit feeb3f7
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 11 deletions.
30 changes: 25 additions & 5 deletions ajc27_freemocap_blender_addon/core_functions/main_controller.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from pathlib import Path
import traceback
from pathlib import Path

from .mesh.skelly_mesh.attach_skelly_mesh import attach_skelly_mesh_to_rig
from .rig.save_bone_and_joint_angles_from_rig import save_bone_and_joint_angles_from_rig

from ..core_functions.bones.enforce_rigid_bones import enforce_rigid_bones
from ..core_functions.empties.creation.create_freemocap_empties import (
create_freemocap_empties,
Expand Down Expand Up @@ -39,7 +41,7 @@ def __init__(self, recording_path: str, save_path: str, config: Config):
self.save_path = save_path
self.recording_name = Path(self.recording_path).stem
self.origin_name = f"{self.recording_name}_origin"
self.rig_name = "rig"
self.rig_name = f"{self.recording_name}_rig"
self._create_parent_empties()
self.freemocap_data_handler = get_or_create_freemocap_data_handler(
recording_path=self.recording_path
Expand All @@ -49,10 +51,12 @@ def __init__(self, recording_path: str, save_path: str, config: Config):
def _create_parent_empties(self):
self.data_parent_object = create_freemocap_parent_empty(name=self.origin_name)
self._empty_parent_object = create_freemocap_parent_empty(
name=f"empties", parent_object=self.data_parent_object
name=f"empties_parent",
parent_object=self.data_parent_object
)
self._video_parent_object = create_freemocap_parent_empty(
name=f"videos", parent_object=self.data_parent_object
name=f"videos_parent",
parent_object=self.data_parent_object
)

def load_freemocap_data(self):
Expand Down Expand Up @@ -155,6 +159,21 @@ def add_rig(self):
print(e)
raise e

def save_bone_and_joint_data_from_rig(self):
try:
print("Saving joint angles...")
csv_file_path = str(Path(self.save_path).parent / "saved_data" / f"{self.recording_name}_bone_and_joint_data.csv")
save_bone_and_joint_angles_from_rig(
rig_name=self.rig_name,
csv_save_path=csv_file_path,
start_frame=0,
end_frame=self.freemocap_data_handler.number_of_frames,
)
except Exception as e:
print(f"Failed to save joint angles: {e}")
print(e)
raise e

def attach_rigid_body_mesh_to_rig(self):
try:
print("Adding rigid_body_bone_meshes...")
Expand Down Expand Up @@ -204,6 +223,7 @@ def run_all(self):
self.save_data_to_disk()
self.create_empties()
self.add_rig()
self.save_bone_and_joint_data_from_rig()
self.attach_rigid_body_mesh_to_rig()
self.attach_skelly_mesh_to_rig()
self.add_videos()
Expand All @@ -218,7 +238,7 @@ def setup_scene(self):
for area in window.screen.areas: # iterate through areas in current screen
if area.type == "VIEW_3D":
for (
space
space
) in area.spaces: # iterate through spaces in current VIEW_3D area
if space.type == "VIEW_3D": # check if space is a 3D view
space.shading.type = "MATERIAL"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ def make_cone_mesh(name: str = "cone_mesh",
emission_strength: float = 1.0,
transmittance: float = 0.0,
vertices: int = 8,
radius1: float = 0.05,
radius2: float = 0.05,
radius1: float = 0.035,
radius2: float = 0.035,
depth: float = 1,
end_fill_type: str = 'TRIFAN',
align: str = 'WORLD',
Expand Down Expand Up @@ -74,7 +74,7 @@ def make_cone_mesh(name: str = "cone_mesh",

def make_joint_sphere_mesh(name: str = "joint_sphere_mesh",
subdivisions: int = 2,
radius: float = 0.1,
radius: float = 0.075,
align: str = 'WORLD',
location: tuple = (0, 0, 0),
scale: tuple = (1, 1, 1),
Expand All @@ -101,7 +101,7 @@ def make_bone_mesh(name: str = "bone_mesh",
joint_color: Union[str, Tuple, List, np.ndarray] = "#aa0055",
cone_color: Union[str, Tuple, List, np.ndarray] = "#00FFFF",
axis_visible: bool = True,
squish_scale: tuple = (.8, 1, 1),
squish_scale: tuple = (.6, 1, 1),
length: float = 1,
) -> bpy.types.Object:
cone = make_cone_mesh(name=f"{name}_cone_mesh",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import csv
from pathlib import Path
from typing import Dict
from ajc27_freemocap_blender_addon.data_models.bones.bone_constraints import BONES_CONSTRAINTS

import bpy


def save_bone_and_joint_angles_from_rig(rig_name: str,
csv_save_path: str,
start_frame: int,
end_frame: int):
Path(csv_save_path).parent.mkdir(parents=True, exist_ok=True)
documenation_save_path = Path(csv_save_path).parent / "_BONE_AND_JOINT_DATA_README.md"

rig = bpy.data.objects[rig_name]

if rig.type != 'ARMATURE':
raise TypeError(f"Object {rig_name} is not an armature!")
all_bone_data = {}
for frame_number in range(start_frame, end_frame + 1):
bpy.context.scene.frame_set(frame_number)
frame_data = {}
all_bone_data[frame_number] = frame_data
for bone in rig.pose.bones:
if bone.name not in BONES_CONSTRAINTS.keys():
continue
frame_data[bone.name] = get_bone_data(bone)

# Save as csv
column_names = []
for bone_key in all_bone_data[0].keys():
for data_name in list(next(iter(frame_data.values())).keys()):
column_names.append(f"{bone_key}_{data_name}")

with open(csv_save_path, 'w', newline='') as file:
writer = csv.DictWriter(file, fieldnames=column_names)
writer.writeheader()
for frame_data in all_bone_data.values():
row_data = []
for bone_data in frame_data.values():
row_data.extend(bone_data.values())
column_data_mappping = dict(zip(column_names, row_data))
writer.writerow(column_data_mappping)

# Save documentation
with open(documenation_save_path, 'w') as file:
file.write(DOCUMENTATION_STRING)


def get_bone_data(bone: bpy.types.PoseBone) -> Dict[str, float]:
return {
"head_center_world_x": bone.head.x,
"head_center_world_y": bone.head.y,
"head_center_world_z": bone.head.z,
"tail_center_world_x": bone.tail.x,
"tail_center_world_y": bone.tail.y,
"tail_center_world_z": bone.tail.z,
"rotation_quaternion_x":bone.matrix.to_quaternion().x,
"rotation_quaternion_y":bone.matrix.to_quaternion().y,
"rotation_quaternion_z":bone.matrix.to_quaternion().z,
"rotation_quaternion_w":bone.matrix.to_quaternion().w,
"rotation_euler_x": bone.matrix.to_euler().x,
"rotation_euler_y": bone.matrix.to_euler().y,
"rotation_euler_z": bone.matrix.to_euler().z,
"rotation_euler_order": bone.matrix.to_euler().order,
"matrix_0_0": bone.matrix[0][0],
"matrix_0_1": bone.matrix[0][1],
"matrix_0_2": bone.matrix[0][2],
"matrix_0_3": bone.matrix[0][3],
"matrix_1_0": bone.matrix[1][0],
"matrix_1_1": bone.matrix[1][1],
"matrix_1_2": bone.matrix[1][2],
"matrix_1_3": bone.matrix[1][3],
"matrix_2_0": bone.matrix[2][0],
"matrix_2_1": bone.matrix[2][1],
"matrix_2_2": bone.matrix[2][2],
"matrix_2_3": bone.matrix[2][3],
"matrix_3_0": bone.matrix[3][0],
"matrix_3_1": bone.matrix[3][1],
"matrix_3_2": bone.matrix[3][2],
"matrix_3_3": bone.matrix[3][3],
}


DOCUMENTATION_STRING = """
# Bone data
This document describes the data that is saved for each bone in the rig.
All of this data is derived from a Blender Armature object based on a slightly tweaked version of the Rigify rig.
For multi-part data (e.g. XYZ location), each component of the data is saved in its own column: e.g. {bone_name}_x, {bone_name}_y, {bone_name}_z
To access the bone data in Blender, use the following command (e.g. to get the Thigh.L bone) in the Blender python console (e.g. in the Scripting Tab): `bone = bpy.context.object["righ"].pose.bone["thigh.L"]`
Theses are the properties of the bone object that are saved:
# Bone Properties
## `bone.head` : location data - X, Y, Z (tuple of floats)
The location of the head of the bone in world space (e.g. (0.0, 0.0, 0.0))
https://docs.blender.org/api/current/bpy.types.PoseBone.html#bpy.types.PoseBone.head
## `bone.tail` : location data - X, Y, Z (tuple of floats)
The location of the tail of the bone in world space (e.g. (0.0, 0.0, 0.0))
https://docs.blender.org/api/current/bpy.types.PoseBone.html#bpy.types.PoseBone.tail
## `bone.rotation_quaternion['_x', '_y', '_z', '_w']` : quaternion - X, Y, Z, W (tuple of floats)
The rotation of the bone in quaternion space (e.g. (0.0, 0.0, 0.0, 1.0))
https://docs.blender.org/api/current/bpy.types.PoseBone.html#bpy.types.PoseBone.rotation_quaternion
https://en.wikipedia.org/wiki/Quaternion
## `bone.rotation_euler['_x', '_y', '_z']` : euler rotation - X, Y, Z (tuple of floats)
The rotation of the bone in euler space (e.g. (0.0, 0.0, 0.0))
https://docs.blender.org/api/current/bpy.types.PoseBone.html#bpy.types.PoseBone.rotation_euler
https://en.wikipedia.org/wiki/Euler_angles
## `bone.rotation_euler.order` : str
The order of the euler rotation (e.g. 'XYZ')
https://docs.blender.org/api/current/bpy.types.PoseBone.html#bpy.types.PoseBone.rotation_euler
https://en.wikipedia.org/wiki/Euler_angles
## `bone.matrix` : 4x4 matrix (tuple of tuples of floats)
The transformation matrix of the bone in world space after constraints and drivers are applied, in the armature object space
https://docs.blender.org/api/current/bpy.types.PoseBone.html#bpy.types.PoseBone.matrix
https://en.wikipedia.org/wiki/Transformation_matrix
"""
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ def create_freemocap_parent_empty(name: str = "freemocap_data_parent_empty",
parent_object: bpy.types.Object = None):
print("Creating freemocap parent empty...")
bpy.ops.object.empty_add(type="ARROWS")
parent_empty = bpy.context.active_object
parent_empty = bpy.context.active_object
parent_empty.name = name

if parent_object is not None:
print(f"Setting parent of {parent_empty.name} to {parent_object.name}")
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "ajc27_freemocap_blender_addon"
version = "0.2.6"
version = "0.2.7"
description = "A Blender Add-on for working with Freemocap Data (based on @ajc27's addon)"
authors = ["Skelly FreeMoCap <[email protected]>"]
license = "AGPLv3"
Expand Down

0 comments on commit feeb3f7

Please sign in to comment.