注释与提示信息全面中文化:源码 66 个 .py + 脚本/配置 45 个文件
背景:现场读代码的人只读中文。此前代码里的 docstring 是英文、行内注释是中文,
一半英文一半中文反而更难看懂 —— 新手打开一个 .py 不知道这个模块为什么存在。
范围(本仓库自己的代码,厂商上游不动)
src/arm_application/** 37 个文件:servo_node / perception_node / handeye_* / dh_pgea /
yolo_detector / kalman_filter / goto_ee_plane / dual_gripper …
src/arm_interfaces/** 11 个文件:paths / manifest / loader / config_guard / errors …
src/arm_control/** 5 个文件
src/arm_bringup/** 3 个 launch
scripts/** 6 个 .py + 29 个 .sh(scripts/ 是"唯一操作入口",用户屏幕上
看到的就是这些提示,所以提示信息也一并中文化)
config/ handeye/ devices/ src/arm_*/config/ 17 个 .yaml/.xml/.xacro/.cfg
口径(写进 docs/FRAMEWORK_RULES.md 新增的 R6.5,作为长期约束)
注释、docstring、print/日志/异常里给人看的句子 = 中文;
标识符、API 名、话题名、参数名、字段名、命令名 = 保留英文原文(用 `` `` 包起来);
单位、十六进制、协议报文、CRC 值、命令示例 = 原样保留;
src/flexiv_*/ = 厂商上游代码,不许改也不许翻译(R4.4)。
翻译保原意与原有详细程度:那些"为什么不能用另一种做法""踩过什么坑"的说明
全部完整保留 —— 它们才是这份代码里最贵的部分。
新增 scripts/check_text_only.py:只改文字时的自证工具
人工 review 靠不住(115 个文件、3000 多行 diff)。这个脚本把改动前后的源码解析成
语法树,删掉 docstring、抹平字符串常量、把 f-string 只留"插值了几次 + 插值是什么",
再比对:完全相同 = 改动范围内没有逻辑变化。
留了一个口子:f-string 的插值按内容排序后再比 —— 因为中文语序常常要和英文反着来
(f"on {port} read {idx} failed" → f"在 {port} 上读 {idx} 失败"),只换显示顺序不该判失败;
但多插一个、少插一个、换掉被插入的表达式,立刻会报出来。
本次 66 个 .py 全部通过该闸门。
验收
改动的 66 个 .py:scripts/check_text_only.py 全部通过(零逻辑变化)
45 个非 .py 文件:去注释后与 HEAD 逐字节相同(零配置变化)
docstring 总数:没有一个文件减少(不存在"翻译时顺手删了说明")
单测:arm_application 233 / arm_driver_flexiv 69 / arm_interfaces 46 /
arm_kinematics 24 / arm_sim_gazebo 236 = 608 passed,与改动前一致
全部 .sh:bash -n 通过;并额外实际执行了 --help 验证(见下一个提交:
bash -n 对 CRLF 脚本会漏报,只有真跑才暴露)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Load ~/ros2/config/site.yaml and print bash export lines or JSON.
|
||||
"""读取 ``~/ros2/config/site.yaml``,打印 bash 的 export 行或 JSON。
|
||||
|
||||
Usage:
|
||||
用法:
|
||||
eval "$(python3 ~/ros2/scripts/lib/export_site_env.py)"
|
||||
python3 ~/ros2/scripts/lib/export_site_env.py --json
|
||||
python3 ~/ros2/scripts/lib/export_site_env.py --print
|
||||
@@ -35,18 +35,18 @@ def site_path() -> Path:
|
||||
def load_site(path: Path | None = None) -> dict:
|
||||
path = path or site_path()
|
||||
if not path.is_file():
|
||||
raise FileNotFoundError(f"site config not found: {path}")
|
||||
raise FileNotFoundError(f"找不到 site 配置: {path}")
|
||||
text = path.read_text(encoding="utf-8")
|
||||
if yaml is None:
|
||||
raise RuntimeError("PyYAML required: pip/conda install pyyaml")
|
||||
raise RuntimeError("需要 PyYAML:pip/conda install pyyaml")
|
||||
data = yaml.safe_load(text) or {}
|
||||
if not isinstance(data, dict):
|
||||
raise ValueError(f"invalid site.yaml: {path}")
|
||||
raise ValueError(f"无效的 site.yaml: {path}")
|
||||
return data
|
||||
|
||||
|
||||
def list_robots(data: dict) -> list[dict]:
|
||||
"""Catalog entries from robot.robots (list or SN-keyed map)."""
|
||||
"""从 robot.robots 取清单条目(列表或按 SN 索引的映射)。"""
|
||||
raw = (data.get("robot") or {}).get("robots") or []
|
||||
out: list[dict] = []
|
||||
if isinstance(raw, dict):
|
||||
@@ -90,7 +90,7 @@ def _deep_merge(base: dict, overlay: dict) -> dict:
|
||||
|
||||
|
||||
def normalize_robot_sn(data: dict, want: str) -> str:
|
||||
"""Map 89/91/left/right/label → full Flexiv SN."""
|
||||
"""把 89/91/left/right/label 映射成完整的 Flexiv SN。"""
|
||||
w = (want or "").strip()
|
||||
if not w:
|
||||
return w
|
||||
@@ -109,7 +109,7 @@ def normalize_robot_sn(data: dict, want: str) -> str:
|
||||
|
||||
|
||||
def resolve_robot(data: dict, sn: str | None = None) -> dict:
|
||||
"""Active arm: ROBOT_SN env overrides robot.sn; catalog fills ip/iface/frames."""
|
||||
"""当前生效的机械臂:环境变量 ``ROBOT_SN`` 覆盖 robot.sn;清单补齐 ip/iface/frames。"""
|
||||
robot = dict(data.get("robot") or {})
|
||||
catalog = list_robots(data)
|
||||
want = normalize_robot_sn(
|
||||
@@ -146,7 +146,7 @@ def resolve_robot(data: dict, sn: str | None = None) -> dict:
|
||||
|
||||
|
||||
def apply_active_arm(data: dict, sn: str | None = None) -> tuple[dict, dict]:
|
||||
"""Merge robots[] camera/gripper/paths/servo onto site defaults for the active SN."""
|
||||
"""把 robots[] 里的 camera/gripper/paths/servo 合并到当前 SN 对应的 site 默认值上。"""
|
||||
robot = resolve_robot(data, sn)
|
||||
merged = dict(data)
|
||||
for key in _ARM_SECTION_KEYS:
|
||||
@@ -157,14 +157,14 @@ def apply_active_arm(data: dict, sn: str | None = None) -> tuple[dict, dict]:
|
||||
|
||||
|
||||
def camera_ns(data: dict) -> str:
|
||||
"""Topic namespace for the active wrist camera (d405_left / d405_right)."""
|
||||
"""当前腕部相机(d405_left / d405_right)的话题命名空间。"""
|
||||
cam = data.get("camera") or {}
|
||||
name = str(cam.get("name") or "d405").strip().strip("/")
|
||||
return name or "d405"
|
||||
|
||||
|
||||
def camera_frames(data: dict) -> tuple[str, str, str]:
|
||||
"""(namespace, optical_frame, depth_topic) for the active arm."""
|
||||
"""当前机械臂的(命名空间, 光学坐标系, 深度话题)。"""
|
||||
ns = camera_ns(data)
|
||||
yolo = data.get("yolo") or {}
|
||||
frame = str(yolo.get("camera_frame") or "").strip()
|
||||
@@ -177,7 +177,7 @@ def camera_frames(data: dict) -> tuple[str, str, str]:
|
||||
|
||||
|
||||
def flatten(data: dict) -> dict:
|
||||
"""Flat SITE_* env map for bash scripts."""
|
||||
"""供 bash 脚本使用的扁平 ``SITE_*`` 环境变量映射。"""
|
||||
data, robot = apply_active_arm(data)
|
||||
paths = data.get("paths") or {}
|
||||
cam = data.get("camera") or {}
|
||||
@@ -341,7 +341,7 @@ def flatten(data: dict) -> dict:
|
||||
|
||||
|
||||
def gripper_overrides(data: dict) -> dict:
|
||||
"""ROS parameter dict for dh_pgea_node (DH Modbus, not Flexiv gripper)."""
|
||||
"""供 dh_pgea_node 使用的 ROS 参数字典(DH Modbus,不是 Flexiv 夹爪)。"""
|
||||
data, _robot = apply_active_arm(data)
|
||||
g = data.get("gripper") or {}
|
||||
return {
|
||||
@@ -362,7 +362,7 @@ def gripper_overrides(data: dict) -> dict:
|
||||
|
||||
|
||||
def perception_overrides(data: dict) -> dict:
|
||||
"""ROS parameter dict for perception_node."""
|
||||
"""供 perception_node 使用的 ROS 参数字典。"""
|
||||
data, _robot = apply_active_arm(data)
|
||||
paths = data.get("paths") or {}
|
||||
yolo = data.get("yolo") or {}
|
||||
@@ -415,7 +415,7 @@ def perception_overrides(data: dict) -> dict:
|
||||
|
||||
|
||||
def servo_overrides(data: dict) -> dict:
|
||||
"""ROS parameter dict for servo_node."""
|
||||
"""供 servo_node 使用的 ROS 参数字典。"""
|
||||
data, robot = apply_active_arm(data)
|
||||
paths = data.get("paths") or {}
|
||||
yolo = data.get("yolo") or {}
|
||||
@@ -431,7 +431,7 @@ def servo_overrides(data: dict) -> dict:
|
||||
"arm_label": str(robot.get("label") or robot.get("arm") or ""),
|
||||
"z_ref_m": float(servo.get("z_ref_m") or yolo.get("z_ref_m") or 0.40),
|
||||
}
|
||||
# Pass through remaining servo keys that match node params
|
||||
# 透传其余与节点参数同名的 servo 键
|
||||
keys = [
|
||||
"control_hz", "watchdog_timeout_sec", "lost_timeout_sec", "stale_hold_sec",
|
||||
"max_extrapolate_sec", "predict_enable", "predict_sec",
|
||||
@@ -460,7 +460,7 @@ def main() -> int:
|
||||
ap.add_argument("--json", action="store_true")
|
||||
ap.add_argument("--print", action="store_true", dest="do_print")
|
||||
ap.add_argument("--config", default="")
|
||||
ap.add_argument("--sn", "--arm", dest="sn", default="", help="89|91|left|right|full SN")
|
||||
ap.add_argument("--sn", "--arm", dest="sn", default="", help="89|91|left|right|完整 SN")
|
||||
args = ap.parse_args()
|
||||
if args.sn:
|
||||
os.environ["ROBOT_SN"] = str(args.sn)
|
||||
@@ -471,35 +471,35 @@ def main() -> int:
|
||||
print(json.dumps({"site": data, "env": flat}, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
if args.do_print:
|
||||
print(f"site config: {path}")
|
||||
print(f"site 配置: {path}")
|
||||
print(
|
||||
f" robot SN : {flat['SITE_ROBOT_SN']}"
|
||||
f" 机械臂 SN : {flat['SITE_ROBOT_SN']}"
|
||||
f"{(' (' + flat['SITE_ROBOT_LABEL'] + ')') if flat.get('SITE_ROBOT_LABEL') else ''}"
|
||||
f" arm={flat.get('SITE_ROBOT_ARM') or '?'}"
|
||||
)
|
||||
print(f" catalog : {flat.get('SITE_ROBOT_CATALOG') or '(none)'}")
|
||||
print(f" robot IP : {flat['SITE_ROBOT_IP']} pc={flat['SITE_PC_IP']} iface={flat.get('SITE_NET_IFACE') or '?'}")
|
||||
print(f" frames : prefix={flat['SITE_JOINT_PREFIX']} ee={flat['SITE_EE_FRAME']}")
|
||||
print(f" tool name : {flat.get('SITE_ROBOT_TOOL_NAME') or '(keep current)'}")
|
||||
print(f" 清单 : {flat.get('SITE_ROBOT_CATALOG') or '(无)'}")
|
||||
print(f" 机械臂 IP : {flat['SITE_ROBOT_IP']} pc={flat['SITE_PC_IP']} iface={flat.get('SITE_NET_IFACE') or '?'}")
|
||||
print(f" 坐标系 : prefix={flat['SITE_JOINT_PREFIX']} ee={flat['SITE_EE_FRAME']}")
|
||||
print(f" 工具名 : {flat.get('SITE_ROBOT_TOOL_NAME') or '(保持当前)'}")
|
||||
catalog_sns = [str(r.get("sn") or "") for r in list_robots(data)]
|
||||
if catalog_sns and flat["SITE_ROBOT_SN"] not in catalog_sns:
|
||||
print(
|
||||
f" WARNING: robot.sn {flat['SITE_ROBOT_SN']!r} is not in robots[]",
|
||||
f" 警告:robot.sn {flat['SITE_ROBOT_SN']!r} 不在 robots[] 里",
|
||||
file=sys.stderr,
|
||||
)
|
||||
print(f" yolo weights : {flat['SITE_YOLO_WEIGHTS']}")
|
||||
print(f" resolved : {flat['SITE_YOLO_WEIGHTS_RESOLVED']}")
|
||||
print(f" handeye : {flat['SITE_HANDEYE_FILE']}")
|
||||
print(f" handeye wp : {flat['SITE_HANDEYE_WAYPOINTS']}")
|
||||
print(f" handeye dir : {flat['SITE_HANDEYE_WORKDIR']}")
|
||||
print(f" named poses : {flat.get('SITE_NAMED_POSES_FILE')}")
|
||||
print(f" YOLO 权重 : {flat['SITE_YOLO_WEIGHTS']}")
|
||||
print(f" 解析后 : {flat['SITE_YOLO_WEIGHTS_RESOLVED']}")
|
||||
print(f" 手眼 : {flat['SITE_HANDEYE_FILE']}")
|
||||
print(f" 手眼路点 : {flat['SITE_HANDEYE_WAYPOINTS']}")
|
||||
print(f" 手眼目录 : {flat['SITE_HANDEYE_WORKDIR']}")
|
||||
print(f" 命名位姿 : {flat.get('SITE_NAMED_POSES_FILE')}")
|
||||
print(
|
||||
" board : "
|
||||
" 标定板 : "
|
||||
f"{flat['SITE_HANDEYE_BOARD_COLS']}x{flat['SITE_HANDEYE_BOARD_ROWS']} "
|
||||
f"inner, square={flat['SITE_HANDEYE_SQUARE_M']} m"
|
||||
f"内角点, square={flat['SITE_HANDEYE_SQUARE_M']} m"
|
||||
)
|
||||
print(
|
||||
f" camera : serial={flat['SITE_CAM_SERIAL'] or '(empty)'} "
|
||||
f" 相机 : serial={flat['SITE_CAM_SERIAL'] or '(空)'} "
|
||||
f"{flat['SITE_CAM_WIDTH']}x{flat['SITE_CAM_HEIGHT']}@{flat['SITE_CAM_FPS']} "
|
||||
f"ae={flat.get('SITE_CAM_AUTO_EXPOSURE')} "
|
||||
f"exp={flat.get('SITE_CAM_EXPOSURE_US')}us "
|
||||
@@ -507,26 +507,26 @@ def main() -> int:
|
||||
f"ae_limit={flat.get('SITE_CAM_AE_LIMIT_US')}us emitter={flat.get('SITE_CAM_EMITTER')}"
|
||||
)
|
||||
print(
|
||||
f" catalog cam : leftD405={flat.get('SITE_D405_LEFT_SERIAL')} "
|
||||
f" 清单相机 : leftD405={flat.get('SITE_D405_LEFT_SERIAL')} "
|
||||
f"rightD405={flat.get('SITE_D405_RIGHT_SERIAL')} "
|
||||
f"D435={flat.get('SITE_D435_SERIAL')}"
|
||||
)
|
||||
print(
|
||||
f" gripper : {flat.get('SITE_GRIP_MODEL')} port={flat.get('SITE_GRIP_PORT')}"
|
||||
f" 夹爪 : {flat.get('SITE_GRIP_MODEL')} port={flat.get('SITE_GRIP_PORT')}"
|
||||
)
|
||||
print(
|
||||
f" hole_detect : backend={flat.get('SITE_HOLE_BACKEND')} "
|
||||
f"weights={flat.get('SITE_HOLE_WEIGHTS')}"
|
||||
)
|
||||
print(
|
||||
f" tactile : backend={flat.get('SITE_TACTILE_BACKEND')} "
|
||||
f" 触觉 : backend={flat.get('SITE_TACTILE_BACKEND')} "
|
||||
f"L={flat.get('SITE_TACTILE_LEFT')} R={flat.get('SITE_TACTILE_RIGHT')} "
|
||||
f"cfg={flat.get('SITE_TACTILE_CONFIG')}"
|
||||
)
|
||||
print(f" go_home : pose={flat['SITE_GO_HOME_POSE']} speed={flat['SITE_GO_HOME_SPEED']}")
|
||||
he_path = Path(flat["SITE_HANDEYE_FILE"]).expanduser()
|
||||
if not he_path.is_file():
|
||||
print(f" WARNING: hand-eye file missing: {he_path}", file=sys.stderr)
|
||||
print(f" 警告:手眼文件缺失: {he_path}", file=sys.stderr)
|
||||
else:
|
||||
try:
|
||||
he_data = yaml.safe_load(he_path.read_text(encoding="utf-8")) or {}
|
||||
@@ -536,28 +536,28 @@ def main() -> int:
|
||||
he_data.get("uncalibrated") or "T_4x4" not in he_data
|
||||
):
|
||||
print(
|
||||
f" WARNING: {he_path.name} is not calibrated for this arm "
|
||||
f" 警告:{he_path.name} 没有针对该机械臂标定 "
|
||||
f"(run_handeye.sh teach/calibrate --arm {flat.get('SITE_ROBOT_ARM') or '91'})",
|
||||
file=sys.stderr,
|
||||
)
|
||||
if "063991" in flat["SITE_ROBOT_SN"] and flat.get("SITE_CAM_SERIAL") == flat.get("SITE_D405_LEFT_SERIAL"):
|
||||
print(
|
||||
" WARNING: 91 is using the LEFT wrist D405 serial — check robots[].camera.serial",
|
||||
" 警告:91 用的是左腕 D405 的序列号 — 请检查 robots[].camera.serial",
|
||||
file=sys.stderr,
|
||||
)
|
||||
if "RGI" in str(flat.get("SITE_GRIP_MODEL") or ""):
|
||||
print(
|
||||
" NOTE: 本臂是 RGI。CLI: bash ~/ros2/scripts/run_gripper.sh --arm 91 --cli open|rotate",
|
||||
" 提示:本臂是 RGI。CLI: bash ~/ros2/scripts/run_gripper.sh --arm 91 --cli open|rotate",
|
||||
file=sys.stderr,
|
||||
)
|
||||
w = Path(flat["SITE_YOLO_WEIGHTS"]).expanduser()
|
||||
if not w.is_file():
|
||||
print(f" WARNING: weights file missing: {w}", file=sys.stderr)
|
||||
print(f" 警告:权重文件缺失: {w}", file=sys.stderr)
|
||||
return 2
|
||||
return 0
|
||||
# bash exports
|
||||
# bash 导出
|
||||
for k, v in flat.items():
|
||||
# escape for eval
|
||||
# 为 eval 转义
|
||||
vv = str(v).replace("'", "'\"'\"'")
|
||||
print(f"export {k}='{vv}'")
|
||||
return 0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Load ~/ros2/config/site.yaml into SITE_* environment variables.
|
||||
# Usage (from other scripts):
|
||||
# 把 ~/ros2/config/site.yaml 加载成 `SITE_*` 环境变量。
|
||||
# 用法(在其它脚本里):
|
||||
# source "${ROOT}/scripts/lib/load_site_config.sh"
|
||||
|
||||
_SITE_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
@@ -13,7 +13,7 @@ else
|
||||
# shellcheck disable=SC1090
|
||||
eval "$(python3 "${_SITE_EXPORTER}" --config "${_SITE_FILE}")"
|
||||
export SITE_CONFIG="${_SITE_FILE}"
|
||||
# Resolve 89/91/left/right aliases to the full Flexiv SN for RDK.
|
||||
# 把 89/91/left/right 别名解析成完整的 Flexiv SN,供 RDK 使用。
|
||||
if [[ -n "${SITE_ROBOT_SN:-}" ]]; then
|
||||
export ROBOT_SN="${SITE_ROBOT_SN}"
|
||||
fi
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
# Parse --sn / --arm from argv. Must be sourced BEFORE load_site_config.sh
|
||||
# so ROBOT_SN is visible to export_site_env.py.
|
||||
# 从 argv 里解析 --sn / --arm。必须在 `load_site_config.sh` 之前 source,
|
||||
# 这样 `ROBOT_SN` 才能被 export_site_env.py 看到。
|
||||
#
|
||||
# source "${ROOT}/scripts/lib/parse_arm_args.sh"
|
||||
# parse_arm_args "$@"
|
||||
# set -- "${PARSE_ARM_REST[@]}"
|
||||
#
|
||||
# Aliases (89|91|left|right) are resolved in export_site_env.py.
|
||||
# 别名(89|91|left|right)在 export_site_env.py 里解析。
|
||||
|
||||
parse_arm_args() {
|
||||
PARSE_ARM_REST=()
|
||||
|
||||
Reference in New Issue
Block a user