找回密码
 立即注册
首页 业界区 安全 ROS1 noetic 中将 Unitree G1 基于 Gazebo/RViz 关节联 ...

ROS1 noetic 中将 Unitree G1 基于 Gazebo/RViz 关节联动

支季雅 2026-1-13 21:45:00
博客地址:https://www.cnblogs.com/zylyehuo/
基础:ROS1 noetic 中将 Unitree G1 的 URDF 导入 Gazebo/RViz


  • RViz 只是“眼睛”,Gazebo 才是“身体”。
  • RViz 滑条 (joint_state_publisher):它只是在广播“数据”:“我现在假设关节在 30 度”。RViz 听到了,就画给你看。
  • Gazebo:它模拟的是真实的物理电机。它听不到“假设”,它只接受“电机电压/扭矩指令”。
目前的连接是断开的: 滑条 GUI ---> /joint_states ---> RViz (更新画面) Gazebo (不仅没收到指令,而且现在的模型里连“电机”都没装,只有空壳)
要解决这个问题,需要做三件事


  • 给 URDF 装电机:添加  标签。
  • 配置控制器:生成 controllers.yaml。
  • 写个转换桥梁:把滑条的信号“翻译”成电机指令发给 Gazebo。
效果预览

1.png

运行 rviz_gazebo.py

2.png

运行 rviz_gazebo_fixed.py

3.png

第一步:安装必要的 ROS 控制包
  1. sudo apt-get install ros-noetic-ros-control ros-noetic-ros-controllers ros-noetic-gazebo-ros-control
复制代码
第二步:运行一键配置脚本

~/g1_ws/src/g1_description/scripts/rviz_gazebo.py

