跳转至

Smart Object Primitive TODO

本文档单独定义 object_sockets -> keyframe_targets -> Kimodo constraints 这条规范和第一版 procedural 生成方式。目标是把 MotionBricks 的 Smart Object 思路迁移到当前 SAGE + Kimodo + Isaac/ProtoMotions pipeline,但第一版不训练模型、不生成 full-body pose,只生成 sparse targets 和 constraints。

1. 核心概念

1.1 object_sockets

object_sockets 是物体侧的 affordance anchors,绑定在 object local frame 上。

它回答:

这个 object 哪里可以交互?
这个 object 的 front/right/up 是什么?
哪里是 seat surface?
哪里是 front floor region?

它应该跟着物体移动和旋转:

T_world_socket = T_world_object @ T_object_socket

示例:

{
  "object_id": "pouf:e55447",
  "object_sockets": {
    "seat_center": {"xyz": [0.0, 0.0, 0.42], "frame": "object"},
    "seat_front_axis": {"xy": [0.0, -1.0], "frame": "world_or_object"},
    "seat_right_axis": {"xy": [1.0, 0.0], "frame": "world_or_object"},
    "seat_surface": {"height": 0.42, "frame": "world"},
    "front_floor_region": {"center_xy": [0.0, -0.55], "radius": 0.30, "frame": "object"}
  }
}

第一版来源:

scene_context.json:
  explicit_interaction_targets_by_mode["sit"]
  seat_front_direction_xy
  seat_front_standing_point_hint_xy
  object bbox / dimensions / rotation

task_validation.json:
  standing_xy
  target_xyz
  path_points

1.2 keyframe_targets

keyframe_targets 是 humanoid 侧的 sparse targets,由 object_sockets 通过 procedural binding rules 生成。

它回答:

在这个 interaction 中,humanoid root/pelvis/feet/hands 应该去哪里?
heading 应该朝哪里?
哪些目标导出成 root / end-effector / fullbody constraint?

它不是 full-body pose,不包含完整 SMPL joint angles。

示例:

{
  "primitive_id": "sit:pouf:e55447:slot0",
  "keyframe_targets": {
    "pre_sit_root": {
      "xyz": [2.8, 4.6, 0.95],
      "heading_xy": [0.0, -1.0],
      "kimodo_type": "root2d"
    },
    "final_sit_pose": {
      "root_xyz": [2.8, 4.2, 0.50],
      "heading_xy": [0.0, -1.0],
      "kimodo_type": "end-effector",
      "joint_targets": [
        {"joint_name": "Hips", "xyz": [2.8, 4.2, 0.48]},
        {"joint_name": "LeftFoot", "xyz": [2.67, 4.75, 0.0]},
        {"joint_name": "RightFoot", "xyz": [2.93, 4.75, 0.0]}
      ]
    }
  }
}

注意:kimodo_type 在这里表示最终应导出的 Kimodo native constraint type,但这段 JSON 仍是我们的中间层目标描述,不是 Kimodo 原生 constraints.json。Kimodo 当前不接受 per-target hardness = soft/hard

1.3 Kimodo constraints

Kimodo constraints 是 motion/stage 侧的实际输入。它把 keyframe_targets 绑定到具体 frame。

它回答:

在第几帧,Kimodo 需要满足哪个 sparse target?

推荐第一版尽量只用 Kimodo native constraint type:

root2d:
  root path / start-end root xy / heading

end-effector:
  final sit Hips / LeftFoot / RightFoot / LeftHand / RightHand targets
  通过 canonical/retrieved pose frame + FK/IK 转成 Kimodo 原生 JSON

fullbody:
  少量关键姿态的强约束;只在确实需要时使用,避免过约束

Kimodo 原生支持的 constraint type 是 root2dfullbodyleft-footright-footleft-handright-handend-effector。当前 repo 里的 root3d 是 wrapper-side monkey patch,不是 Kimodo native type。长期设计应该把 root3d 降级为 legacy/quick baseline,而不是作为主接口。

当前 plan_to_constraints.py 还没有把 procedural keypoints 导出成 native end-effector / left-foot / right-foot constraints,但这是需要实现的核心能力,不是只能做 validation。

当前 Kimodo constraint schema 不支持 hardness 这种 per-target 软硬约束字段。当前可用的控制方式是:

1. 选择是否导出某个 target 到 Kimodo。
2. 选择导出成哪种 native constraint type:root2d / fullbody / left-foot / right-foot / left-hand / right-hand / end-effector。
3. 选择约束在哪些 frame 生效。
4. 调整 generation 时的 cfg constraint weight,例如 meta/cfg 里的 constraint_weight。

