Blender 5 patch

I was having trouble, error when “stop recording”. Also the model was getting flipped in recording (not in streaming). I fixed it with the patch below but much of it was “AI” and so can’t trust it fully. But it works for me. Hope this is helpful to you.

--- rebocap_blender_plugin/ops/rebocap_connection.py	2025-11-05 15:25:10.000000000 -0800
+++ /rebocap_blender_plugin/ops/rebocap_connection.py	2026-01-10 14:35:16.139730400 -0800
@@ -10,6 +10,44 @@
 from .utils import show_message_box
 from ..rebocap_api.rebocap_ws_sdk import REBOCAP_JOINT_NAMES
 
+def ensure_fcurve(action, datablock, data_path: str, index: int):
+    """
+    Blender 5.0+: Action.fcurves was removed; use fcurve_ensure_for_datablock().
+    Older Blender: fall back to action.fcurves.new().
+    """
+    if hasattr(action, "fcurve_ensure_for_datablock"):
+        return action.fcurve_ensure_for_datablock(
+            datablock=datablock,
+            data_path=data_path,
+            index=index,
+        )
+    # Blender <= 4.x fallback
+    return action.fcurves.new(data_path=data_path, index=index)
+
+def quat_make_continuous(prev_q: Optional[Quaternion], q: Quaternion) -> Quaternion:
+    """
+    Ensure quaternion continuity across frames (avoid q / -q sign flips which
+    cause long-arc interpolation and apparent skeleton flips when baking).
+    """
+    q = q.copy()
+    q.normalize()
+    if prev_q is not None and prev_q.dot(q) < 0.0:
+        q = -q
+    return q
+
+def set_kp_linear_vector(kp):
+    # Linear interpolation prevents Bezier overshoot between quaternion keys
+    kp.interpolation = 'LINEAR'
+    # VECTOR handles (optional) make it strictly piecewise-linear and avoid auto handle weirdness
+    kp.handle_left_type = 'VECTOR'
+    kp.handle_right_type = 'VECTOR'
+
+def set_curve_quat_safe(curve):
+    # Depending on Blender version, these exist; safe to guard
+    if hasattr(curve, "auto_smoothing"):
+        curve.auto_smoothing = 'NONE'
+    if hasattr(curve, "extrapolation"):
+        curve.extrapolation = 'CONSTANT'
 
 class RebocapWsSdkV1(RebocapWsSdk):
 
@@ -410,7 +448,6 @@
             if right_shoulder_bone is None and right_arm_bone is not None:
                 all_pose[17] = all_pose[14] @ all_pose[17]
 
-            # print(all_pose[1])
             for idx, bone in enumerate(map_bones):
                 if bone is None or idx == -1 or idx in (10, 11, 22, 23):
                     if idx == 0:  # hip must bind!!!
@@ -479,6 +516,11 @@
 
     if source_obj is None:
         return
+
+    # ensure all driven bones are in quaternion mode before baking
+    for pb in source_obj.pose.bones:
+        pb.rotation_mode = 'QUATERNION'
+
     all_pose_list = []
     for record in record_list:
         all_pose_list.append([Quaternion([pose[3], pose[0], pose[1], pose[2]]) for pose in record[1]])
@@ -522,31 +564,44 @@
             if idx == 0:  # hip must bind!!!
                 break
             continue
+
         bone_name = bone.name
         bone_info = obj_info.get_or_insert_bone(bone_name)
         rest_matrix_to_world, rest_matrix_from_world = bone_info.get_rest_init()
+
         rotation_quaternion = []
         t = []
+
+        prev_q = None
+        begin_t = record_list[0][3]
         for fn, frame in enumerate(record_list):
             t.append((frame[3] - begin_t) / dt + 1)
+
             pose = all_pose_list[fn][idx]
             new_pose = rest_matrix_from_world @ pose @ rest_matrix_to_world
+            new_pose = quat_make_continuous(prev_q, new_pose)
             rotation_quaternion.append(new_pose)
+            prev_q = new_pose
+
         data_path = 'pose.bones["%s"].rotation_quaternion' % bone.name
         for axis_i in range(4):
-            curve = action.fcurves.new(data_path=data_path, index=axis_i)
+            curve = ensure_fcurve(action, obj, data_path, axis_i)
+            set_curve_quat_safe(curve)
             keyframe_points = curve.keyframe_points
             frame_count = len(rotation_quaternion)
             keyframe_points.add(frame_count)
-            for i, rotation_quaternion_item in enumerate(rotation_quaternion):
-                keyframe_points[i].co = (
-                    t[i],
-                    rotation_quaternion_item[axis_i]
-                )
+            for i, q in enumerate(rotation_quaternion):
+                kp = keyframe_points[i]
+                kp.co = (t[i], q[axis_i])
+                kp.interpolation = 'LINEAR'
+                kp.handle_left_type = 'VECTOR'
+                kp.handle_right_type = 'VECTOR'
+            curve.update()
 
+        # KEY ROOT LOCATION ON THE ACTUAL ROOT BONE (matches streaming behavior)
         if idx == 0 and obj_info.root_bone is not None:
-            bone = obj_info.root_bone
-            data_path = 'pose.bones["%s"].location' % bone.name
+            root_pb = obj_info.root_bone
+            data_path = f'pose.bones["{root_pb.name}"].location'
 
             all_trans_new = []
             for i, frame in enumerate(record_list):