这份生成的机器人会由于没有控制代码而倒地
  1. #!/usr/bin/env python3
  2. import os
  3. import sys
  4. import xml.etree.ElementTree as ET
  5. # ================= 路径配置 =================
  6. SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
  7. PKG_ROOT = os.path.dirname(SCRIPT_DIR)
  8. PKG_NAME = "g1_description"
  9. # 文件路径
  10. SRC_URDF = os.path.join(PKG_ROOT, "urdf/g1_29dof.urdf")
  11. DST_URDF = os.path.join(PKG_ROOT, "urdf/g1_complete.urdf")
  12. YAML_FILE = os.path.join(PKG_ROOT, "config/controllers.yaml")
  13. BRIDGE_SCRIPT = os.path.join(PKG_ROOT, "scripts/bridge_node.py")
  14. LAUNCH_GAZEBO = os.path.join(PKG_ROOT, "launch/1_gazebo.launch")
  15. LAUNCH_RVIZ = os.path.join(PKG_ROOT, "launch/2_rviz.launch")
  16. # 颜色映射
  17. COLOR_MAP = {
  18.     "white": "Gazebo/Grey",
  19.     "grey":  "Gazebo/Grey",
  20.     "dark":  "Gazebo/DarkGrey",
  21.     "black": "Gazebo/FlatBlack"
  22. }
  23. print(f"正在生成【全物理走路调试版】环境...")
  24. def generate_files():
  25.     # 1. 目录检查
  26.     os.makedirs(os.path.join(PKG_ROOT, "config"), exist_ok=True)
  27.     os.makedirs(os.path.join(PKG_ROOT, "launch"), exist_ok=True)
  28.    
  29.     if not os.path.exists(SRC_URDF):
  30.         print(f"错误: 找不到 {SRC_URDF}")
  31.         sys.exit(1)
  32.     tree = ET.parse(SRC_URDF)
  33.     root = tree.getroot()
  34.     joint_names = []
  35.     # ================= 1. URDF 物理配置 (Floating Base) =================
  36.    
  37.     # 1.1 移除所有 World 固定锚点 (彻底自由)
  38.     for link in root.findall('link'):
  39.         if link.get('name') == 'world': root.remove(link)
  40.     for joint in root.findall('joint'):
  41.         if joint.get('name') == 'world_anchor': root.remove(joint)
  42.     # 1.2 确保 base_link 存在 (作为浮动基座的根)
  43.     has_base = False
  44.     for link in root.findall('link'):
  45.         if link.get('name') == 'base_link': has_base = True
  46.     if not has_base:
  47.         base_link = ET.Element('link', name='base_link')
  48.         # 给 base_link 一个微小的惯性,防止 Gazebo 忽略它
  49.         inertial = ET.SubElement(base_link, 'inertial')
  50.         ET.SubElement(inertial, 'mass', value="0.01")
  51.         ET.SubElement(inertial, 'inertia', ixx="0.001", ixy="0", ixz="0", iyy="0.001", iyz="0", izz="0.001")
  52.         root.insert(0, base_link)
  53.         
  54.         # 连接 base_link -> pelvis
  55.         joint = ET.Element('joint', name='base_to_pelvis', type='fixed')
  56.         ET.SubElement(joint, 'parent', link='base_link')
  57.         ET.SubElement(joint, 'child', link='pelvis')
  58.         ET.SubElement(joint, 'origin', xyz="0 0 0", rpy="0 0 0")
  59.         root.insert(1, joint)
  60.     # 1.3 物理参数精细化 (摩擦力与接触刚度)
  61.     # 清理所有旧的 gazebo 标签
  62.     for gazebo in root.findall('gazebo'):
  63.         root.remove(gazebo)
  64.     for link in root.findall('link'):
  65.         name = link.get('name')
  66.         
  67.         # A. 惯性修复
  68.         inertial = link.find('inertial')
  69.         if inertial:
  70.             mass = inertial.find('mass')
  71.             if mass and float(mass.get('value')) < 0.05: mass.set('value', '0.05')
  72.             inertia = inertial.find('inertia')
  73.             if inertia:
  74.                 for ax in ['ixx','iyy','izz']:
  75.                     if float(inertia.get(ax)) < 0.0001: inertia.set(ax, '0.0001')
  76.         # B. Gazebo 属性生成
  77.         gz = ET.SubElement(root, 'gazebo', reference=name)
  78.         
  79.         # 开启重力!(这是走路的前提)
  80.         ET.SubElement(gz, 'turnGravityOff').text = 'false'
  81.         ET.SubElement(gz, 'selfCollide').text = 'false' # 暂时关闭自碰撞防止炸机
  82.         # 颜色匹配
  83.         visual = link.find('visual')
  84.         mat_name = "white"
  85.         if visual:
  86.             mat = visual.find('material')
  87.             if mat is not None: mat_name = mat.get('name', 'white').lower()
  88.         ET.SubElement(gz, 'material').text = COLOR_MAP.get(mat_name, "Gazebo/Grey")
  89.         # C. 【关键】脚底摩擦力增强
  90.         # 假设 link 名字里带 ankle_roll 的是脚底接触件
  91.         if "ankle_roll" in name:
  92.             # 高摩擦力,防止打滑
  93.             ET.SubElement(gz, 'mu1').text = "1.5"
  94.             ET.SubElement(gz, 'mu2').text = "1.5"
  95.             # 接触刚度 (kp) 和 阻尼 (kd)
  96.             # kp 越大越硬,kd 越大越不弹
  97.             ET.SubElement(gz, 'kp').text = "1000000.0"
  98.             ET.SubElement(gz, 'kd').text = "100.0"
  99.             # 最小穿透深度,防止抖动
  100.             ET.SubElement(gz, 'minDepth').text = "0.001"
  101.             ET.SubElement(gz, 'maxVel').text = "0.0" # 防止高速弹射
  102.     # ================= 2. 插件与电机 =================
  103.     # ros_control
  104.     gz_c = ET.SubElement(root, 'gazebo')
  105.     pl_c = ET.SubElement(gz_c, 'plugin', name="gazebo_ros_control", filename="libgazebo_ros_control.so")
  106.     ET.SubElement(pl_c, 'robotNamespace').text = f"/{PKG_NAME}"
  107.     ET.SubElement(pl_c, 'legacyModeNS').text = "true"
  108.     # P3D (真值反馈,用于做闭环控制)
  109.     gz_p3d = ET.SubElement(root, 'gazebo')
  110.     pl_p3d = ET.SubElement(gz_p3d, 'plugin', name="p3d_base_controller", filename="libgazebo_ros_p3d.so")
  111.     ET.SubElement(pl_p3d, 'alwaysOn').text = "true"
  112.     ET.SubElement(pl_p3d, 'updateRate').text = "100.0"
  113.     ET.SubElement(pl_p3d, 'bodyName').text = "pelvis"
  114.     ET.SubElement(pl_p3d, 'topicName').text = "ground_truth_odom"
  115.     ET.SubElement(pl_p3d, 'frameName').text = "world"
  116.     ET.SubElement(pl_p3d, 'xyzOffsets').text = "0 0 0"
  117.     ET.SubElement(pl_p3d, 'rpyOffsets').text = "0 0 0"
  118.     # 电机接口
  119.     for joint in root.findall('joint'):
  120.         if joint.get('type') in ['revolute', 'continuous', 'prismatic']:
  121.             name = joint.get('name')
  122.             joint_names.append(name)
  123.             
  124.             # 去重
  125.             is_dup = False
  126.             for tr in root.findall('transmission'):
  127.                 if tr.get('name') == f"{name}_trans": is_dup = True
  128.             
  129.             if not is_dup:
  130.                 trans = ET.SubElement(root, 'transmission', name=f"{name}_trans")
  131.                 ET.SubElement(trans, 'type').text = "transmission_interface/SimpleTransmission"
  132.                 j = ET.SubElement(trans, 'joint', name=name)
  133.                 ET.SubElement(j, 'hardwareInterface').text = "hardware_interface/PositionJointInterface"
  134.                 act = ET.SubElement(trans, 'actuator', name=f"{name}_motor")
  135.                 ET.SubElement(act, 'mechanicalReduction').text = "1"
  136.                 ET.SubElement(act, 'hardwareInterface').text = "hardware_interface/PositionJointInterface"
  137.     tree.write(DST_URDF, encoding='utf-8', xml_declaration=True)
  138.     # ================= 3. PID 参数 (支撑级) =================
  139.     with open(YAML_FILE, 'w') as f:
  140.         f.write(f"{PKG_NAME}:\n")
  141.         f.write("  joint_state_controller:\n")
  142.         f.write("    type: joint_state_controller/JointStateController\n")
  143.         f.write("    publish_rate: 100\n\n")
  144.         
  145.         for jn in joint_names:
  146.             f.write(f"  {jn}_controller:\n")
  147.             f.write("    type: position_controllers/JointPositionController\n")
  148.             f.write(f"    joint: {jn}\n")
  149.             # 这里的 PID 是控制器层面的,可以稍大
  150.             f.write("    pid: {p: 800.0, i: 0.0, d: 20.0}\n")
  151.         
  152.         # Gazebo 物理引擎 PID (核心)
  153.         f.write("\n  gazebo_ros_control:\n    pid_gains:\n")
  154.         for jn in joint_names:
  155.             if "ankle" in jn or "knee" in jn or "hip" in jn:
  156.                 # 腿部:需要大力矩支撑体重,但阻尼要适中防止震荡
  157.                 # P=800~1000 足够支撑 G1 这种体型的机器人
  158.                 f.write(f"      {jn}: {{p: 1000.0, i: 0.1, d: 50.0}}\n")
  159.             else:
  160.                 # 手臂:不需要太大
  161.                 f.write(f"      {jn}: {{p: 300.0, i: 0.0, d: 5.0}}\n")
  162.     # ================= 4. Bridge Script =================
  163.     with open(BRIDGE_SCRIPT, 'w') as f:
  164.         f.write(f"""#!/usr/bin/env python3
  165. import rospy
  166. from sensor_msgs.msg import JointState
  167. from std_msgs.msg import Float64
  168. class Bridge:
  169.     def __init__(self):
  170.         rospy.init_node('rviz_bridge')
  171.         self.pubs = {{}}
  172.         self.ns = "/{PKG_NAME}"
  173.         rospy.Subscriber("/gui_commands", JointState, self.cb)
  174.     def cb(self, msg):
  175.         for i, n in enumerate(msg.name):
  176.             if n not in self.pubs: self.pubs[n] = rospy.Publisher(f"{{self.ns}}/{{n}}_controller/command", Float64, queue_size=10)
  177.             self.pubs[n].publish(msg.position[i])
  178. if __name__ == '__main__': Bridge(); rospy.spin()
  179. """)
  180.     os.chmod(BRIDGE_SCRIPT, 0o755)
  181.     # ================= 5. Launch Gazebo (Walk Ready) =================
  182.     ctl_str = " ".join([f"{jn}_controller" for jn in joint_names])
  183.     urdf_rel = "urdf/g1_complete.urdf"
  184.     yaml_rel = "config/controllers.yaml"
  185.    
  186.     with open(LAUNCH_GAZEBO, 'w') as f:
  187.         f.write(f"""<launch>
  188.   <include file="$(find gazebo_ros)/launch/empty_world.launch">
  189.      
  190.   </include>
  191.   <param name="robot_description" textfile="$(find {PKG_NAME})/{urdf_rel}" />
  192.   <rosparam file="$(find {PKG_NAME})/{yaml_rel}" command="load"/>
  193.   <node name="spawn_urdf" pkg="gazebo_ros" type="spawn_model"
  194.         args="-file $(find {PKG_NAME})/{urdf_rel} -urdf -z 0.85 -model g1_robot
  195.               -J left_hip_pitch_joint -0.45
  196.               -J left_knee_joint 0.9
  197.               -J left_ankle_pitch_joint -0.45
  198.               -J right_hip_pitch_joint -0.45
  199.               -J right_knee_joint 0.9
  200.               -J right_ankle_pitch_joint -0.45"
  201.         output="screen" />
  202.   <node name="ctl_spawner" pkg="controller_manager" type="spawner" respawn="false"
  203.         output="screen" ns="/{PKG_NAME}" args="joint_state_controller {ctl_str}"/>
  204.   <node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher" output="screen">
  205.     <remap from="/joint_states" to="/{PKG_NAME}/joint_states" />
  206.   </node>
  207.   
  208.   <node name="bridge" pkg="{PKG_NAME}" type="bridge_node.py" output="screen" />
  209. </launch>""")
  210.     # ================= 6. Launch RViz =================
  211.     with open(LAUNCH_RVIZ, 'w') as f:
  212.         f.write(f"""<launch>
  213.   <param name="robot_description" textfile="$(find {PKG_NAME})/{urdf_rel}" />
  214.   <node name="jsp_gui" pkg="joint_state_publisher_gui" type="joint_state_publisher_gui">
  215.     <remap from="/joint_states" to="/gui_commands"/>
  216.   </node>
  217.   <node name="rviz" pkg="rviz" type="rviz" args="-d $(find {PKG_NAME})/launch/g1.rviz" />
  218. </launch>""")
  219.     print("\n[完成] 调试环境已就绪!")
  220.     print("-------------------------------------------------------")
  221.     print("1. python3 scripts/rviz_gazebo.py")
  222.     print("2. cd ~/g1_ws && catkin_make && source devel/setup.bash")
  223.     print("3. roslaunch g1_description 1_gazebo.launch")
  224.     print("   -> 注意:Gazebo 启动是暂停的。点击 Play 后,机器人会下落到地面。")
  225.     print("   -> 如果参数合适,它会保持蹲姿平衡;如果不合适,它会倒。")
  226.     print("-------------------------------------------------------")
  227. if __name__ == "__main__":
  228.     generate_files()
