-
Notifications
You must be signed in to change notification settings - Fork 12
Hands On Custom Transition
konodoki edited this page Jul 29, 2026
·
13 revisions
Transition 负责在状态切换期间临时生成电机帧。需要它是因为源状态和目标状态的 qpos/kp/kd 不一定连续,直接切换可能造成突跳;内置过渡可以保持、渐增增益或混合两个运行状态。
当这些内置策略无法表达业务需要的切换过程时,才需要自定义 Transition。本课为 com.example.sin_wave 增加 pose_gain_blend,平滑混合过渡开始帧和目标进入帧。
mods/com.example.sin_wave/pose_gain_blend.py
from collections.abc import Mapping
import numpy as np
from bxi_example_py_elf3.framework.mod_api.transition import (
ConfigReader,
MotorFrame,
SingleClassTransition,
require_entry_frame_provider,
)
class PoseGainBlendTransition(SingleClassTransition):
type_name = "com.example.sin_wave.pose_gain_blend"
def __init__(self, name, duration, curve):
super().__init__(name, duration)
self._curve = curve
self._start = None
self._target = None
self._output = None
@classmethod
def from_config(cls, name: str, raw: Mapping[str, object]):
reader = ConfigReader(raw, name)
duration = reader.float("duration", minimum=0.0)
curve = reader.literal(
"curve", ("linear", "smoothstep"), default="smoothstep"
)
reader.finish()
return cls(name, duration, curve)
def validate_states(self, from_state, to_state):
require_entry_frame_provider(to_state)
def on_start(self, ctx, from_state, to_state):
self._start = MotorFrame.create(
ctx.robot_layout,
ctx.last_motor_frame.qpos,
ctx.last_motor_frame.kp,
ctx.last_motor_frame.kd,
)
natural_target = require_entry_frame_provider(to_state).get_entry_frame(ctx)
self._target = MotorFrame.empty(ctx.robot_layout)
ctx.resolve_motor_frame(natural_target, self._target)
self._output = MotorFrame.empty(ctx.robot_layout)
def apply(self, ctx, dt, progress):
if self._start is None or self._target is None or self._output is None:
raise RuntimeError("pose gain blend has not started")
alpha = progress
if self._curve == "smoothstep":
alpha = progress * progress * (3.0 - 2.0 * progress)
for start, target, output in (
(self._start.qpos, self._target.qpos, self._output.qpos),
(self._start.kp, self._target.kp, self._output.kp),
(self._start.kd, self._target.kd, self._output.kd),
):
np.subtract(target, start, out=output)
output *= alpha
output += start
ctx.set_motor_target(self._output)在 plugin.py 导入类,并加入返回的 ModDefinition:
from bxi_example_py_elf3.framework.mod_api import ModDefinition
from .pose_gain_blend import PoseGainBlendTransition
def create_mod(ctx):
return ModDefinition(
state_factories={
# 保留这个 Mod 原有的状态工厂
},
transition_plugins={
PoseGainBlendTransition.type_name: PoseGainBlendTransition,
},
)注册关系属于当前 Mod,不存在“导入模块时修改全局注册表”的隐式副作用。遗漏 transition_plugins 会在加载配置时得到 unknown transition type,不会悄悄依赖导入顺序。
class SinWaveState(RobotControlState, EntryFrameProvider):
def get_entry_frame(self, ctx):
last = ctx.last_motor_frame
return self._motor_frame(ctx, last.qpos, last.kp, last.kd)transition_profiles:
pose_blend:
type: com.example.sin_wave.pose_gain_blend
duration: 0.5
curve: smoothstep
routes:
- from: com.bxi.basic_actions/normal
event: activate
to: sin_wave
transition: pose_blend单次覆盖:
transition:
profile: pose_blend
duration: 1.0
curve: linear修改插件后重启控制节点;启动日志应显示 ModRuntime 构建成功。常见错误:
-
unknown transition type:插件没有加入transition_plugins。 -
transition key ... does not match:字典键与类的type_name不一致。 -
duplicate transition type:名称未包含唯一 Mod 命名空间。 -
unknown fields:字段未被 reader 消费。 -
must implement EntryFrameProvider:某条目标边能力不足。
下一课:双状态运行混合。