@@ -559,16 +614,18 @@
                 all_trans_new.append(new_local_matrix.translation)
 
             for axis_i in range(3):
-                curve = action.fcurves.new(data_path=data_path, index=axis_i)
+                curve = ensure_fcurve(action, obj, data_path, axis_i)
+                set_curve_quat_safe(curve)
                 keyframe_points = curve.keyframe_points
                 frame_count = len(record_list)
                 keyframe_points.add(frame_count)
 
-                for i, frame in enumerate(record_list):
-                    keyframe_points[i].co = (
-                        t[i],
-                        all_trans_new[i][axis_i]
-                    )
+                for i in range(frame_count):
+                    kp = keyframe_points[i]
+                    kp.co = (t[i], all_trans_new[i][axis_i])
+                    set_kp_linear_vector(kp)
+
+                curve.update()
 
 
 def stop_record(ctx):
@@ -588,17 +645,21 @@
         if source_obj is None or source_obj != obj:
             continue
 
+        # ensure quaternion mode before baking
+        for pb in obj.pose.bones:
+            pb.rotation_mode = 'QUATERNION'
+
         is_direct = obj.rebocap_drive_type == 'DIRECT'
         is_valid = False
         if is_direct:
             for pose_bone in obj.pose.bones:
-                if is_direct:
-                    idx = get_pose_idx(pose_bone)
-                    if idx != -1:
-                        is_valid = True
-                        break
+                idx = get_pose_idx(pose_bone)
+                if idx != -1:
+                    is_valid = True
+                    break
             if not is_valid:
                 continue
+
         obj.animation_data_create()
         obj.animation_data.action = action
         dt = 1 / ctx.scene.render.fps
@@ -608,7 +669,8 @@
 
         global obj_info_helper
         obj_info = obj_info_helper.get_or_insert_obj(obj.name)
-        # below is direct
+
+        # below is direct bake
         for pose_bone in obj.pose.bones:
             bone = obj.data.bones.get(pose_bone.name)
             idx = get_pose_idx(pose_bone)
@@ -617,26 +679,38 @@
 
             bone_info = obj_info.get_or_insert_bone(bone.name)
             rest_matrix_to_world, rest_matrix_from_world = bone_info.get_rest_init()
+
             rotation_quaternion = []
             t = []
-            for frame in record_list:
+
+            prev_q = None
+            for i, frame in enumerate(record_list):
                 t.append((frame[3] - begin_t) / dt + 1)
                 pose = frame[1][idx]
                 new_pose = rest_matrix_from_world @ Quaternion([pose[3], pose[0], pose[1], pose[2]]) @ rest_matrix_to_world
+                new_pose = quat_make_continuous(prev_q, new_pose)
                 rotation_quaternion.append(new_pose)
+                prev_q = new_pose
+
             data_path = 'pose.bones["%s"].rotation_quaternion' % pose_bone.name
             for axis_i in range(4):
-                curve = action.fcurves.new(data_path=data_path, index=axis_i)
+                curve = ensure_fcurve(action, obj, data_path, axis_i)
                 keyframe_points = curve.keyframe_points
                 frame_count = len(rotation_quaternion)
                 keyframe_points.add(frame_count)
-                for i, rotation_quaternion_item in enumerate(rotation_quaternion):
-                    keyframe_points[i].co = (
-                        t[i],
-                        rotation_quaternion_item[axis_i]
-                    )
+                for i, q in enumerate(rotation_quaternion):
+                    kp = keyframe_points[i]
+                    kp.co = (t[i], q[axis_i])
+                    kp.interpolation = 'LINEAR'
+                    kp.handle_left_type = 'VECTOR'
+                    kp.handle_right_type = 'VECTOR'
+                curve.update()
+
+            # KEY ROOT LOCATION ON THE ACTUAL ROOT BONE (matches streaming behavior)
             if idx == 0 and obj_info.root_bone is not None:
-                data_path = 'pose.bones["%s"].location' % pose_bone.name
+                root_pb = obj_info.root_bone
+                data_path = f'pose.bones["{root_pb.name}"].location'
+
                 all_trans_new = []
                 for i, frame in enumerate(record_list):
                     trans = frame[0]
@@ -648,53 +722,68 @@
                     all_trans_new.append(new_local_matrix.translation)
 
                 for axis_i in range(3):
-                    curve = action.fcurves.new(data_path=data_path, index=axis_i)
+                    curve = ensure_fcurve(action, obj, data_path, axis_i)
                     keyframe_points = curve.keyframe_points
                     frame_count = len(record_list)
                     keyframe_points.add(frame_count)
 
                     for i, frame in enumerate(record_list):
-                        keyframe_points[i].co = (
-                            t[i],
-                            all_trans_new[i][axis_i]
-                        )
-    for cu in action.fcurves:
-        for bez in cu.keyframe_points:
-            bez.interpolation = 'LINEAR'
+                        kp = keyframe_points[i]
+                        kp.co = (t[i], all_trans_new[i][axis_i])
+                        kp.interpolation = 'LINEAR'
+                        kp.handle_left_type = 'VECTOR'
+                        kp.handle_right_type = 'VECTOR'