因此本文档里的 kimodo_type 只表示我们最终要走哪种 Kimodo native constraint:

root2d:
  导出到 Kimodo root2d。

left-foot / right-foot:
  通过 Kimodo 原生 left-foot / right-foot constraint 导出。

left-hand / right-hand:
  通过 Kimodo 原生 left-hand / right-hand constraint 导出。

end-effector:
  通过 Kimodo 原生 end-effector constraint 导出,joint_names 可以包含 Hips/LeftFoot/RightFoot/LeftHand/RightHand。

fullbody:
  通过 Kimodo 原生 fullbody constraint 导出;只用于少数 high-confidence keyframe。

关键实现点:Kimodo native end-effector / left-foot / right-foot JSON 不是直接传一个 xyz 点,而是通过 local_joints_rotroot_positionssmooth_root_2d 还原出一帧 skeleton,再从这帧 skeleton 里取对应 joint position。Kimodo 的 UI 可以让用户点选/移动 keypoint,是因为它背后仍然有一帧 motion pose 作为载体。我们的 exporter 要做同样的事情:

keyframe_targets xyz
  -> 选择 canonical/retrieved pose frame
  -> root/heading align 到 object socket
  -> optional lightweight IK 让 FK 后的 feet/hands 更接近 target xyz
  -> 得到 local_joints_rot + root_positions + smooth_root_2d
  -> 导出 Kimodo native end-effector / left-foot / right-foot / left-hand / right-hand constraint

所以这里不是“Kimodo 不支持这些点”,而是“Kimodo 支持这些 constraint set,但我们要实现 procedural keypoint target 到 Kimodo constraint JSON 的适配层”。

root3d wrapper 的作用只是直接写 smooth_root_2d + root_y_pos + global_root_heading,不锁 full-body joint。这个接口直观,但不是 native。native-only 路线下,final sit 的 3D root/pelvis 目标应优先表达为:

root2d:
  final root xy + heading

end-effector:
  joint_names = ["Hips", "LeftFoot", "RightFoot"]
  由 aligned seated pose frame 导出 local_joints_rot / root_positions / smooth_root_2d

风险:Kimodo native end-effector 会同时约束 selected joints 的 position/rotation、root_y_pos、global heading 和 smooth_root_2d;它不是纯 position-only constraint。因此需要和旧 root3d wrapper 做 A/B test,确认是否会因为过约束导致 sit motion 质量下降。

2. 数据流

完整流程:

SAGE scene object
  -> object_sockets
  -> procedural binding rules
  -> keyframe_targets
  -> Kimodo constraints.json
  -> Kimodo multi-stage generation
  -> kinematic validation
  -> ProtoMotions tracker / physics validation

关键区分:

object_sockets:
  object-side anchors
  long-lived
  object-local

keyframe_targets:
  humanoid-side sparse targets
  interaction-specific
  usually world-frame after binding

Kimodo constraints:
  frame-specific model inputs
  generated from keyframe_targets + stage timeline

3. SitSmartObject procedural 生成

3.1 输入字段

scene_context.json 读取:

seat_center_world = explicit_interaction_targets_by_mode["sit"]
seat_front_world_xy = seat_front_direction_xy
seat_stand_hint_xy = seat_front_standing_point_hint_xy
object bbox / dimensions / rotation

task_validation.json 读取:

standing_xy
target_xyz
path_points

3.2 建立 seat frame

定义:

C = seat_center_world
f = normalize(seat_front_world_xy)
r = [-f_y, f_x]
u = [0, 0, 1]
seat_h = C.z

其中:

f = seat front / humanoid final facing direction
r = seat right direction
u = up axis

需要在坐标系测试中确认 seat_front_direction_xy 的正方向。如果发现 heading 翻转,就统一在 coordinates.py 中修正,而不是在每个 primitive 中临时修。

3.3 生成 object_sockets

seat_center:
  position = C
  orientation = [r, f, u]

seat_surface:
  height = seat_h
  center = C

front_floor_region:
  center_xy = C.xy - f * 0.50
  radius = 0.25 ~ 0.35

approach_region:
  center_xy = seat_stand_hint_xy or C.xy - f * 0.55
  radius = 0.30 ~ 0.50

对 sofa/bench,先沿 r 方向采多个 slot:

usable_width = object_width - 2 * armrest_margin
slot_width = 0.55 ~ 0.65
num_slots = max(1, floor(usable_width / slot_width))
slot_center_i = C + r * offset_i

每个 slot_center_i 都生成一套 seat_center/front_floor_region/approach_region

3.4 生成 keyframe_targets

默认参数:

root_stand_height = 0.90 ~ 1.00
root_sit_offset = 0.05 ~ 0.12
pelvis_offset = 0.03 ~ 0.08
pre_sit_distance = 0.40 ~ 0.60
foot_forward_offset = 0.45 ~ 0.60
stance_width = 0.22 ~ 0.32

公式:

pre_sit_root.xy =
  seat_stand_hint_xy
  or C.xy - f * pre_sit_distance

pre_sit_root.z =
  floor_z + root_stand_height

final_sit_root.xy =
  C.xy

final_sit_root.z =
  seat_h + root_sit_offset

pelvis_target.xyz =
  [C.x, C.y, seat_h + pelvis_offset]

left_foot_target.xy =
  C.xy - f * foot_forward_offset - r * stance_width / 2

right_foot_target.xy =
  C.xy - f * foot_forward_offset + r * stance_width / 2

left_foot_target.z =
  floor_z

right_foot_target.z =
  floor_z

heading_xy =
  f

3.5 生成 Kimodo stage constraints

对 multi-stage Kimodo:

stage 0:
  prompt = "A person walks toward the chair/sofa/pouf."
  constraint = root2d path from task_validation.path_points

stage 1:
  prompt = "A person sits down on the chair/sofa/pouf."
  constraint:
    final fullbody 或 end-effector keyframe = aligned seated pose frame
    global_root_heading = heading_xy, 由 keyframe pose 自身或 sparse root2d 提供

推荐第一版 native-only 直接导出,不把 root3d 作为 sit 主接口:

fullbody:
  type = "fullbody"
  frame_indices = [sit_end_frame]
  local_joints_rot = aligned seated pose local rotations
  root_positions = aligned seated pose root position
  smooth_root_2d = final_sit_root.xy after SAGE->Kimodo axis mapping

end-effector:
  type = "end-effector"
  frame_indices = [sit_end_frame]
  joint_names = ["Hips", "LeftFoot", "RightFoot"]
  local_joints_rot = aligned seated pose local rotations
  root_positions = aligned seated pose root position
  smooth_root_2d = final_sit_root.xy after SAGE->Kimodo axis mapping

root2d 在 sit 阶段只用于进入交互前的 locomotion/align。不要在 lowering/sit transition 中继续塞 dense root2d path;否则它可能和 final seated keyframe 互相打架。推荐:

walk/align stage:
  dense 或 sparse root2d 到 pre_sit_root

sit stage:
  不使用 dense root2d
  final frame 使用 native fullbody 或 end-effector keyframe

暂时不直接喂给 Kimodo,但需要保存:

pelvis_target
left_foot_target
right_foot_target
contact_schedule

用途:

kinematic validation
IK/root warp repair
ProtoMotions reward / success condition

root3d 只保留为 legacy ablation / fallback,不作为目标实现:

root3d wrapper:
  frame_indices = [sit_end_frame]
  smooth_root_2d = final_sit_root.xy
  root_y_pos = final_sit_root.z after SAGE->Kimodo axis mapping
  global_root_heading = heading_xy

3.6 基于本周 sit demo 的最小改动测试

目的不是先重写 SAGE converter,而是用本周已经能跑的 Kimodo sit demo 做 isolated test:

固定不变:
  prompt
  frame timeline
  root2d path/heading
  Kimodo model/config/seed
  output/evaluation pipeline

只替换:
  final seated fullbody keyframe 的生成方式

实验组:

A. original demo
   使用本周 demo 里手调/保存的 final fullbody keyframe。

B. generated keyframe, legacy context
   其它 constraints 完全保留,包括 demo 里已有的 root3d height guides。
   只替换 final fullbody keyframe。
   用来隔离判断 keyframe_factory 生成的 pose 是否合理。

C. generated keyframe, native-only sit
   删除 sit transition 里的 root3d height guides。
   保留 locomotion/align 的 root2d。
   final frame 只用 generated fullbody 或 end-effector keyframe。
   用来回答 sit 是否可以不依赖 root3d。

判断标准:

motion naturalness:
  walk -> align -> sit transition 没有明显瞬移、抖动、脚滑、突然蹲地再升起。

keyframe control:
  final Hips/root 落在 seat support region。
  final root heading 与 seat_front_direction 一致。
  LeftFoot/RightFoot 在 floor/free-space region。

collision:
  final keyframe pose 不能明显穿入 seat/back/armrest。
  feet 不能穿地。
  lower legs 不能和 chair/sofa front 发生大穿透。

如果 B 失败,问题优先归因于 keyframe_factory 的 pose alignment / collision validation。 如果 B 成功但 C 失败,说明当前 Kimodo 仍需要中间 height/pose guidance;下一步不是继续用 root3d,而是加一个 native mid-sit keyframe 或 end-effector(Hips) 中间帧。