复制代码
~/g1_ws/src/g1_description/scripts/rviz_gazebo_fixed.py

这份生成的机器人会固定在世界系中
  1. #!/usr/bin/env python3
  2. import os
  3. import sys
  4. import xml.etree.ElementTree as ET
  5. # ================= 路径配置 =================
  6. SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
  7. PKG_ROOT = os.path.dirname(SCRIPT_DIR)
  8. PKG_NAME = "g1_description"
  9. # 文件路径
  10. SRC_URDF = os.path.join(PKG_ROOT, "urdf/g1_29dof.urdf")
  11. DST_URDF = os.path.join(PKG_ROOT, "urdf/g1_complete.urdf")
  12. YAML_FILE = os.path.join(PKG_ROOT, "config/controllers.yaml")
  13. BRIDGE_SCRIPT = os.path.join(PKG_ROOT, "scripts/bridge_node.py")
  14. LAUNCH_GAZEBO = os.path.join(PKG_ROOT, "launch/1_gazebo.launch")
  15. LAUNCH_RVIZ = os.path.join(PKG_ROOT, "launch/2_rviz.launch")
  16. # 颜色映射表 (URDF材质名 -> Gazebo材质名)
  17. COLOR_MAP = {
  18.     "white": "Gazebo/Grey",       # 银灰色
  19.     "grey":  "Gazebo/Grey",
  20.     "dark":  "Gazebo/DarkGrey",   # 深黑色
  21.     "black": "Gazebo/FlatBlack"
  22. }
  23. print(f"正在生成【完美静止 + 颜色修复版】配置文件...")
  24. def generate_files():
  25.     os.makedirs(os.path.join(PKG_ROOT, "config"), exist_ok=True)
  26.     os.makedirs(os.path.join(PKG_ROOT, "launch"), exist_ok=True)
  27.    
  28.     if not os.path.exists(SRC_URDF):
  29.         print(f"错误: 找不到 {SRC_URDF}")
  30.         sys.exit(1)
  31.     tree = ET.parse(SRC_URDF)
  32.     root = tree.getroot()
  33.     joint_names = []
  34.     # ================= 1. 结构修复 (Base Link & World Anchor) =================
  35.     # 1.1 补全 base_link
  36.     has_base = False
  37.     for link in root.findall('link'):
  38.         if link.get('name') == 'base_link': has_base = True
  39.     if not has_base:
  40.         base_link = ET.Element('link', name='base_link')
  41.         root.insert(0, base_link)
  42.         joint = ET.Element('joint', name='base_to_pelvis', type='fixed')
  43.         ET.SubElement(joint, 'parent', link='base_link')
  44.         ET.SubElement(joint, 'child', link='pelvis')
  45.         ET.SubElement(joint, 'origin', xyz="0 0 0", rpy="0 0 0")
  46.         root.insert(1, joint)
  47.     # 1.2 添加 World 锚点 (固定在 0.79m)
  48.     has_world = False
  49.     for link in root.findall('link'):
  50.         if link.get('name') == 'world': has_world = True
  51.     if not has_world:
  52.         world_link = ET.Element('link', name='world')
  53.         root.insert(0, world_link)
  54.         anchor = ET.Element('joint', name='world_anchor', type='fixed')
  55.         ET.SubElement(anchor, 'parent', link='world')
  56.         ET.SubElement(anchor, 'child', link='base_link')
  57.         ET.SubElement(anchor, 'origin', xyz="0 0 0.79", rpy="0 0 0")
  58.         root.insert(1, anchor)
  59.     # ================= 2. 颜色与物理修复 (核心) =================
  60.     # 先清理所有现有的 gazebo 标签,避免冲突
  61.     for gazebo in root.findall('gazebo'):
  62.         root.remove(gazebo)
  63.     # 遍历所有 Link,重新生成属性
  64.     for link in root.findall('link'):
  65.         name = link.get('name')
  66.         if name == 'world': continue
  67.         # --- A. 物理惯性清洗 ---
  68.         inertial = link.find('inertial')
  69.         if inertial:
  70.             mass = inertial.find('mass')
  71.             if mass:
  72.                 if float(mass.get('value')) < 0.01: mass.set('value', '0.01')
  73.             inertia = inertial.find('inertia')
  74.             if inertia:
  75.                 for ax in ['ixx','iyy','izz']:
  76.                     if float(inertia.get(ax)) < 0.001: inertia.set(ax, '0.001')
  77.         # --- B. 生成 Gazebo 标签 (颜色 + 物理) ---
  78.         gz = ET.SubElement(root, 'gazebo', reference=name)
  79.         
  80.         # B1. 物理属性 (无重力,无碰撞)
  81.         ET.SubElement(gz, 'turnGravityOff').text = 'true'
  82.         ET.SubElement(gz, 'selfCollide').text = 'false'
  83.         # B2. 颜色自动匹配
  84.         # 查找该 link 下的 visual -> material -> name
  85.         visual = link.find('visual')
  86.         mat_name = "white" # 默认值
  87.         if visual:
  88.             mat = visual.find('material')
  89.             if mat is not None:
  90.                 mat_name = mat.get('name', 'white').lower()
  91.         
  92.         # 根据字典映射颜色
  93.         gz_color = COLOR_MAP.get(mat_name, "Gazebo/Grey")
  94.         ET.SubElement(gz, 'material').text = gz_color
  95.     # ================= 3. 插件与电机 =================
  96.     gz_c = ET.SubElement(root, 'gazebo')
  97.     pl_c = ET.SubElement(gz_c, 'plugin', name="gazebo_ros_control", filename="libgazebo_ros_control.so")
  98.     ET.SubElement(pl_c, 'robotNamespace').text = f"/{PKG_NAME}"
  99.     ET.SubElement(pl_c, 'legacyModeNS').text = "true"
  100.     for joint in root.findall('joint'):
  101.         if joint.get('type') in ['revolute', 'continuous', 'prismatic']:
  102.             name = joint.get('name')
  103.             joint_names.append(name)
  104.             
  105.             # 检查重复
  106.             is_dup = False
  107.             for tr in root.findall('transmission'):
  108.                 if tr.get('name') == f"{name}_trans": is_dup = True
  109.             
  110.             if not is_dup:
  111.                 trans = ET.SubElement(root, 'transmission', name=f"{name}_trans")
  112.                 ET.SubElement(trans, 'type').text = "transmission_interface/SimpleTransmission"
  113.                 j = ET.SubElement(trans, 'joint', name=name)
  114.                 ET.SubElement(j, 'hardwareInterface').text = "hardware_interface/PositionJointInterface"
  115.                 act = ET.SubElement(trans, 'actuator', name=f"{name}_motor")
  116.                 ET.SubElement(act, 'mechanicalReduction').text = "1"
  117.                 ET.SubElement(act, 'hardwareInterface').text = "hardware_interface/PositionJointInterface"
  118.     tree.write(DST_URDF, encoding='utf-8', xml_declaration=True)
  119.     # ================= 4. PID Config (柔和防抖) =================
  120.     with open(YAML_FILE, 'w') as f:
  121.         f.write(f"{PKG_NAME}:\n")
  122.         f.write("  joint_state_controller:\n")
  123.         f.write("    type: joint_state_controller/JointStateController\n")
  124.         f.write("    publish_rate: 100\n\n")
  125.         for jn in joint_names:
  126.             f.write(f"  {jn}_controller:\n")
  127.             f.write("    type: position_controllers/JointPositionController\n")
  128.             f.write(f"    joint: {jn}\n")
  129.             # 这里的 PID 可以小一点,因为 Gazebo 物理层有自己的 PID
  130.             f.write("    pid: {p: 100.0, i: 0.0, d: 1.0}\n")
  131.         
  132.         # Gazebo 物理 PID (P=50, D=0.5 足够驱动失重关节)
  133.         f.write("\n  gazebo_ros_control:\n    pid_gains:\n")
  134.         for jn in joint_names:
  135.             f.write(f"      {jn}: {{p: 50.0, i: 0.0, d: 0.5}}\n")
  136.     # ================= 5. Bridge Script =================
  137.     with open(BRIDGE_SCRIPT, 'w') as f:
  138.         f.write(f"""#!/usr/bin/env python3
  139. import rospy
  140. from sensor_msgs.msg import JointState
  141. from std_msgs.msg import Float64
  142. class Bridge:
  143.     def __init__(self):
  144.         rospy.init_node('rviz_bridge')
  145.         self.pubs = {{}}
  146.         self.ns = "/{PKG_NAME}"
  147.         rospy.Subscriber("/gui_commands", JointState, self.cb)
  148.     def cb(self, msg):
  149.         for i, n in enumerate(msg.name):
  150.             if n not in self.pubs: self.pubs[n] = rospy.Publisher(f"{{self.ns}}/{{n}}_controller/command", Float64, queue_size=10)
  151.             self.pubs[n].publish(msg.position[i])
  152. if __name__ == '__main__': Bridge(); rospy.spin()
  153. """)
  154.     os.chmod(BRIDGE_SCRIPT, 0o755)
  155.     # ================= 6. Launch Gazebo =================
  156.     ctl_str = " ".join([f"{jn}_controller" for jn in joint_names])
  157.     urdf_rel = "urdf/g1_complete.urdf"
  158.     yaml_rel = "config/controllers.yaml"
  159.    
  160.     with open(LAUNCH_GAZEBO, 'w') as f:
  161.         f.write(f"""<launch>
  162.   <include file="$(find gazebo_ros)/launch/empty_world.launch">
  163.    
  164.    
  165.   </include>
  166.   <param name="robot_description" textfile="$(find {PKG_NAME})/{urdf_rel}" />
  167.   <rosparam file="$(find {PKG_NAME})/{yaml_rel}" command="load"/>
  168.   <node name="spawn_urdf" pkg="gazebo_ros" type="spawn_model"
  169.         args="-file $(find {PKG_NAME})/{urdf_rel} -urdf -model g1_robot"
  170.         output="screen" />
  171.   <node name="ctl_spawner" pkg="controller_manager" type="spawner" respawn="false"
  172.         output="screen" ns="/{PKG_NAME}" args="joint_state_controller {ctl_str}"/>
  173.   <node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher" output="screen">
  174.     <remap from="/joint_states" to="/{PKG_NAME}/joint_states" />
  175.   </node>
  176.   
  177.   <node name="bridge" pkg="{PKG_NAME}" type="bridge_node.py" output="screen" />
  178. </launch>""")
  179.     # ================= 7. Launch RViz =================
  180.     with open(LAUNCH_RVIZ, 'w') as f:
  181.         f.write(f"""<launch>
  182.   <param name="robot_description" textfile="$(find {PKG_NAME})/{urdf_rel}" />
  183.   <node name="jsp_gui" pkg="joint_state_publisher_gui" type="joint_state_publisher_gui">
  184.     <remap from="/joint_states" to="/gui_commands"/>
  185.   </node>
  186.   <node name="rviz" pkg="rviz" type="rviz" args="-d $(find {PKG_NAME})/launch/g1.rviz" />
  187. </launch>""")
  188.     print("-------------------------------------------------------")
  189.     print("1. python3 scripts/rviz_gazebo.py")
  190.     print("2. cd ~/g1_ws && catkin_make && source devel/setup.bash")
  191.     print("3. roslaunch g1_description 1_gazebo.launch")
  192.     print("4. roslaunch g1_description 2_rviz.launch")
  193.     print("-------------------------------------------------------")
  194. if __name__ == "__main__":
  195.     generate_files()
复制代码
第三步:运行配置脚本
  1. python3 ~/g1_ws/src/g1_description/scripts/rviz_gazebo.py
  2. # python3 ~/g1_ws/src/g1_description/scripts/rviz_gazebo_fixed.py
复制代码
第四步:编译并运行
  1. cd ~/g1_ws
  2. catkin_make
  3. source devel/setup.bash
复制代码
终端 1 (仿真)
  1. roslaunch g1_description 1_gazebo.launch
复制代码
终端 2 (控制)
  1. roslaunch g1_description 2_rviz.launch
复制代码
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

2026-1-19 01:39:15

举报

2026-1-22 21:30:49

举报

喜欢鼓捣这些软件,现在用得少,谢谢分享!
2026-2-7 04:08:23

举报

喜欢鼓捣这些软件,现在用得少,谢谢分享!
2026-2-8 08:32:39

举报

懂技术并乐意极积无私分享的人越来越少。珍惜
2026-2-8 08:35:17

举报

2026-2-9 09:45:38

举报

喜欢鼓捣这些软件,现在用得少,谢谢分享!
2026-2-9 22:11:01

举报

2026-2-11 04:42:54

举报

12下一页
您需要登录后才可以回帖 登录 | 立即注册