4. 其他 primitive 的规则草案

4.1 PickupSmartObject

object_sockets:

grasp_center
grasp_front_axis
top_surface
left_grasp_side
right_grasp_side
approach_region

keyframe_targets:

pre_grasp_root
left_wrist_target
right_wrist_target
object_attach_frame

MVP 规则:

small object:
  one-hand wrist target at object top/front

box/cushion/pillow:
  two-hand wrist targets on left/right bbox sides

floor object:
  lower torso / bend style prompt

tabletop object:
  reach style prompt

4.2 PlaceSmartObject

object_sockets:

support_surface_center
support_surface_height
front_approach_region
release_region

keyframe_targets:

pre_place_root
release_object_pose
wrist_release_target

MVP 规则:

release_object_pose.xyz =
  support_surface_center + [0, 0, object_half_height + clearance]

wrist_release_target =
  release_object_pose.xyz + hand_offset

4.3 CarrySmartObject

object_sockets:

carry_center
left_hand_attach
right_hand_attach
torso_attach

keyframe_targets:

hand_object_relative_pose
object_trajectory

MVP 规则:

carry object kinematically attached to wrist/torso proxy
validate hand-object distance and object-scene collision

5. TODO

5.1 Schema

  • 新增 utils/hsi_primitive_factory/schemas.py
  • 定义 ObjectSocket
  • 定义 KeyframeTarget
  • 定义 SmartObjectBinding
  • 定义 PrimitiveConstraintBundle
  • 明确 frame 字段:sage_worldkimodo_worldobject_local

5.2 Sit MVP

  • scene_context.json 提取 sit object。
  • 读取 explicit_interaction_targets_by_mode["sit"]
  • 读取 seat_front_direction_xy
  • 读取 seat_front_standing_point_hint_xy
  • 生成 object_sockets
  • 生成 keyframe_targets
  • 导出 primitive_constraints.json
  • pre_sit_root / final_sit_root 导出为 Kimodo root2d start/end。
  • heading_xy 导出为 global_root_heading
  • 选一个 canonical/retrieved seated pose frame。
  • 将 seated pose frame 做 root/heading align 到 final_sit_root
  • 基于 aligned pose 导出 Kimodo native fullbody constraint,复现本周 sit demo 的 final keyframe 接口。
  • 基于同一个 aligned pose 导出 Kimodo native end-effector constraint,joint_names = ["Hips", "LeftFoot", "RightFoot"],作为 ablation。
  • 如 foot target 误差过大,再加 lightweight IK 或 CEM search 微调 seated pose。
  • 做本周 demo isolated test:只替换 final seated keyframe,其余 prompt/timeline/root2d/seed 不变。
  • 做 native-only sit test:删除 root3d height guides,只保留 final fullbodyend-effector keyframe。
  • 如果 native-only 失败,改用 native mid-sit fullbody/end-effector(Hips) keyframe,而不是优先恢复 root3d

5.3 Validation

  • 检查 foot targets 是否在 free floor。
  • 检查 final_sit_root 是否靠近 seat target。
  • 检查 heading 是否和 seat front 一致。
  • 检查 pelvis target 是否在 seat support region。
  • 检查 aligned seated keyframe pose 与 chair/sofa proxy 的碰撞。
  • 检查 feet-floor penetration、thigh-seat penetration、shin-front collision、torso-back/armrest penetration。
  • 对 collision 失败的 keyframe 尝试 root offset / yaw / foot placement 小范围 search,仍失败则 reject。
  • 记录每个 rejected candidate 的 reason。

5.4 Debug visualization

  • 在 SAGE topdown overlay 画 object_sockets。
  • 在 SAGE topdown overlay 画 keyframe_targets。
  • 在 Kimodo viewer overlay 画 native end-effector target markers。
  • 在 ProtoMotions playback 中画 target object 和 humanoid target markers。

5.5 后续扩展

  • 支持 sofa/bench multi-slot。
  • 支持 pickup object grasp sockets。
  • 支持 support surface place sockets。
  • 支持 object trajectory for carry/place。
  • 实现通用 keyframe_targets 到 Kimodo left-foot / right-foot / left-hand / right-hand / end-effector constraints 的 exporter。
  • 为 end-effector exporter 实现 canonical pose construction、retrieval pose alignment 或 lightweight IK,把 sparse xyz targets 转成 Kimodo 需要的 local_joints_rot + root_positions + smooth_root_2d
  • 评估 pelvis/Hips target 是否通过 native end-effector(["Hips"])、native fullbody、还是 legacy root3d fallback 处理。
  • 如果 procedural success rate 不够,再加入 retrieval 或 CEM search。