当前位置:   article > 正文

CoppeliaSim用户手册中文翻译版(二)_coppeliasim ik模块

coppeliasim ik模块
CoppeliaSim 用户手册

文章目录

在这里插入图片描述

5. 计算模块

  CoppeliaSim提供强大的计算功能或计算模块,这些功能或计算模块未直接封装在对象中(例如,接近传感器或视觉传感器),而是对一个或多个对象进行操作。

计算模块包括:

  • 碰撞检测模块:该模块允许跟踪,记录和可视化任何可碰撞实体之间可能发生的碰撞。
  • 最小距离计算模块:该模块允许跟踪,记录和可视化任何可测量实体之间的最小距离。
  • 反向运动学计算模块:该模块允许以非常有效的方式解决任何类型的反向或正向运动学问题。
  • 动态模块:该模块允许动态模拟对象或模型以实现对象交互(例如,碰撞响应、对象抓取等)。

  其他类似功能也可以通过插件提供,如路径/运动计划插件所提供的实例。
  某些计算模块允许注册用户定义的计算对象。计算对象不同于场景对象,但是通过对其进行操作而间接链接到它们。这意味着计算对象本身不能存在:

  • 碰撞检测对象(或碰撞对象)依赖于可碰撞对象。
  • 最小距离计算对象(或距离对象)依赖于可测量的对象。
  • 逆运动学计算对象(或IK组)主要依赖于虚拟对象和运动链,其中关节对象起着核心作用。

  如果在场景对象的复制/粘贴操作期间维护了给定计算对象的完整性,则该计算对象也将被自动复制。例如,如果碰撞对象A由可碰撞对象B和可碰撞对象C定义(即,在对象B和对象C之间执行碰撞检测),并且如果对象B和C同时复制(在同一复制粘贴中)操作),则碰撞对象A也将被复制。这代表了一项强大的功能,它不仅可以复制对象或模型,还可以复制其所有相关的计算对象(包括所有相关的脚本),从而可以保留对象或模型的全部功能和行为。

5.1 计算模块属性对话框

  计算模块属性对话框位于[菜单栏->工具->计算模块属性]。 您还可以通过单击其工具栏按钮来打开对话框:

在这里插入图片描述
计算模块属性对话框显示与计算模块相关的属性。 该对话框分为4部分:

  • 碰撞检测:与碰撞检测有关的属性。
  • 距离计算:与最小距离计算有关的属性。
  • 逆运动学:与逆运动学有关的属性。
  • 动力学:与动力学相关的属性。

  对话框上部的4个按钮允许选择想要显示的属性类型:
在这里插入图片描述

5.2 碰撞检测

  CoppeliaSim可以非常灵活的方式检测两个可碰撞实体之间的碰撞。该计算是精确的干扰计算。碰撞检测模块将仅检测碰撞。但是它不会直接对它们做出反应(有关碰撞响应,请参考动力学模块)。下图说明了冲突检测功能:
在这里插入图片描述

  碰撞检测模块允许注册作为可碰撞实体对的碰撞对象。在模拟过程中,每个已注册的碰撞对象的碰撞状态可以用不同的颜色显示,或记录在图形对象中。有关如何记录碰撞状态的更多信息,请参考图形和图形数据流类型。如果已注册的碰撞对象在复制粘贴操作中同时复制了它们的两个组成实体,则会自动复制它们的碰撞检测模块。
  碰撞检测模块中使用的碰撞检测例程也可以通过Coppelia几何例程作为独立例程使用。
  碰撞检测对话框是计算模块属性对话框的一部分,位于[菜单栏->工具->计算模块属性]。您还可以通过单击其工具栏按钮来打开对话框:
在这里插入图片描述

  在计算模块属性对话框中,单击“碰撞检测”按钮以显示碰撞检测对话框:
在这里插入图片描述

  • 启用所有碰撞检测:允许启用或禁用所有已注册碰撞对象的碰撞检测。
  • 添加新的碰撞对象:允许指定两个实体进行碰撞检测。该按钮下方的列表显示了所有可以通过双击重命名的已注册碰撞对象。可以选择列表中的各个碰撞对象,然后在下面显示相关属性(请参见下文)。
  • 调整碰撞颜色:允许调整将碰撞实体显示的颜色。另请参见碰撞更改碰撞器颜色和碰撞更改碰撞颜色项。
  • 显式处理:指示是否应显式处理所选的碰撞对象。如果选中此选项,则在调用sim.handleCollision(sim.handle_all_except_explicit)时将不处理该碰撞对象的碰撞检测,但仅在调用sim.handleCollision(sim.handle_all)或sim.handleCollision(collisionObjectHandle)时才进行。如果用户希望在子脚本中而不是在主脚本中处理该碰撞对象的碰撞检测(如果未选中,则该碰撞对象的碰撞检测将被处理两次,一次sim.handleCollision(sim.handle_all_except_explicit )在主脚本中被调用,并且在子脚本中调用sim.handleCollision(collisionObjectHandle)时被调用一次。另请参阅有关显式和非显式调用的部分。
  • 碰撞更改碰撞器/碰撞体颜色:启用或禁用碰撞器/碰撞体实体在碰撞期间的颜色更改。
  • 比较科尔。轮廓(仅限形状):如果启用,则将对形状-形状碰撞执行详尽的碰撞检测:所有相交点(即碰撞轮廓)均被计算并可视化。但是,这比简单的碰撞检测需要更多的计算时间。
  • 调整轮廓颜色:可以调整碰撞轮廓线的颜色(请参见上面的“计算碰撞轮廓”项)。
  • 轮廓宽度:碰撞轮廓线的宽度(请参见上面的“计算碰撞轮廓”项)。

5.3 距离计算

  CoppeliaSim可以非常灵活的方式测量两个可测量实体之间的最小距离。该计算是精确的最小距离计算。距离计算模块将仅测量距离;但是它不会直接对他们做出反应。下图说明了距离计算功能:
在这里插入图片描述

  距离计算模块允许注册作为可测量实体对的距离对象。在仿真过程中,每个可视距离对象的最小距离段可以可视化或记录在图形对象中。有关如何记录距离对象的更多信息,请参考图和图数据流类型。如果在复制粘贴操作中同时复制了两个距离实体,则已注册的距离对象将自动复制。
  最小距离计算模块中使用的距离计算例程也可以通过Coppelia几何例程作为独立例程使用。
  距离计算对话框是计算模块属性对话框的一部分,该对话框位于[菜单栏->工具->计算模块属性]。您还可以通过单击其工具栏按钮来打开对话框:
在这里插入图片描述

  在计算模块属性对话框中,单击距离计算按钮以显示距离计算对话框:
在这里插入图片描述

  • 启用所有距离计算:允许启用或禁用所有已注册距离对象的距离计算。
  • 添加新的距离对象:允许指定两个实体进行距离计算。按钮下方的列表显示所有可注册的距离对象,可通过双击重命名。可以选择列表中的各个距离对象,然后在下面显示相关属性(请参见下文)。
  • 显式处理:指示是否应显式处理所选距离对象。如果选中此选项,则在调用sim.handleDistance(sim.handle_all_except_explicit)时将不处理该距离对象的距离计算,而仅在调用sim.handleDistance(sim.handle_all)或sim.handleDistance(distanceObjectHandle)时才进行。如果用户希望在子脚本中而不是在主脚本中处理该距离对象的距离测量(如果未选中,则两次处理该距离对象的距离计算,一次sim.handleDistance(sim.handle_all_except_explicit )在主脚本中被调用,并且在子脚本中调用sim.handleDistance(distanceObjectHandle)时被调用一次。另请参阅有关显式和非显式调用的部分。
    使用阈值:当距离对实体相距很远并且不需要计算距离时,可以指定距离阈值来加快计算速度。
  • 显示距离段:如果启用,则该距离对象的最小距离段在场景中变得可见。
  • 线段宽度:距离线段的宽度。
  • 线段颜色:允许调整距离线段的颜色。

5.4 逆运动学

  CoppeliaSim的逆运动学(IK)计算模块非常强大且灵活。它允许以逆运动学模式(IK模式)或正向运动学模式(FK模式)处理几乎任何类型的机构。

  IK问题可以看作是找到与给定身体元素(通常是末端执行器)的某些特定位置和/或方向相对应的关节值之一。更一般而言,它是从任务空间坐标到关节空间坐标的转换。例如,对于串行操纵器,问题将是在给定末端执行器的位置(和/或方向)的情况下找到操纵器中所有关节的值。反问题-在给定关节值的情况下找到末端执行器的位置-被称为FK问题,通常被认为比IK更容易完成。在处理开放式运动学链时,这确实是正确的,但不适用于一般类型的机械配置,例如以下所示:
在这里插入图片描述

在CoppeliaSim中,可以通过两种不同的独立方法使用运动学功能:

  • 通过运动学插件API函数:此方法允许专门通过API函数设置复杂的运动学任务。 这是推荐的方法,因为它可以将运动学功能与仿真模型可能涉及的其他方面(例如动力学等)很好地隔离开来。
  • 通过内置的运动学功能(通过GUI):通过设置和使用场景对象,通过GUI创建IK元素和IK组来准备运动学任务。 仅对包含几十个关节的复杂机构(包括闭环,并行机构等)建议使用此方法。

独立于所选的方法,CoppeliaSim的运动学功能使用完全相同的概念和术语来设置运动学任务:

  • IK组和IK元素的基础知识
  • 解决IK和FK的任何类型的机制

  最后,确保在文件夹scenes / ik_fk_simple_examples中查看与IK和FK相关的各种简单示例场景。

5.4.1 IK组和IK元素的基础知识

  CoppeliaSim使用IK组和IK元素解决逆向和正向运动学任务。 重要的是要了解如何解决IK任务,以便充分利用模块的功能。 确保在文件夹scenes / ik_fk_simple_examples中查看与IK和FK相关的各种简单示例场景。

  IK任务由IK组定义,该IK组包含一个或多个IK元素:

  • IK组:IK组将一个或多个IK元素分组。 要求解简单运动学链的运动学,需要一个包含一个IK元素的IK组。 IK组定义一个或多个IK元素的整体求解属性(例如,使用哪种求解算法等)。
  • IK元素:IK元素指定简单的运动链。 一个IK元素代表一个运动学链。 运动链是包含至少一个关节对象的链接。 简而言之,IK元素由以下组成:
    • 基础 它代表了运动链的开始。
    • 链接(关节以外的任何类型的对象)。 但是,不在IK模式下的关节也被视为链接(在这种情况下,它们表现为刚性关节(固定值))。
    • 关节。 但是,不在IK模式下的关节不被视为关节,而被视为链接(请参见上文)。 另请参见关节属性。
    • 尖端。 尖端始终是虚拟对象,并且是所考虑的运动链中的最后一个对象(当从基部到尖端时)。 尖端假人应链接到目标假人(请参见下文),并且链接应为IK(尖端-目标)链接类型。 另请参阅虚拟属性。
    • 目标。 目标始终是虚拟对象,并表示尖端在仿真过程中应采用(或遵循)的位置和/或方向。 目标虚拟对象应链接到尖端虚拟对象(请参见上文),并且链接应为IK,尖端-目标链接类型。 另请参阅虚拟属性。

   下图显示了为IK元素指定的两条运动链。 IK元素以相似的方式感知两条链(第二个示例的第一个关节被IK元素忽略):
在这里插入图片描述

  IK元素的目标(即IK元素的分辨率)是通过计算运动链的适当关节值,使目标紧跟尖端(即尖端和目标重叠,并具有一定约束):
在这里插入图片描述

  在上面的示例中(为简单起见,采用2D),我们可以为尖端-目标对指定各种约束,例如:

  • X位置约束:尖端将仅在X轴上跟随目标,并且运动链对于此任务将显得多余,因为链本身具有3个自由度(DoF)。
  • X/Y位置约束:尖端将仅在位置上跟随目标,并且运动链对于此任务仍将显得多余。
  • X/Y位置+Θ方向约束:尖端将在位置和方向上跟随目标,并且运动链对于此任务将不再显示为冗余。

  请注意,即使对于最基本的IK任务,IK元素也是通过包含的IK组的分辨率来求解的。
  以相同的方式处理两个单独的运动链,但是这一次需要两个IK组(并且每个IK组都应该为每个运动链包含一个IK元素)。 两个IK组的解算顺序并不重要:
在这里插入图片描述

  在上面的示例中,应该将target2附加到第一个运动链的可移动部分,然后求解顺序变得很重要,并且应该首先求解IK group1(解决结果将取代target2,如下图所示):
在这里插入图片描述

  当一个IK元素建立在另一个IK元素之上而没有共享任何共同关节时,可能会出现类似情况,如下图所示:第一个运动学链用黑色表示,第二个运动学链用浅蓝色表示。 紫色表示的Base2是两条链之间的公共对象。 解决IK element2不会替换紫色链接,但是解决IK element1会替换紫色链接。 因此,与上述情况一样,必须在IK group2之前解决IK group1(解决顺序很重要):
在这里插入图片描述

  当两个或多个运动链共享共同的关节时,会出现更困难的情况。 在这种情况下,顺序求解在大多数情况下不起作用(在以下示例中,两个IK元素倾向于将公共关节旋转到相反的方向),因此需要同时求解方法。 要同时求解多个IK元素,只需将它们分组为一个公共IK组。 下图说明了这种情况:
在这里插入图片描述

5.4.2 解决IK和FK的任何类型的机制

  通过使用IK组和IK元素解决了机构的反向运动学(IK)或正向运动学(FK)(请参阅关于IK组和IK元素的基础知识部分)。 牢记以下检查点,以便成功设置IK或FK计算:

  • 通过提供底座和尖端来指定各个运动链。
  • 指定要遵循的目标(只需将尖端虚拟对象链接到目标虚拟对象(请参阅虚拟对象属性以获取更多详细信息))。
  • 如果它们共享公共关节,则将IK元素分组在单个IK组中。
  • 排序各个IK组以获得所需的行为。
  • 验证运动链中的关节具有启用或禁用的正确属性。 有关更多详细信息,请参见关节属性。
  • 确认各个IK元素没有受到过度约束(X,Y,Z,Alpha-Beta,Gamma)。 如果无法避免,请选择阻尼分辨率方法。

  最后一点很重要,必须理解:已打开所有约束的运动链尖端将在x,y,z方向上遵循其关联的目标,同时尝试保持与目标相同的方向。 但是,只有在运动链具有至少6个非冗余自由度(DoF)的情况下,此方法才有效。 提示应始终受到适当的约束(即,永远不要指示超出该机制中的DoF的约束)。 位置约束在大多数情况下是相对于基座方向指定的,如下图所示:
在这里插入图片描述

  但是,有时无法正确指定尖端的约束,在这种情况下,IK组的计算方法应为具有适当选择的阻尼系数的阻尼方法(例如DLS方法)。 当无法达到目标(无法达到或接近单一配置)时,也应选择阻尼分辨率方法。 阻尼可以使计算更稳定,但请记住,阻尼始终会降低IK计算的速度(需要进行多次迭代才能将笔尖放置到位)。

  启用Alpha-Beta约束将使笔尖的z轴方向与目标的z轴方向匹配,如果关闭了Gamma约束,则保持绕z轴的自由旋转。 启用Alpha-Beta约束和Gamma约束时,尖端将尝试采用与其关联目标完全相同的方向。

  有关IK组和IK元素的基础知识部分,介绍了解决简单运动链的IK问题的步骤。 解决简单运动链的FK问题是微不足道的(只需将期望的关节值应用于链中的所有关节即可获得尖端或末端执行器的位置和方向)。 解决封闭机构的IK和FK问题不是那么简单。

解决IK和FK的封闭机构

  在下一节中,将讨论两个一般示例,这些示例应使用户能够理解解决一般类型封闭机制的方法:

  如果发生FK问题,请首先确定要控制的关节(即,驱动机械的关节,活动关节)。 这些关节应从所有运动学计算中排除(选择与逆运动学模式不同的关节模式(请参见关节属性))。 从现在开始,通过运动学计算,这些接头将被认为是刚性的。 然后,确定需要关闭的运动链。 关闭将通过以尖端-目标对的形式的循环闭合约束进行处理,如下图所示:
在这里插入图片描述

  然后,为活动关节设置所需的关节值,并调用逆运动学功能来处理回路闭合约束。 (默认主脚本处理所有未标记为显式处理的IK组)。

  以下示例显示了一些可用于解决复杂运动学问题的附加功能:
在这里插入图片描述

  从图中可以看出,用户需要执行一项IK任务:将尖端放在目标上(或让笔尖跟随目标)。 这可以通过常规方式解决,或者用户可以使用联合依赖功能。 下图说明了这一点:
在这里插入图片描述

  IK的主要任务是达到目标,回路闭合约束负责闭合机构,关节重叠约束负责保持机构的基础重叠(作为一条链)。 关节相关性线性方程的参数必须仔细选择才能达到完美的重叠(例如,如果两个相应的关节(通过重叠约束链接的关节)具有相同的方向,则需要设置方程中的系数 到-1!(因为一个关节是自下而上构建的,而另一个关节是自上而下构建的))。

  大多数时候,有多种方法可以解决一种机构的IK或FK,在实施最复杂的替代方案之前,总是值得考虑各种替代方案!

5.5 逆运动对话框

  运动学逆对话框是计算模块属性对话框的一部分,位于[菜单栏->工具->计算模块属性]。 您还可以通过单击其工具栏按钮来打开对话框:
在这里插入图片描述

  在计算模块属性对话框中,单击“逆运动学”按钮以显示逆运动学对话框:
在这里插入图片描述

  • 启用反向运动学:启用或禁用所有反向运动学计算。
  • 添加新的IK组:添加一个新的空IK组。 IK组可以包含一个或多个IK元素。 IK元素是基本的运动链IK任务,并且IK组可以将它们分组以同时求解。 仅在需要时才使用同时求解(比顺序求解更长的计算时间)。 IK元素始终必须与IK组相关联,并且本身不能存在。 按钮下方的列表显示了将在IK计算期间求解的所有IK组。 需要选择列表中的IK组,以便在对话框的其余部分中可视化其参数。 列表中的顺序很重要(IK组2可能需要IK组1的结果才能正确执行或更快地执行)。 列表旁边的两个按钮允许更改所选IK组的位置。
  • IK组处于活动状态:允许打开和关闭单个IK组。
  • 显式处理:指示是否应显式处理所选的IK组。 如果选中,则在调用sim.handleIkGroup(sim.handle_all_except_explicit)时将不处理该IK组的IK计算,仅在调用sim.handleIkGroup(sim.handle_all)或sim.handleIkGroup(ikGroupHandle)时才进行。 如果用户希望在子脚本中而不是在主脚本中处理该IK组的运动学,这将很有用(如果未选中,则在sim.handleIkGroup(sim.handle_all_except_explicit)一次将处理该IK组的IK计算两次。 在主脚本中被调用,并且在子脚本中被调用sim.handleIkGroup(ikGroupHandle)时被调用一次。 另请参阅有关显式和非显式调用的部分。
  • 计算方法:用于指定IK组分辨率的计算方法。 伪逆是最快的方法,但是当目标和尖端之间的距离太远,运动链受到过度约束或机制接近单一配置或目标无法到达时,伪逆可能会变得不稳定。 DLS较慢但更稳定,因为它是一种阻尼分辨率方法(可以指定阻尼因数(阻尼))。 当伪逆方法可能失败时,这是一个不错的选择。
  • 阻尼:使用阻尼分辨率方法(DLS)时的阻尼系数。 较大的值可导致更稳定的分辨率,但速度慢得多。 适当调整此值很重要。
  • 最高迭代次数:可以指定最大迭代次数。 这是给定IK组的最大计算通过次数,直到达到指定的分辨率精度为止。 阻尼分辨率(DLS)通常比无阻尼分辨率(伪逆)需要更多的迭代。
  • 忽略最高步长:如果选中此属性,则将忽略在关节属性中指定的最大步长。
  • 编辑条件参数:可以为选定的IK组调整条件分辨率参数。 将弹出以下对话框:
  • 在这里插入图片描述
    • 如果…,请执行:这是条件解析部分。 用户可以在下拉列表中选择一个IK组,其IK分辨率结果将决定是否要解决当前IK组。 被认为是成功的IK组计算,其IK元素都在其指定的线性/角度精度内。
    • 在以下情况下恢复:…:如果分辨率不成功(未达到位置和/或方向精度),则允许还原初始IK组配置(联合值)。 结合以上条件求解,用户可以例如组合两种不同的计算方法。 如果机械手的目标可能无法达到或接近奇异点,这将非常有用:第一次解析尝试将尝试使用非阻尼解析方法(伪逆,快速)求解IK组,如果不成功,则尝试 第二次尝试将尝试使用阻尼分辨率方法(较慢的DLS)解决该问题。 用户当然也可以从脚本,插件等中“手动”处理IK分辨率。
  • 编辑IK元素:允许相对于选定的IK组编辑各种IK元素。 单击此按钮将打开“ IK元素”对话框。

5.5.1 IK元素对话框

  IK元素对话框是逆运动学对话框的一部分。 该对话框显示给定IK组的各种IK元素。 在逆运动学对话框中,选择一个IK组,然后单击“编辑IK元素”按钮以打开IK元素对话框:
在这里插入图片描述

  • 添加带有尖端的新IK元素:添加具有默认值的新IK元素(由其尖端坐dummy定义)。 IK组可以包含几个IK元素,显示在列表中。 列表中的IK元素将在IK计算过程中同时求解(同时,因为它们是同一IK组的一部分)。 列表中的顺序并不重要。 在列表中选择一个IK元素将显示其属性和参数。
  • 元素处于活动状态:可以打开和关闭选定的IK元素。 无效的IK元素可能会在仿真期间自动关闭。
  • 基础:IK链的基础对象。 请记住,如果没有另外指定,则约束是相对于基础对象的方向指定的。
  • 目标:尖端dummy应该跟随。 目标不应属于指定的运动链(否则可能无法解析IK元素),但是建议将目标构建在基础之上(以保持干净的场景层次)。 如果所选的尖端dummy未链接到任何其他dummy,或者如果链接类型不是IK、尖端目标,则将显示警告消息,而不是目标dummy名。
  • 约束:在IK元素求解过程中需要遵守的约束。 默认情况下,相对于基础指定位置约束,但是可以选择另一个虚拟对象作为约束参考框架(相对于坐标框架项目)。 过度约束的IK元素将无法正确解决且不稳定。 仔细分析需要哪些约束以及相对于什么参考系,这一点很重要。 但是,有时很难不过度约束IK元素。 在这种情况下,请为IK组选择阻尼分辨率方法(DLS),然后适当调整阻尼系数。 但是请记住,衰减的分辨率较慢。 (X,Y和Z与位置相关(尖端将遵循目标的位置),Alpha-Beta和Gamma与方向相关(尖端将遵循目标的方向))。
  • 线性/角度精度:指定所需的线性/角度精度(如果尖端在目标的线性和角度精度内(考虑指定的约束),则认为IK元素已解决)。
  • 线性/角权重:IK分辨率权重,用于位置或方向分辨率。 用户可以选择为IK元素分辨率优先考虑位置跟随或方向跟随。 这对于冗余机制尤其有用。

5.6 动力学

  CoppeliaSim的动力学模块当前支持四种不同的物理引擎:Bullet物理库,Open Dynamics引擎,Vortex Studio引擎和Newton Dynamics引擎。 在任何时候,用户都可以根据自己的仿真需求自由地从一种引擎快速切换到另一种引擎。 物理引擎支持如此多样化的原因是,物理模拟是一项复杂的任务,可以通过不同程度的精度,速度或支持多种功能来实现:

  • Bullet物理库:一个开源的物理引擎,具有3D碰撞检测,刚体动力学和软体动力学(CoppeliaSim当前不支持该功能)。 它用于游戏和电影的视觉效果。 它通常被认为是游戏物理引擎。
    在这里插入图片描述
  • 开放式动力学引擎(ODE):具有两个主要组件的开源物理引擎:刚体动力学和碰撞检测。 它已用于许多应用程序和游戏中。 它通常被认为是游戏物理引擎。
    在这里插入图片描述
  • Vortex®Studio:一种闭源商业物理学引擎,可产生高保真物理模拟。 Vortex提供了大量物理属性的实际参数(即对应于物理单位),使该引擎既逼真又精确。 Vortex主要用于高性能/高精度工业和研究应用。 CoppeliaSim的Vortex插件基于Vortex Studio,该插件要求每个用户向CM Labs注册以获取免费许可证密钥。
    在这里插入图片描述
  • 牛顿动力学:牛顿动力学是一个跨平台的逼真的物理仿真库。 它实现了确定性求解器,它不是基于传统的LCP或迭代方法,而是分别具有两者的稳定性和速度。 此功能使Newton Dynamics不仅成为游戏的工具,而且还成为任何实时物理模拟的工具。 当前的插件实现是BETA版本。
    在这里插入图片描述

  动态模块允许模拟接近现实世界对象交互的对象之间的交互。 它可以使物体掉落,碰撞,反弹,但也可以使机械手抓住物体,用传送带将零件向前推动,或者使车辆在不平坦的地形上以逼真的方式滚动。 下图说明了动态仿真:
在这里插入图片描述

  与许多其他仿真软件包不同,CoppeliaSim并非纯粹的动力学模拟器。 可以将其看作是一种混合模拟器,它结合了运动学和动力学特性,以便在各种模拟情况下获得最佳性能。 如今,物理引擎仍然依赖于许多近似值,并且相对不精确且运行缓慢,因此在任何可能的情况下,您都应尝试使用运动学代替(例如对于机器人操纵器),并且仅在不可行的情况下依赖动力学(例如机器人操纵器的抓取器)。 如果要模拟的移动机器人不应该与环境发生碰撞或物理交互(无论如何,大多数移动机器人都很少这样做),并且该移动机器人只能在平坦的地面上运行(将绝大多数移动机器人分组) ,然后尝试使用运动学或几何计算来模拟机器人的运动。 结果将更快,更准确。

  动力学模块的某些结果可以由图形对象记录。 有关如何记录动态数据的更多信息,请参考图形和图形数据流类型。

5.6.1 设计动态仿真

  在CoppeliaSim中,将仅动态模拟有限数量的对象。 这些是形状、关节和力传感器,但是这将取决于场景结构和对象属性,是否会动态模拟给定的对象。 在仿真过程中可以轻松识别动态仿真的对象,因为在场景层次结构中,对象名称旁边会出现以下图标:
在这里插入图片描述

  双击场景层次中的图标(仅在仿真过程中)将显示一些与对象的动态行为有关的信息。 动态模拟应该具有但由于某种原因而不能动态模拟的对象将改为显示以下图标:
在这里插入图片描述

静态/非静态,响应/非响应的形状

  根据形状在动态模拟中的行为,可以将其分为4组:
在这里插入图片描述

  在动态仿真期间,静态形状将不受影响(即它们相对于其父对象的位置是固定的),而非静态形状将直接受到重力或其他约束条件的影响(例如,动态启用的关节,请参见下文)。 可响应的形状在动态碰撞期间会相互影响(即,它们会产生相互碰撞的反应,它们会相互反弹)。 下图说明了静态/非静态,可响应/不可响应行为:
在这里插入图片描述

  除非它们各自的碰撞蒙版不重叠,否则两个可响应的形状将始终产生碰撞反应。 可以在“形状动力学”属性对话框中设置静态/非静态,可响应/不可响应的形状属性以及碰撞蒙版。

动态启用的关节/力传感器

  如果非静态形状不受其他限制,则它们会掉落(即受重力影响)。 可以通过将两个形状与可动态启用的关节或可动态启用的力传感器连接在一起来设置形状之间的动态约束。

  • 动态启用的关节是处于力或扭矩模式或以混合方式运行的关节(请参见关节属性),并且具有作为父对象的形状以及恰好必须是非静态形状的一个子对象的形状。 另外,有可能使接头处于闭环构造中。 在这种情况下,关节必须通过虚拟假人链接连接到两个形状(链接类型必须为“动态”,“重叠约束”)。 有关虚拟虚拟链接,请参阅虚拟属性。
  • 动态启用的力传感器是具有形状作为父对象和正好一个子对象(必须是非静态形状)的力传感器。 另外,可以将力传感器包括在回路闭合构造中。 在这种情况下,力传感器必须通过虚拟的虚拟链接连接到两个形状(链接类型必须为“动态”,“重叠约束”)。 有关虚拟虚拟链接,请参阅虚拟属性。

  下图显示了将关节或力传感器视为动态启用的有效情况(假设关节/力传感器和两个形状位于动态模拟的模型中,这是默认情况):
在这里插入图片描述

  请遵循上述准则,以获取动态启用的关节或力传感器,这一点非常重要。 在模拟过程中,如果CoppeliaSim发现未动态启用的力传感器,它将在场景层次视图中其名称旁边显示一个小的警告图标。 在力/扭矩模式下的关节或应该以混合方式操作且没有动态启用的关节也会发生同样的情况。

以下是无法动态启用关节的一些示例情况:

  • 关节未处于力或扭矩模式,并且关节未以混合方式运行。
  • 关节的父级不是形状。
  • 关节有多个子对象。
  • 关节直接连接到另一个关节。
  • 关节(或其连接的两个形状之一)位于未动态模拟的模型(层次树)中(请参阅模型对话框以了解有关如何禁用特定模型的动态模拟的更多信息)。

以下是一些无法动态启用力传感器的示例情况:

  • 力传感器的父级不是形状。
  • 力传感器具有多个子对象。
  • 力传感器(或它连接的两个形状之一)位于未动态模拟的模型(层次树)中(请参阅模型对话框以了解有关如何禁用特定模型的动态模拟的更多信息)。

刚性化合物

  未通过动态启用的关节或力传感器链接的两个非静态形状将在动态仿真过程中彼此独立移动。 如果希望两个或多个形状表现为一个形状,则必须将它们分组(菜单栏->编辑->分组/合并->分组选定的形状)。 确保适当调整最终形状的惯性矩。 确保您还仔细阅读了有关纯形状的部分:
在这里插入图片描述

  复合形状将表现为刚性实体,并且还具有相同的动态特性(例如摩擦系数)。 有时需要具有动态特性的刚性实体。 在这种情况下,必须通过力传感器将形状刚性连接。 这种结构的刚度不如复合形状强,但在大多数情况下效果良好:
在这里插入图片描述

  通过使两个形状通过两个链接的虚拟对象链接起来,可以获得相似的结果。 两个虚拟实体的链接类型必须为“动态”,“重叠约束”(请参阅虚拟属性)。 在动态仿真期间,两个链接的虚拟对象将尝试采用相同的位置和方向。 下图显示了用于动态仿真的有效刚性回路闭合约束:
在这里插入图片描述

设计注意事项1

  使用纯形状。 只要有可能,请尝试将纯形状用作可响应形状:在动态仿真过程中,纯形状会更加稳定且速度更快。 与其使用复杂的机器人模型三角形网格作为可响应的形状,或者使用稍微好一些的凸形表示,不如尝试用几种纯形状近似三角形网格,然后将它们分组[菜单栏->编辑->分组/合并- >对选定的形状进行分组](请注意,如果您合并纯形状而不是对它们进行分组,则生成的形状将不再是纯形状)。 下图说明了机器人隐藏的可响应纯形状:
在这里插入图片描述

  从复杂的非纯形状中提取纯形状的一种简便方法是,进入复杂形状的三角形编辑模式,然后选择感兴趣的区域并提取矩形,球形或圆柱形的纯形状。 请参阅三角形编辑模式下的“提取长方体”,“提取圆柱体”和“提取球体”按钮。 确保您还阅读了有关导入和准备刚体的教程。 使用动态内容可视化工具栏按钮可视化和验证场景的动态内容也是一种好习惯(请参阅设计注意事项3)。

  当物体可以碰撞但不能持续碰撞,或者在机械/机器人的稳定性中不发挥重要作用时,则不一定非要使用纯形状,凸形也可以是可行的替代方案;

设计注意事项2

  使用凸形状代替随机形状。 只要有可能,请尝试使用纯形状作为可响应形状(请参阅设计注意事项1)。 但是,这并不总是那么容易做到,也不是实用的(例如,想像一个圆环)。 在这种情况下,您可以生成凸形状或凸分解形状(即仅包含凸网格的简单/复合形状)。 凸形状比随机形状执行得更快并且更稳定(但是它们仍然不如纯形状快和稳定!)。

  选择要简化为凸形的形状,然后选择[菜单栏->编辑->将所选形状变形为凸形…]。 另请参阅项目[菜单栏->添加->选定形状的凸分解…]。 下图说明了凸分解:
在这里插入图片描述

设计注意事项3

  仔细检查场景的动态内容。 有时,要在模拟中发挥积极作用的隐藏形状可能会有些混乱。 动态启用的形状,关节或力传感器通常是这种情况:实际上,它们大多数时候都隐藏在观察者的眼中。 但是,始终可以在仿真期间通过激活动态内容可视化按钮来检查动态内容:
在这里插入图片描述

  动态对象将根据其功能或设置以各种颜色显示。 请参考下图:
在这里插入图片描述

  注意上图中非纯形状的外观:它们的三角形网格轮廓以绿色或灰色表示。 应该不惜一切代价避免使用动态模拟的非纯形状,因为它们需要更长的计算时间,并且呈现出不太稳定的行为(但是,凸形比非凸形更快,更稳定)。 另请参考上面的设计注意事项1。

设计注意事项4

  使用简单的层次结构。 建立要动态模拟的模型时,请将所有非动态对象附加到动态对象(非静态形状和动态启用的关节)。 上面的轮式机器人模型如下图所示:
在这里插入图片描述

设计注意事项5

  仔细选择模型基础对象。 在构建动态模型时,实际上在构建静态模型时,始终要谨慎考虑模型将扮演的角色。 是否可以单独使用? 它可以建立在另一个模型或对象之上吗? 还是可以接受其他要在其之上构建的模型或对象? 考虑以下示例:

  您已经创建了移动机器人的模型。 您还创建了夹具的模型。 显然,您希望能够轻松地将抓手模型附加到机器人模型的顶部。 相反,将永远都没有意义(将机器人模型附加在抓具模型之上)。 您可能还想单独使用机器人模型和机械手模型。 以下是可能的模型定义的场景层次结构视图:
在这里插入图片描述

  注意“机器人”和“夹具”形状对象旁边的模型图标。 这表明在它们之上构建的所有对象都是同一模型定义的一部分。 两种模型各自运行良好。 但是,如果尝试将夹持器连接到机器人顶部(通过选择夹持器模型,然后选择机器人模型,然后单击[菜单栏->编辑->使上一个选定的对象成为父对象]),则夹持器将不会 在仿真过程中保持固定在机器人上。 以下是上述两个模型的场景层次结构,其中机械手已连接到机械手:
在这里插入图片描述

  请注意,在上述场景层次中,纯形状“抓取器”是如何在纯形状“机器人”之上构建的。 并记住,如果不通过力传感器的连接加以限制,非静态形状将会掉落……确切地说,我们需要在“夹具”和“机器人”之间安装一个力传感器,以使两者之间具有牢固的连接!

  机器人的正确模型定义必须包括抓具的连接点(或其中几个),如下图所示:
在这里插入图片描述

  固定点是一个简单的力传感器。 将抓具模型与上述机器人模型组装在一起,将使抓具相对于机器人保持固定:
在这里插入图片描述

  为了进一步简化组装过程,您可以自定义组装工具栏按钮的行为,以便以正确的相对位置/方向自动将抓手放置在附着点上。 有关更多详细信息,请参见有关模型的部分以及对象公共属性中的对话框项“组装”。

设计注意事项6

  使用合理的尺寸。 形状太长,太细或太小可能会表现出奇怪的现象(抖动、跳跃)。 如果可能,请尝试将尺寸保持在3厘米以上。 否则,您可以在动态引擎常规属性对话框中调整内部缩放参数。

设计注意事项7

  保持质量相似且不要太轻。 使用动态启用的关节或动态启用的力传感器链接两个形状时,请确保两个形状的质量没有太大差异(m1 <10 * m2和m2 <10 * m1),否则,关节或力传感器可能会非常柔软 摇摆不定,并且存在较大的位置/原点误差(但是,有时也可以将这种效果用作自然阻尼)。 此外,应避免质量过低的形状,因为它们无法将很大的力施加到其他形状上(即使由强力执行器推动)。

设计注意事项8

  保持主要转动惯量较大。 尝试使主要的惯性矩/质量(*请参见“形状动力学属性”对话框)相对较大,否则机械链条可能难以控制和/或表现出奇怪的行为。

设计注意事项9

  将动态形状分配给第9层。将所有应该隐藏的动态形状分配给第9层(请参阅对象的公共属性):在CoppeliaSim中,默认情况下,除第9-16层之外的所有层都是可见的。 编辑或测试场景时,您可以通过临时启用第9层(另请参阅层选择对话框)来快速可视化场景中所有隐藏的形状。

设计注意事项10

  两个动态项目之间绝对不能有静态形状。 静态形状将中断动态链的逻辑行为:
在这里插入图片描述

设计注意事项11

  切勿在动态项目的顶部放置静态可响应形状。 静态意味着形状的轨迹不会受到任何碰撞的影响,但是如果它同时是可响应的,则意味着它本身可以通过碰撞影响其他形状的轨迹。 仿真结果将是不可预测的。

5.6.2 一般动力学特性

  常规动力学属性对话框是计算模块属性对话框的一部分,该对话框位于[菜单栏->工具->计算模块属性]。 您还可以通过单击其工具栏按钮来打开对话框:
在这里插入图片描述

  在计算模块属性对话框中,单击“动力学”按钮以显示常规动力学属性:
在这里插入图片描述

  • 启用动态:允许启用/禁用整个场景的动态计算。
  • 要使用的引擎:要使用的动力学引擎。 Bullet代表Bullet物理库,ODE代表Open Dynamics引擎,Vortex代表Vortex Studio引擎,Newton代表Newton Dynamics引擎。 每个引擎都有其特定的优点和缺点。 在一个引擎上运行良好的模拟并不总是意味着在其他引擎上也运行良好。 每个引擎都有特定的参数,这些参数可以全局设置(请参阅下文中的“调整引擎参数”),也可以在局部设置(在与接头有关的材料属性或动力学引擎属性对话框中)。 确保阅读相应的引擎文档,以实现最佳参数设置。
  • 显示接触点:允许显示可响应形状之间的接触点。
  • 调整引擎参数:打开动态引擎常规属性对话框,该对话框允许调整引擎特定的全局参数。
  • 重力:施加于所有非静态形状(例如重力)的恒定力的幅度和方向。
5.6.2.1 动态引擎常规属性

  动态引擎常规属性对话框是常规动力学属性对话框的一部分。 该对话框显示引擎配置和各种引擎特定的全局属性。 在常规动力学属性对话框中,单击调整引擎参数按钮以打开以下对话框:
在这里插入图片描述

  每个引擎都有特定的参数,可以在全局(请参阅下文)或局部(在与接头有关的材料属性或动力学引擎属性中)设置特定参数。

  • 配置:允许在动态引擎的预定义或自定义配置之间快速切换。 但是,强烈建议保留默认配置以避免兼容性问题(例如,将应该以不同的配置设置运行的模型组合在一起永远不会产生良好的结果)。

Bullet属性

与Bullet物理库相关的属性。 请确保还参考Bullet用户手册以获取详细信息。

  • 项目符号时间步长:指定动力学计算所需的时间步长。 强烈建议将时间步长保持为5ms(默认值)。 结合50ms的仿真时间步长,每遍模拟将得出10个动力学计算步骤。
  • 约束求解器类型:要使用的求解器类型。
  • 约束求解迭代:指定将用于求解约束的迭代次数(较大的值通常会导致速度较慢但更精确的模拟(通常))。
  • 内部缩放:指定如何在内部处理尺寸。 如果动态场景是由较小的形状(❤️ cm)或很大的形状组成的,则可以通过调整比例因子来提高仿真稳定性。 对于子弹引擎而言尤其如此。 启用“完全缩放”时,将缩放与尺寸相关的值以及与质量相关的值,否则,将仅缩放与尺寸相关的值。
  • 碰撞余量缩放:指定如何相对于内部缩放参数来缩放碰撞余量。 应用以下关系:新的碰撞容限=项目符号碰撞容限内部缩放比例碰撞容限缩放比例。 可以基于形状替代此设置(请参见材料属性)。

ODE属性

与Open Dynamics Engine相关的属性。 请确保还参考ODE用户手册以了解详细信息。

  • ODE时间步长:指定动力学计算所需的时间步长。 强烈建议将时间步长保持为5ms(默认值)。 结合50ms的仿真时间步长,每遍模拟将得出10个动力学计算步骤。
  • 使用’quickStep’:选择后,将使用快速迭代求解方法。 QuickStep迭代属性越大,计算(通常)就越精确。 如果未选择quickStep方法,则对于小型系统,计算可以更精确,更快。 但是,较大的系统可能会非常缓慢,不稳定,并可能导致突然崩溃!
  • 内部缩放:指定如何在内部处理尺寸。 如果动态场景是由较小的形状(❤️ cm)或很大的形状组成的,则可以通过调整比例因子来提高仿真稳定性。 启用“完全缩放”时,将缩放与尺寸相关的值以及与质量相关的值,否则,将仅缩放与尺寸相关的值。
  • 全局ERP:全局减少错误参数,有关更多详细信息,请参阅ODE文档。
  • 全局CFM:全局约束力混合,有关更多详细信息,请参考ODE文档。

Vortex属性

与Vortex Studio引擎相关的属性。 确保还参考Vortex用户手册以了解详细信息。

  • 涡旋时间步长:指定动力学计算所需的时间步长。 强烈建议将时间步长保持为5ms(默认值)。 结合50ms的仿真时间步长,每遍模拟将得出10个动力学计算步骤。
  • 接触公差:碰撞检测过程中考虑的ε距离。
  • 自动睡眠:如果启用,则不活动的刚体将被禁用,直到事件再次将其唤醒。
  • 多线程:启用用于动力学和碰撞过程的多线程算法。
  • 约束属性:为每个约束方程式提供阈值,以用于约束一致性,阻尼和粘度。
  • 线性顺应性:基于线性位置的方程(如弹簧)的阈值顺应性(1 /刚度)。
  • 线性阻尼:基于线性位置的方程式(例如弹簧)的阈值阻尼。
  • 线性动力学损失:基于线性速度的方程式(例如摩擦力)的阈值粘度。
  • 角顺应性:基于方向的方程的阈值顺应性(1 /刚度)。
  • 角阻尼:基于方向方程的阈值阻尼。
  • 角动力损失:基于取向速度方程的阈值粘度。

Newton属性

与牛顿动力学引擎相关的属性。 确保还参考牛顿用户手册以获取详细信息。

  • 牛顿时间步长:指定动力学计算所需的时间步长。 强烈建议将时间步长保持为5ms(默认值)。 结合50ms的仿真时间步长,每遍模拟将得出10个动力学计算步骤。
  • 约束求解迭代:指定将用于求解约束的迭代次数(较大的值通常会导致速度较慢但更精确的模拟(通常))。
  • 多线程:启用用于动力学和碰撞过程的多线程算法。
  • 精确求解器:如果启用,将使用更精确的求解器,但是可能会导致不稳定。
  • 关节精度高:如果启用,将使用更精确的关节求解器,但是可能会导致不稳定。
  • 接触合并公差:距离阈值,在该距离阈值处,闭合的接触点将合并为一个接触点。

Votex附加信息

  使用引擎约束属性可使所有约束更柔和,从而有助于避免数值不稳定。 对于涉及非常大质量的模拟,可能必须减少约束松弛。 这些参数是全局参数,会影响所有约束和碰撞接触。 对于单个约束松弛,您可以直接参考约束参数,其中每个约束方程式可以独立地松弛。 对于因碰撞而引起的不稳定性,可以使用蒙皮厚度来平滑法向响应或增加滑移参数,以增加接触摩擦的粘度。 对于轻质刚体的链条和承受较大拉力的约束(例如,手动机构抓住比手指大几个数量级的物体),使用角速度阻尼将有助于提高仿真的鲁棒性。

6. 在CoppliaSim及其周围编写代码

  CoppeliaSim是高度可定制的模拟器:模拟的各个方面都可以定制。此外,模拟器本身可以进行定制和裁剪,以使其性能完全符合要求。这可以通过精心设计的应用程序编程接口(API)来实现。支持六种不同的编程或编码方法,每种方法都具有相对于其他方法的特定优点(显然还有缺点),但是全部六种方法是相互兼容的(即可以同时使用,甚至可以手拉手使用)。模型、场景或模拟器本身的控制实体可以位于内部:

  • 嵌入式脚本(即通过脚本自定义仿真(即场景或模型)):这种方法(编写Lua脚本)非常容易且灵活,保证与所有其他默认CoppeliaSim安装兼容(只要不使用自定义Lua命令或与分布式插件一起使用)。此方法允许自定义特定的仿真,仿真场景,并在一定程度上定制仿真器本身。这是最简单,最常用的编程方法。
  • 插件或沙箱脚本:此方法(包含编写Lua脚本)允许快速自定义模拟器本身。加载项(或沙盒脚本)可以自动启动并在后台运行,也可以被称为函数(例如在编写导入器/导出器时很方便)。附加组件不应特定于某个特定的仿真或模型,而应提供更通用的,与仿真器绑定的功能。
  • 插件(即通过插件自定义模拟器和/或仿真):该方法主要包括为CoppeliaSim编写插件。通常,插件仅用于通过自定义的Lua命令提供仿真,因此与第一种方法结合使用。有时,插件用于为CoppeliaSim提供特殊功能,这些功能需要快速的计算能力(脚本在大多数情况下比编译语言要慢),与硬件设备的特定接口(例如,真实的机器人)或特殊的通信接口与外界。
  • 远程API客户端(即,通过远程API客户端应用程序定制模拟器和/或仿真):此方法允许外部应用程序(例如位于机器人,另一台机器等上)以非常简单的方式连接到CoppeliaSim ,使用远程API命令。
  • ROS节点(即通过ROS节点自定义模拟器和/或仿真):此方法允许外部应用程序(例如位于机器人,另一台机器等上)通过ROS(机器人操作系统)连接到CoppeliaSim。
  • BlueZero节点(即通过BlueZero节点自定义模拟器和/或仿真):此方法允许外部应用程序(例如位于机器人,另一台机器等上)通过BlueZero连接到CoppeliaSim。

  外部控制器教程中还讨论了上述6种方法。下表详细描述了每种方法的各自优点和缺点:
在这里插入图片描述

  下图说明了CoppeliaSim及其周围的各种自定义可能性:
在这里插入图片描述

以下简要描述了上图中指示的各种通信或消息传递机制:

  • 从主客户端应用程序或常规API的插件进行的C/C++ API调用。 如果该语言提供了调用C函数的机制(例如,对于Java,请参见Java本机接口(JNI)),则可以源自非C/C++应用程序。
  • 子脚本的有序调用/执行。 由主脚本中的sim.handleChildScripts启动。
  • Lua API从主脚本、子脚本或自定义脚本调用到常规API。 除了调用回调插件的自定义Lua函数外,所有调用都定向到CoppeliaSim引擎(请参阅下一项)。
  • 从模拟器到插件的回调调用。 回调调用源自对自定义Lua函数的Lua脚本调用(请参见上一项)。
  • 从模拟器到插件的事件回调调用。 请参阅sim_message_eventcallback_-type的消息。
  • 来自外部应用程序,机械手,远程PC等的旧版远程API调用。
  • CoppeliaSim与外部应用程序、机器人、远程PC等之间的ROS数据交换。
  • 与外部应用程序之间的套接字、管道、串行端口等连接。
  • Lua API从加载项或沙箱脚本调用到常规API。 除了调用回调插件的自定义Lua函数外,所有调用都定向到CoppeliaSim引擎。
  • 从CoppeliaSim引擎到自定义脚本的执行调用。
  • CoppeliaSim与外部应用程序、机器人、远程PC等之间的BlueZero数据交换。
  • 来自外部应用程序、机械手、远程PC等的基于B0的远程API调用。

6.1 嵌入式脚本

  CoppeliaSim是一个高度可定制的模拟器:几乎每个模拟步骤都是用户定义的。 通过集成的脚本解释器可以实现这种灵活性。 脚本语言是Lua,它是一种扩展编程语言,旨在支持常规过程编程。 有关Lua的更多信息,请参阅Lua速成课程部分和在线文档。 默认情况下,CoppeliaSim使用正式的和原始的Lua,但是如果您愿意,可以通过将system/usrset.txt中的变量useExternalLuaLibrary设置为true来告诉CoppeliaSim使用另一种Lua。 在这种情况下,所有Lua调用都通过simLua库处理,该库本身将与LuaJIT(Lua即时编译器)链接。 simLua库项目文件位于此处。 有关Lua和LuaJIT的致谢和鸣谢,请参见此处。
在这里插入图片描述

  CoppeliaSim扩展了Lua的命令并添加了CoppeliaSim特定的命令,这些命令可以通过其sim前缀来识别(例如sim.handleCollision)。 有关所有特定于CoppeliaSim的Lua命令的列表,请参阅常规API。 也可以从主客户端应用程序或插件中注册新的自定义Lua命令。 有关更多信息,请参考相关的API函数。

  通过使用在线提供的Lua扩展库,可以轻松扩展Lua的功能本身。

  嵌入式脚本是嵌入到场景(或模型)中的脚本,即,脚本是场景的一部分,并将与场景(或模型)的其余部分一起保存和加载。 支持不同类型的嵌入式脚本。 每种类型都有特定的功能和应用领域:
在这里插入图片描述

支持两种主要类型的嵌入式脚本:

  • 模拟脚本:模拟脚本是仅在模拟期间执行的脚本,用于自定义模拟或模拟模型。 主仿真循环通过主脚本处理,模型/机器人通过子脚本控制。
  • 自定义脚本:这些脚本也可以在不运行模拟时执行,并用于自定义模拟场景或模拟器本身。

  除了嵌入式脚本,CoppeliaSim还支持加载项和沙箱脚本,这些附件和沙箱脚本可提供未链接到特定场景或模型的特定功能。

  在不同的脚本类型中,记住一些与场景对象(附加到场景对象,即关联的脚本)相关联的脚本可能很有用,例如子脚本、联合控制回调脚本和自定义脚本,而另一些是不相关联的(即未关联的脚本)。关联脚本构成CoppeliaSim分布式控制体系结构的基础,如果它们的关联对象被复制,则共享可自动复制的便捷属性。

  CoppeliaSim脚本可以在任何许可下发布。

6.1.1 仿真脚本

  仿真脚本是仅在仿真运行时才执行的嵌入式脚本。 有两种类型的仿真脚本:
在这里插入图片描述

  • 主脚本:默认情况下,每个场景都有一个处理所有功能的主脚本(即负责调用子脚本(请参见下文))。 没有主脚本,模拟将无法运行。 可以自定义主脚本,但是最好在子脚本中完成所有自定义工作。
  • 子脚本:每个场景对象都可以与一个子脚本相关联,该子脚本将处理模拟的特定部分。 它们的一个特殊之处是它们也可以线程运行(其他任何脚本类型都不是)。 子脚本最常见的用途是让它们控制一个模型(例如,机器人)。

  由于子脚本已附加到场景对象(即它们是关联的脚本),因此它们还将在复制和粘贴操作期间被复制,这是一个重要功能,可轻松扩展模拟场景。 关联脚本构成了CoppeliaSim分布式控制体系结构的基础。

6.1.1.1 主脚本和子脚本

  仿真脚本中的主脚本和子脚本在每个仿真中起着核心作用:主脚本包含仿真循环代码,而子脚本包含用于控制模型的典型代码(例如机器人、传感器或执行器)。

  默认情况下,每个场景都有一个处理所有功能的主脚本。 没有主脚本,模拟将无法运行。 可以自定义主脚本,但是最好在子脚本中完成所有自定义工作。

  每个场景对象都可以与一个子脚本相关联,该子脚本将以独立和分布式的方式处理模拟的特定部分。 子脚本最常见的用途是让它们控制模型。

以下是主脚本和子脚本之间的主要区别:

  • 只能有一个主脚本。 子脚本可以有无限数量。
  • 主脚本是独立的,最好不要自定义。 子脚本与场景对象相关联,应进行自定义。
  • 主脚本绝不会在场景对象的复制/粘贴操作中重复。 子脚本将自身与关联的场景对象一起复制。
  • 主脚本不能被线程化。 子脚本可以是线程的也可以是非线程的。
  • 子脚本可以使用特殊类型的系统回调函数。
6.1.1.1.1 主脚本

  主脚本是模拟脚本。默认情况下,CoppeliaSim中的每个场景都有一个主脚本。它包含允许模拟运行的基本代码。没有主脚本,运行中的仿真将无法执行任何操作。

  主脚本包含系统适当调用的回调函数的集合。如果未定义给定的系统回调函数,则将忽略该调用。除初始化功能外,所有其他功能均为可选。默认的主脚本通常分为4个主系统回调函数:

  • 初始化函数:sysCall_init。该部分仅在模拟开始时执行一次。代码负责准备仿真等。
  • 驱动函数:sysCall_actuation。此部分将在每次模拟过程中执行。该代码负责以通用方式处理模拟器的所有驱动功能(逆运动学、动力学等)。三个命令特别有用:sim.launchThreadedChildScripts、sim.resumeThreads和sim.handleChildScripts。 sim.launchThreadedChildScripts / sim.resumeThreads启动/恢复线程子脚本,而sim.handleChildScripts调用非线程子脚本的sysCall_actuation函数。没有这些命令,子脚本将无法执行或无法执行其驱动功能,并且特定的模型功能或行为将无法按预期运行。
  • 感知函数:sysCall_sensing。此部分将在每次模拟过程中执行。该代码负责以通用方式处理模拟器的所有传感函数(接近传感器、碰撞检测等)。两个命令特别重要:sim.resumeThreads和sim.handleChildScripts。 sim.resumeThreads恢复线程子脚本,而sim.handleChildScripts调用非线程子脚本的sysCall_sensing函数。没有这些命令,子脚本将无法执行其感应功能,并且特定的模型功能或行为将无法按预期运行。
  • 恢复函数:sysCall_cleanup。该部分将在模拟结束之前执行一次。该代码负责恢复对象的初始配置、清除传感器状态、碰撞状态等。

以下是典型的主要脚本,略作简化:

function sysCall_init()
    -- Initialization part:
    sim.handleSimulationStart()
    sim.openModule(sim.handle_all)
    sim.handleGraph(sim.handle_all_except_explicit,0)
end

function sysCall_actuation()
    -- Actuation part:
    sim.resumeThreads(sim.scriptthreadresume_default)
    sim.resumeThreads(sim.scriptthreadresume_actuation_first)
    sim.launchThreadedChildScripts()
    sim.handleChildScripts(sim.syscb_actuation)
    sim.resumeThreads(sim.scriptthreadresume_actuation_last)
    sim.handleCustomizationScripts(sim.syscb_actuation)
    sim.handleAddOnScripts(sim.syscb_actuation)
    sim.handleSandboxScript(sim.syscb_actuation)
    sim.handleModule(sim.handle_all,false)
    sim.resumeThreads(2)
    sim.handleMechanism(sim.handle_all_except_explicit)
    sim.handleIkGroup(sim.handle_all_except_explicit)
    sim.handleDynamics(sim.getSimulationTimeStep())
end

function sysCall_sensing()
    -- Sensing part:
    sim.handleSensingStart()
    sim.handleCollision(sim.handle_all_except_explicit)
    sim.handleDistance(sim.handle_all_except_explicit)
    sim.handleProximitySensor(sim.handle_all_except_explicit)
    sim.handleVisionSensor(sim.handle_all_except_explicit)
    sim.resumeThreads(sim.scriptthreadresume_sensing_first)
    sim.handleChildScripts(sim.syscb_sensing)
    sim.resumeThreads(sim.scriptthreadresume_sensing_last)
    sim.handleCustomizationScripts(sim.syscb_sensing)
    sim.handleAddOnScripts(sim.syscb_sensing)
    sim.handleSandboxScript(sim.syscb_sensing)
    sim.handleModule(sim.handle_all,true)
    sim.resumeThreads(sim.scriptthreadresume_allnotyetresumed)
    sim.handleGraph(sim.handle_all_except_explicit,sim.getSimulationTime()+sim.getSimulationTimeStep())
end

function sysCall_cleanup()
    -- Clean-up part:
    sim.resetCollision(sim.handle_all_except_explicit)
    sim.resetDistance(sim.handle_all_except_explicit)
    sim.resetProximitySensor(sim.handle_all_except_explicit)
    sim.resetVisionSensor(sim.handle_all_except_explicit)
    sim.closeModule(sim.handle_all)
end

function sysCall_suspend()
    sim.handleChildScripts(sim.syscb_suspend)
    sim.handleCustomizationScripts(sim.syscb_suspend)
    sim.handleAddOnScripts(sim.syscb_suspend)
    sim.handleSandboxScript(sim.syscb_suspend)
end

function sysCall_suspended()
    sim.handleCustomizationScripts(sim.syscb_suspended)
    sim.handleAddOnScripts(sim.syscb_suspended)
    sim.handleSandboxScript(sim.syscb_suspended)
end

function sysCall_resume()
    sim.handleChildScripts(sim.syscb_resume)
    sim.handleCustomizationScripts(sim.syscb_resume)
    sim.handleAddOnScripts(sim.syscb_resume)
    sim.handleSandboxScript(sim.syscb_resume)
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70

  不应该修改主脚本。 原因如下:CoppeliaSim的优势之一是可以将任何模型(机器人、执行器、传感器等)复制到场景中并立即投入使用。 修改主脚本时,存在模型不再按预期执行的风险(例如,如果您的主脚本缺少sim.handleChildScripts命令,则复制到场景中的所有模型将根本无法运行)。 另一个原因是,保留默认的主脚本可使旧场景轻松调整以适应新功能(例如,如果新的CoppeliaSim版本引入了简洁的命令sim.doMagic(),则旧场景将被自动更新,以便在其主脚本中也自动调用该命令)。

  但是,如果出于某种原因您确实需要修改场景的主脚本,则可以通过双击场景层次结构顶部的世界图标旁边的浅红色脚本图标来执行此操作:
在这里插入图片描述

  从打开主脚本的那一刻起,它将被标记为自定义脚本,并且将不再自动更新。

  主脚本中的大多数命令的行为或操作方式均相似。 如果以距离计算功能为例,则在常规部分中具有:

  • sim.handleDistance(sim.handle_all_except_explicit):此命令的作用是为所有已注册并在距离计算对话框中列出的距离对象计算最小距离(对距离对象的处理距离计算将计算其最小距离, 设置距离变量,最小距离段将显示在场景中)。 除已标记为显式处理的对象外,所有距离对象均使用该命令进行处理(即计算)。

  任何新的距离对象都将由上述命令自动处理(只要未将其标记为显式处理即可)。 完全相同的机制适用于碰撞检测、接近传感器和视觉传感器模拟,逆运动学等。这是一种功能强大的机制,它允许运行简单的模拟而无需编写任何代码。

  大多数子脚本的系统回调函数都是通过sim.handleChildScripts函数从主脚本调用的,该函数以级联方式对场景层次和附加到单个场景对象的子脚本进行操作。

  如果查看默认的主脚本,您会注意到,启动函数允许启动或修改场景内容(例如sim.handleIkGroup、sim.handleDynamics等),而感知函数则允许感测和探测场景内容(例如 sim.handleCollision、sim.handleDistance、sim.handleProximitySensor等)。 以下说明了模拟配备了接近传感器的移动机器人时默认主脚本中发生的情况:
在这里插入图片描述

  考虑到上述顺序,子脚本将始终(使用sim.readProximitySensor)从先前的感应(发生在前一次模拟遍的末尾,在主脚本内部,使用sim.handleProximitySensor)读取(使用sim.readProximitySensor),然后对障碍物做出反应。

  如果需要显式处理传感器,请确保始终在感测部分中进行操作,否则可能会遇到显示错误的情况,如下图所示:
在这里插入图片描述

  与主脚本一样,它们也具有驱动和感应功能,非线程子脚本也一样。 另一方面,在主脚本处于驱动或感应功能时,可以压缩线程子脚本来运行,请参阅API函数sim.setThreadResumeLocation。

6.1.1.1.2 子脚本

  子脚本是模拟脚本。 CoppeliaSim支持每个场景无限数量的子脚本。 每个子脚本代表一小部分用Lua编写的例程,允许在仿真中处理特定功能。 子脚本附加到场景对象(或与场景对象关联),并且可以从场景层次结构中的脚本图标轻松识别它们:
在这里插入图片描述

  双击脚本图标可以打开脚本编辑器。您可以更改给定脚本的属性,或通过脚本对话框将其与另一个对象关联。通过选择对象,然后导航到[菜单栏->添加->关联的子脚本],可以将新的子脚本附加到对象。子脚本与场景对象的关联具有重要且积极的后果:

  • 很好的可移植性:子脚本将与其关联对象一起保存/加载。使用子脚本,您可以创建极其可移植的代码和仿真模型,而无需依赖任何系统特定的插件。一个功能齐全的模型可以包含在一个文件中(可在各种平台上使用而无需修改),而依靠插件进行模型控制则不是这种情况。此外,出于同样的原因,依赖于子脚本的模型也无需长期维护(例如,新的OS版本将不需要您调整部分代码或重新编译,就像使用插件时一样)。
  • 固有的可伸缩性:如果具有附加子脚本的对象被复制,则其子脚本也将被复制。复制的子脚本的内容将与原始子脚本的内容相同,但是,复制的子脚本将知道已被复制并正确重定向对象访问(例如,如果原始子脚本正在访问“机器人”,则复制的子脚本会自动在“ robot”后附加名称后缀,以访问重复的“ robot”而不是原始的“ robot”)。有关更多详细信息,请参阅有关以编程方式访问常规类型对象的部分。自动名称后缀调整允许复制对象和行为,而无需重写/调整任何代码。
  • 不同模型版本之间没有冲突:如果您修改给定模型的子脚本(例如,根据需要对其进行自定义),则对其他类似模型没有影响。当依靠插件而不是子脚本进行模型控制时,这是一个更为关键的方面:实际上,对于插件,您总是冒与以前的插件版本发生冲突的风险。
  • 与仿真循环非常轻松的同步:子脚本可以运行线程和非线程(请参阅下文)。甚至子脚本的线程版本也可以轻松地与仿真循环同步,这是一个强大的功能。子脚本可以有两种不同的类型:非线程子脚本或线程子脚本:
    在这里插入图片描述

非线程子脚本

  非线程子脚本包含系统回调函数的集合。这些不应该阻塞。这意味着每次调用它们时,它们都应执行一些任务,然后返回控制权。 如果未返回控制,则整个模拟将停止。非线程子脚本函数由主脚本在每个模拟步骤中从主脚本的驱动和感测函数中至少调用两次。 系统还将在适当的地方(例如,在子脚本初始化、清理等期间)调用其他系统回调函数。 只要有可能,应该始终选择非线程化的子脚本,而不是线程化的子脚本。

  非线程子脚本遵循精确的调用或执行顺序:默认情况下,子脚本的调用从叶对象(或无子对象)开始,以根对象(或无父对象)结束。 从默认主脚本调用的sim.handleChildScripts命令处理最重要的系统回调函数。想象一个代表自动门的模拟模型的示例:前和后各有一个接近传感器,用于检测正在接近的人。 当人员足够靠近时,门将自动打开。 下面的代码显示了一个典型的非线程子脚本,该脚本说明了上面的示例:

function sysCall_init()
    sensorHandleFront=sim.getObjectHandle("DoorSensorFront")
    sensorHandleBack=sim.getObjectHandle("DoorSensorBack")
    motorHandle=sim.getObjectHandle("DoorMotor")
end

function sysCall_actuation()
    resF=sim.readProximitySensor(sensorHandleFront) 
    resB=sim.readProximitySensor(sensorHandleBack)
    if ((resF>0)or(resB>0)) then
        sim.setJointTargetVelocity(motorHandle,-0.2)
    else
        sim.setJointTargetVelocity(motorHandle,0.2)
    end
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

非线程子脚本应分为4个主要功能:

  • 初始化函数:sysCall_init。 这部分将只执行一次(第一次调用子脚本)。 这可以在模拟的开始,也可以在模拟的中间:请记住,与子脚本关联的对象可以随时复制/粘贴到场景中,也可以在模拟运行时复制/粘贴到场景中。 通常,您将在这部分中放置一些初始化代码以及处理检索。
  • 驱动函数:sysCall_actuation。 该部分将在模拟步骤的激活阶段的每个模拟步骤中执行。 有关致动阶段的更多详细信息,请参考主脚本默认代码,但是通常,您会在此部分中进行某些致动(无感测)。
  • 感知函数:sysCall_sensing。 该部分将在模拟步骤的感测阶段的每个模拟步骤中执行。 有关感测阶段的更多详细信息,请参考主脚本默认代码,但是通常,您仅在该部分中进行感测(不执行任何操作)。
  • 恢复函数:sysCall_cleanup。 该部分将在模拟结束之前或脚本销毁之前执行一次。

但是非线程子脚本可以使用更多的系统回调函数来响应各种事件:

function sysCall_init() -- not optional!!
    -- Put some initialization code here
end

function sysCall_actuation()
    -- Put some actuation code here
end

function sysCall_sensing()
    -- Put some sensing code here
end

function sysCall_cleanup()
    -- Put some restoration code here
end

function sysCall_dynCallback(inData)
    -- See the dynamics callback function section in the user manual for details about the input argument
end

function sysCall_jointCallback(inData)
    -- See the joint callback function section in the user manual for details about input/output arguments
    return outData
end

function sysCall_contactCallback(inData)
    -- See the contact callback function section in the user manual for details about input/output arguments
    return outData
end

function sysCall_vision(inData)
    -- See the vision callback function section in the user manual for details about input/output arguments
    return outData
end

function sysCall_trigger(inData)
    -- See the trigger callback function section in the user manual for details about input/output arguments
    return outData
end

function sysCall_beforeCopy(inData)
    for key,value in pairs(inData.objectHandles) do
        print("Object with handle "..key.." will be copied")
    end
end

function sysCall_afterCopy(inData)
    for key,value in pairs(inData.objectHandles) do
        print("Object with handle "..key.." was copied")
    end
end

function sysCall_afterCreate(inData)
    for key,value in pairs(inData.objectHandles) do
        print("Object with handle "..value.." was created")
    end
end

function sysCall_beforeDelete(inData)
    for key,value in pairs(inData.objectHandles) do
        print("Object with handle "..key.." will be deleted")
    end
    -- inData.allObjects indicates if all objects in the scene will be deleted
end

function sysCall_afterDelete(inData)
    for key,value in pairs(inData.objectHandles) do
        print("Object with handle "..key.." was deleted")
    end
    -- inData.allObjects indicates if all objects in the scene were deleted
end

function sysCall_suspend()
    -- Simulation is about to be suspended
end

function sysCall_suspended()
    -- Simulation is suspended
end

function sysCall_resume()
    -- Simulation is about to be resumed
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83

线程子脚本

  线程子脚本是将在线程中启动的脚本。 线程子脚本的启动(和恢复)由默认主脚本代码通过sim.launchThreadedChildScripts和sim.resumeThreads函数处理。 线程化子脚本的启动/恢复以精确的顺序执行。 当线程子脚本的执行仍在进行时,将不会再次启动它。 线程化的子脚本结束后,仅当取消选中脚本属性中的“一次执行”项时,才能重新启动该子脚本。 场景层次结构中的线程子脚本图标显示为淡蓝色而不是白色,表明它将在线程中启动。

  如果编程不当,线程子脚本与非线程子脚本相比有几个弱点:它们更加耗费资源,可能浪费一些处理时间,并且对模拟停止命令的响应可能会稍慢一些。

  以下显示了一个典型的线程化子脚本代码,但是由于它在循环中浪费了宝贵的计算时间,因此它不是完美的(该代码从上面的示例中处理自动滑动门):

function sysCall_threadmain()
    -- Put some initialization code here:
    sensorHandleFront=sim.getObjectHandle("DoorSensorFront")
    sensorHandleBack=sim.getObjectHandle("DoorSensorBack")
    motorHandle=sim.getObjectHandle("DoorMotor")
    
    -- Here we execute the regular thread code:
    while sim.getSimulationState()~=sim.simulation_advancing_abouttostop do
        resF=sim.readProximitySensor(sensorHandleFront)
        resB=sim.readProximitySensor(sensorHandleBack)
        if ((resF>0)or(resB>0)) then
            sim.setJointTargetVelocity(motorHandle,-0.2)
        else
            sim.setJointTargetVelocity(motorHandle,0.2)
        end
        -- this loop wastes precious computation time since we should only read new
        -- values when the simulation time has changed (i.e. in next simulation step).
    end
end

function sysCall_cleanup()
	-- Put some clean-up code here:
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

线程子脚本通常被分段为一组系统回调函数的集合,主要的函数包括:

  • 主要函数:syscall_threadmain:这个系统回调函数是唯一一个非可选的函数。 它将在线程启动时执行,直到线程结束前不久。 这可以在模拟开始时进行,也可以在模拟进行到一半时进行;请记住,与子脚本关联的对象可以随时复制/粘贴到场景中,也可以在模拟运行时进行复制/粘贴。 通常,您会将一些初始化代码和主循环放在这一部分中;循环中的代码负责处理模拟的特定部分(例如,处理自动推拉门)。 在上面的具体示例中,循环浪费了宝贵的计算时间,并且与主模拟循环异步运行。 有关更好的示例,请参阅更下面的内容。
  • 恢复函数:该部分将在模拟结束之前或线程结束之前执行一次。

  CoppeliaSim使用线程来模仿协程的行为,而不是传统上的使用它们,因此具有很大的灵活性和控制力;默认情况下,线程化的子脚本将执行约1-2毫秒,然后自动切换到另一个线程。 可以使用sim.setThreadSwitchTiming或sim.setThreadAutomaticSwitch更改此默认行为。 切换当前线程后,它将在下一个模拟过程中恢复执行(即在currentTime + simulationTimeStep时间)。 线程切换是自动的(在指定时间之后发生),但是sim.switchThread命令允许在需要时缩短该时间。 使用以上三个命令,可以实现与主仿真循环的完美同步。 以下代码(从上面的示例处理自动滑门)显示了与主仿真循环的子脚本同步:

function sysCall_threadmain()
    -- Put some initialization code here:
    sim.setThreadAutomaticSwitch(false) -- disable automatic thread switches
    sensorHandleFront=sim.getObjectHandle("DoorSensorFront")
    sensorHandleBack=sim.getObjectHandle("DoorSensorBack")
    motorHandle=sim.getObjectHandle("DoorMotor")
    
    -- Here we execute the regular thread code:
    while sim.getSimulationState()~=sim.simulation_advancing_abouttostop do
        resF=sim.readProximitySensor(sensorHandleFront)
        resB=sim.readProximitySensor(sensorHandleBack)
        if ((resF>0)or(resB>0)) then
            sim.setJointTargetVelocity(motorHandle,-0.2)
        else
            sim.setJointTargetVelocity(motorHandle,0.2)
        end
        sim.switchThread() -- Explicitely switch to another thread now!
        -- from now on, above loop is executed once in each simulation step.
        -- this way you do not waste precious computation time and run synchronously.
    end
end

function sysCall_cleanup()
    -- Put some clean-up code here
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

  现在,在while循环之上,每个主仿真循环将只执行一次,而不会浪费相同的仿真时间一次又一次地读取传感器状态。 默认情况下,线程始终在主脚本调用sim.resumeThreads(sim.scriptthreadresume_default)时恢复。 如果需要确保线程仅在主脚本处于感测阶段时运行,则可以使用API函数sim.setThreadResumeLocation重新确定线程的恢复位置。

  无法将CoppeliaSim线程的类似于协程的行为与常规线程区分开,不同之处在于,如果外部命令(例如Lua库提供的套接字通信命令)是阻塞的,那么CoppeliaSim也将显示为阻塞。 在这种情况下,可以按以下示例定义非阻塞节:

sim.setThreadIsFree(true) -- Start of the non-blocking section

http = require("socket.http")
print(http.request("http://www.google.com")) -- this command may take several seconds to execute

sim.setThreadIsFree(false) -- End of the non-blocking section
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

  在非阻塞部分中,请尝试避免调用sim函数。 永远不要忘记关闭阻塞部分,否则CoppeliaSim可能会挂起或运行得更慢。 有关CoppeliaSim中线程操作的更多详细信息,另请参阅与线程相关的功能和阻止功能。

  为了正确执行,不应中断某些操作(设想在一个循环中移动多个对象)。 在这种情况下,您可以使用sim.setThreadAutomaticSwitch函数临时禁止线程切换。

6.1.1.2 自定义脚本

  定制脚本是嵌入式脚本,可在很大程度上用于自定义模拟场景。 它们被附加到场景对象(或与场景对象关联),并且可以从场景层次结构中的暗脚本图标轻松识别它们:
在这里插入图片描述

  双击脚本图标可以打开脚本编辑器。 您可以更改给定脚本的属性,或通过脚本对话框将其与另一个对象关联。 您可以通过选择对象,然后导航到[菜单栏->添加->关联的自定义脚本],将新的自定义脚本附加到该对象。

以下是定制脚本的主要属性:

  • 运行非线程。
  • 它们始终在同一场景中执行:在运行模拟时以及在不运行模拟时。
  • 它们附加到场景对象(或与场景对象关联)(即它们是关联的脚本)。 关联脚本构成了CoppeliaSim分布式控制体系结构的基础,并且共享了便利的属性,如果关联对象被复制,该属性将被自动复制。

  以上属性允许自定义脚本共享附加组件和子脚本的某些最佳功能。 自定义脚本允许创建可自定义的模型,例如:设想一个被放入场景中的模型,即使没有运行模拟,该模型也能够自我配置或调整。 这可能是一个机器人,用户可以在其中通过单个滑块重新定位来调整各种链接长度。

  自定义脚本包含阻止功能的集合。 这意味着每次调用它们时,它们都应执行一些任务,然后返回控制权。 如果未返回控制,则整个应用程序将暂停。 定制脚本函数经常由系统调用,但主脚本也会遵循精确的执行顺序进行调用。 定制脚本还支持回调功能。

定制脚本应分为几个功能,如以下框架脚本所示:

-- This is a customization script. It is intended to be used to customize a scene in
-- various ways, mainly when simulation is not running. When simulation is running,
-- do not use customization scripts, but rather child scripts if possible

function sysCall_init()
    -- do some initialization here
end

function sysCall_nonSimulation()
    -- is executed when simulation is not running
end

function sysCall_cleanup()
    -- do some clean-up here
end

-- You can define additional system calls here:
function sysCall_beforeSimulation()
end

function sysCall_actuation()
end

function sysCall_sensing()
end

function sysCall_suspend()
end

function sysCall_suspended()
end

function sysCall_resume()
end

function sysCall_afterSimulation()
end

function sysCall_beforeInstanceSwitch()
end

function sysCall_afterInstanceSwitch()
end

function sysCall_userConfig()
end

function sysCall_dynCallback(inData)
    -- See the dynamics callback function section in the user manual for details about the input argument
end

function sysCall_jointCallback(inData)
    -- See the joint callback function section in the user manual for details about input/output arguments
    return outData
end

function sysCall_contactCallback(inData)
    -- See the contact callback function section in the user manual for details about input/output arguments
    return outData
end

function sysCall_beforeCopy(inData)
    for key,value in pairs(inData.objectHandles) do
        print("Object with handle "..key.." will be copied")
    end
end

function sysCall_afterCopy(inData)
    for key,value in pairs(inData.objectHandles) do
        print("Object with handle "..key.." was copied")
    end
end

function sysCall_beforeDelete(inData)
    for key,value in pairs(inData.objectHandles) do
        print("Object with handle "..key.." will be deleted")
    end
    -- inData.allObjects indicates if all objects in the scene will be deleted
end

function sysCall_afterDelete(inData)
    for key,value in pairs(inData.objectHandles) do
        print("Object with handle "..key.." was deleted")
    end
    -- inData.allObjects indicates if all objects in the scene were deleted
end

function sysCall_beforeMainScript()
    -- Can be used to step a simulation in a custom manner.
    local outData={doNotRunMainScript=false} -- when true, then the main script won't be executed
    return outData
end

function sysCall_afterCreate(inData)
    for i=1,#inData.objectHandles,1 do
        print("Object with handle "..inData.objectHandles[i].." was created")
    end
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98

  如果可能,请不要使用自定义脚本来运行仿真代码,这最好通过子脚本来处理。

6.1.1.3 回调函数

  回调函数是特殊的脚本函数,可以由CoppeliaSim在模拟步骤的特定状态下调用:

  • 在每个动力学模拟步骤之前和之后,物理引擎都会调用动力学回调函数。
  • 物理引擎为每个联系人对调用联系人回调函数。
  • 联合回调函数由物理引擎调用以进行自定义联合控制。
  • 每当获取或应用新图像时,系统都会调用视觉回调函数。
6.1.1.3.1 动态回调函数

  非线程子脚本或自定义脚本可以包含动态回调函数。 如果存在,则物理引擎将在每个动力学模拟步骤之前和之后使用适当的参数调用回调函数。 动态回调函数可能经常被调用,通常每个模拟步骤调用10 * 2次(请记住,默认情况下,物理引擎时间步骤比模拟时间步骤小10倍)。 因此,请保持简单,以避免减慢仿真速度。

  以下是一个简单的动态回调函数:

function sysCall_dynCallback(inData)
    -- This function gets called often, so it might slow down the simulation
    --     (this is called twice at each dynamic simulation step, by default 20x more often than a child script)
    -- We have:
    -- inData.passCnt : the current dynamics calculation pass. 1-10 by default. See next item for details.
    -- inData.totalPasses : the number of dynamics calculation passes for each "regular" simulation pass.
    --                      10 by default (i.e. 10*5ms=50ms which is the default simulation time step)
    -- inData.dynStepSize : the step size used for the dynamics calculations (by default 5ms)
    -- inData.afterStep : false when called before, and true after a dynamics step was computed.

    local txt=string.format(" the %ith dynamics calculation step (out of %i steps)",inData.passCnt,inData.totalPasses)
    if inData.afterStep then
        txt="After"..txt
    else
        txt="Before"..txt
    end
    print(txt)
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
6.1.1.3.2 关节回调函数

  非线程子脚本或自定义脚本可以包含联合回调函数。 当存在给定关节时(必须动态启用并且还必须启用其控制循环),物理引擎将使用适当的参数调用回调函数,从而允许用户自定义相关关节的控制环以进行编写低级控制算法。 可能经常调用联合回调函数,对于给定的联合,通常每个模拟步骤调用10次(请记住,默认情况下,物理引擎时间步比模拟时间步小10倍)。 因此,请保持简单,以避免减慢仿真速度。

  以下是一个简单的PID联合回调函数:

function sysCall_jointCallback(inData)
    -- This function gets called often, so it might slow down the simulation
    --     (this is called at each dynamic simulation step, by default 10x more often than a child script)
    -- We have:
    -- inData.first : whether this is the first call from the physics engine, since the joint
    --                was initialized (or re-initialized) in it.
    -- inData.revolute : whether the joint associated with this script is revolute or prismatic
    -- inData.cyclic : whether the joint associated with this script is cyclic or not
    -- inData.handle : the handle of the joint associated with this script
    -- inData.lowLimit : the lower limit of the joint associated with this script (if the joint is not cyclic)
    -- inData.highLimit : the higher limit of the joint associated with this script (if the joint is not cyclic)
    -- inData.passCnt : the current dynamics calculation pass. 1-10 by default. See next item for details.
    -- inData.totalPasses : the number of dynamics calculation passes for each "regular" simulation pass.
    --                      10 by default (i.e. 10*5ms=50ms which is the default simulation time step)
    -- inData.currentPos : the current position of the joint
    -- inData.targetPos : the desired position of the joint
    -- inData.errorValue : targetPos-currentPos (with revolute cyclic joints we take the shortest cyclic distance)
    -- inData.effort : the last force or torque that acted on this joint along/around its axis. With Bullet,
    --                 torques from joint limits are not taken into account
    -- inData.dynStepSize : the step size used for the dynamics calculations (by default 5ms)
    -- inData.targetVel : the joint target velocity (as set in the user interface)
    -- inData.maxForce : the joint maximum force/torque (as set in the user interface)
    -- inData.velUpperLimit : the joint velocity upper limit (as set in the user interface)
    --
    -- Make sure that the joint is dynamically enabled, is in force/torque mode, motor enabled and
    -- control loop enabled, otherwise this function won't be called

    if inData.first then
        PID_P=0.1
        PID_I=0
        PID_D=0
        pidCumulativeErrorForIntegralParam=0
    end
    
    -- The control happens here:
    -- 1. Proportional part:
    local ctrl=inData.errorValue*PID_P
    
    -- 2. Integral part:
    if PID_I~=0 then
        pidCumulativeErrorForIntegralParam=pidCumulativeErrorForIntegralParam+inData.errorValue*inData.dynStepSize
    else
        pidCumulativeErrorForIntegralParam=0
    end
    ctrl=ctrl+pidCumulativeErrorForIntegralParam*PID_I
    
    -- 3. Derivative part:
    if not inData.first then
        ctrl=ctrl+(inData.errorValue-pidLastErrorForDerivativeParam)*PID_D/inData.dynStepSize
    end
    pidLastErrorForDerivativeParam=inData.errorValue
    
    -- 4. Calculate the velocity needed to reach the position in one dynamic time step:
    local maxVelocity=ctrl/inData.dynStepSize -- max. velocity allowed.
    if (maxVelocity>inData.velUpperLimit) then
        maxVelocity=inData.velUpperLimit
    end
    if (maxVelocity<-inData.velUpperLimit) then
        maxVelocity=-inData.velUpperLimit
    end
    local forceOrTorqueToApply=inData.maxForce -- the maximum force/torque that the joint will be able to exert

    -- 5. Following data must be returned to CoppeliaSim:
    firstPass=false
    local outData={}
    outData.velocity=maxVelocity
    outData.force=forceOrTorqueToApply
    return outData
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
6.1.1.3.3 contact回调函数

  非线程子脚本或自定义脚本可以包含联系人回调函数。 如果存在,并且物理引擎检测到两个可响应形状之间发生冲突,则将使用适当的参数调用联系人回调函数,从而允许用户自定义联系人的处理方式。 联系人回调函数可能经常被调用,有时每个模拟步骤会调用数百次(还请记住,默认情况下,一个模拟步骤将物理引擎调用10次)。 因此,请保持简单,以避免减慢仿真速度。

  以下是典型的联系人回调函数:

function sysCall_contactCallback(inData)
    -- Will objects with inData.handle1 and inData.handle2 respond to dynamic collision?
    local retData={}
    retData.ignoreContact=false -- handle contact here
    retData.collisionResponse=true -- shapes will collide

    if inData.engine==sim.physics_bullet then
        retData.bullet={}
        retData.bullet.friction=0
        retData.bullet.restitution=0
    end

    if inData.engine==sim.physics_ode then
        retData.ode={}
        retData.ode.maxContacts=16
        retData.ode.mu=0
        retData.ode.mu2=0
        retData.ode.bounce=0
        retData.ode.bounceVel=0
        retData.ode.softCfm=0
        retData.ode.softErp=0
        retData.ode.motion1=0
        retData.ode.motion2=0
        retData.ode.motionN=0
        retData.ode.slip1=0
        retData.ode.slip2=0
        retData.ode.fDir1={0,0,0}
        local mode=1 -- bit-coded. See below
        -- 1=dContactMu2
        -- 2=dContactFDir1
        -- 4=dContactBounce
        -- 8=dContactSoftERP
        -- 16=dContactSoftCFM
        -- 32=dContactMotion1
        -- 64=dContactMotion2
        -- 128=dContactSlip1
        -- 256=dContactSlip2
        -- 512=dContactApprox1_1
        -- 1024=dContactApprox1_2
        -- 2048=dContactApprox1
        retData.ode.contactMode=mode
    end

    if inData.engine==sim.physics_vortex then
    end

    if inData.engine==sim.physics_newton then
        retData.newton={}
        retData.newton.staticFriction=0
        retData.newton.kineticFriction=0
        retData.newton.restitution=0
    end

    return(retData)
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
6.1.1.3.4 视觉回调函数

  当与视觉传感器关联时,子脚本或自定义脚本可以包括视觉回调功能。 当存在于给定的视觉传感器时,则系统将在每次获取或应用新图像时调用回调函数,从而允许用户执行图像处理。 以下API函数就是这种情况:sim.handleVisionSensor、sim.checkVisionSensor、sim.checkVisionSensorEx、sim.setVisionSensorImage和sim.setVisionSensorCharImage。

  视觉回调函数的位置适用一些条件:通常,它应位于自定义脚本或非线程子脚本中。 但是,如果从线程子脚本中调用触发API函数(例如sim.handleVisionSensor),则视觉回调函数也应位于线程子脚本中。 如果在非线程子脚本和自定义脚本中都存在视觉回调函数,并且该自定义脚本都附加到视觉传感器,则将首先调用该子脚本,然后再调用自定义脚本。

  以下表示一个空的视觉回调函数:

function sysCall_vision(inData)
    -- We have:
    -- inData.handle : the handle of the vision sensor.
    -- inData.resolution : the x/y resolution of the vision sensor
    -- inData.clippingPlanes : the near and far clipping planes of the vision sensor
    -- inData.viewAngle : the view angle of the vision sensor (if in persp. proj. mode)
    -- inData.orthoSize : the ortho size of the vision sensor (if in orth. proj. mode)
    -- inData.perspectiveOperation : true if the sensor is in persp. proj. mode

    local outData={}
    outData.trigger=false -- true if the sensor should trigger
    outData.packedPackets={} -- a table of packed packets. Can be accessed via e.g. sim.readVisionSensor
    return outData
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

  可以通过使用各种API函数来执行图像处理。 视觉插件导出了一些非常简单的图像处理功能。 通过图像插件(OpenCV包装器)支持更多图像处理功能。

  以下是一个简单的边缘检测视觉回调函数,该函数触发并返回一个数据包(基于视觉插件功能):

function sysCall_vision(inData)
    simVision.sensorImgToWorkImg(inData.handle)
    simVision.edgeDetectionOnWorkImg(inData.handle,0.1)
    simVision.workImgToSensorImg(inData.handle)

    local outData={}
    outData.trigger=true
    local packetData={1.0,42.123,129.3}
    outData.packedPackets={sim.packFloatTable(packetData)}
    return outData
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

  以下是视觉回调函数,该函数在获取的图像上绘制一个圆圈(基于图像插件函数):

function sysCall_vision(inData)
    local imgHandle=simIM.readFromVisionSensor(inData.handle)
    local center={inData.resolution[1]/2,inData.resolution[2]/2}
    local radius=(inData.resolution[1]+inData.resolution[2])/8
    simIM.circle(imgHandle,center,radius,{255,0,255},4)
    simIM.writeToVisionSensor(imgHandle,inData.handle)
    simIM.destroy(imgHandle)
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
6.1.1.3.5 触发回调函数

  当与视觉传感器、接近传感器或力/扭矩传感器相关联时,子脚本或自定义脚本可以包括触发器回调函数。

  某些条件适用于触发器回调函数的位置:通常,它应位于自定义脚本或非线程子脚本中。 但是,如果从线程子脚本中调用了触发API函数(例如sim.handleVisionSensor或sim.handleProximitySensor),则触发器回调函数也应位于线程子脚本中。 如果非线程子脚本和自定义脚本中都存在触发器回调函数,并且这两个都附加到对象触发器上,则将首先调用子脚本,然后再调用自定义脚本。

  视觉传感器可以在视觉回调函数内部生成触发信号。 然后按以下示例调用触发器回调(如果存在):

function sysCall_trigger(inData)
    -- We have:
    -- inData.handle : the handle of the vision sensor.
    -- inData.packedPackets : an array of data packets, packed (use sim.unpackFloatTable to unpack)
    --    the first data packet always contains 15 auxiliary values about the acquired image:
    --    - minimum of {intensity, red, green blue and depth value}
    --    - maximum of {intensity, red, green blue and depth value}
    --    - average of {intensity, red, green blue and depth value}

    local outData={}
    outData.trigger=true
    return outData
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

  当检测到物体时,接近传感器会生成触发信号。 然后按以下示例调用触发器回调(如果存在):

function sysCall_trigger(inData)
    -- We have:
    -- inData.handle : the handle of the proximity sensor.
    -- inData.detectedObjectHandle : handle of detected object
    -- inData.detectedPoint : detected point, relative to sensor frame
    -- inData.normalVector : normal vector at detected point, relative to sensor frame

    local outData={}
    outData.trigger=true
    return outData
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

  以下是力/转矩传感器触发回调函数的示例,其中力/转矩传感器已损坏:

function sysCall_trigger(inData)
    -- We have:
    -- inData.handle : the handle of the force/torque sensor.
    -- inData.force : current force
    -- inData.torque : current torque
    -- inData.filteredForce : current filtered force
    -- inData.filteredTorque : current filtered torque
    sim.breakForceSensor(inData.handle)
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
6.1.1.3.6 用户配置回调函数

  定制脚本可以包括用户配置回调函数,该函数在用户双击场景层次结构中的用户参数图标时触发。 例如,这提供了一种灵活的行为来设置仿真参数;可以打开一个复杂的自定义用户界面进行用户交互,或者打开一个简单的文件对话框,如以下示例所示:

function sysCall_userConfig()
    fileAndPath=sim.fileDialog(sim.filedlg_type_load,'Select file','','','text files','txt')
    print('Selected file is: ',fileAndPath)
end
  • 1
  • 2
  • 3
  • 4
6.1.1.4 脚本对话框

  脚本对话框位于[菜单栏->工具->脚本]。 另外,也可以通过其工具栏按钮进行访问:
在这里插入图片描述

  • 插入新脚本:允许添加新脚本。
  • 脚本列表:所有已加载的脚本。可以使用Delete键删除脚本。双击可在脚本编辑器中打开脚本。也可以通过双击场景层次结构中的脚本图标来打开脚本。
  • 禁用:指示脚本是启用还是禁用。对于给定的模型,还可以在模型对话框中禁用其所有子脚本和自定义脚本。
  • 仅执行一次:此项仅适用于线程化子脚本。取消选中此项目时,主脚本将重新启动已结束的线程。
  • 禁用并出现错误:由于自定义脚本不断运行,因此可能难以对脚本错误做出反应。通过启用此复选框,自定义脚本将在发生错误时禁用自身。
  • 关联对象:当前与脚本关联的对象。
  • 执行优先级:指定脚本的执行优先级。执行优先级仅与相同类型的脚本(即非线程子脚本,线程子脚本或自定义脚本)有关,并且仅与场景层次结构中的同级脚本有关。更多详细信息可以在这里找到。
  • 树遍历:指定何时执行脚本(相对于场景层次结构中紧随其后的脚本(其后代脚本))。如果使用反向,则首先执行后代脚本,使用反向,最后执行后代脚本。与父级相同使用与第一个祖先脚本相同的树遍历。树遍历仅与相同类型的脚本有关(即非线程子脚本,线程子脚本或自定义脚本)。更多详细信息可以在这里找到。
  • 调试模式:提供一种监视函数调用和监视变量的简单方法。通过拦截每个调用(一次在实际发生之前执行一次,也执行一次一次)并调用脚本lua / sim.lua中的调试功能__HIDDEN __。debug.entryFunc(可以根据需要由用户修改)来启用该功能。 )。支持以下调试模式:
    • 监视系统调用:仅监视从脚本外部发出的调用。
    • 监视时间大约为1秒:监视变量的更改时间大约为1秒。 sim.setDebugWatchList只能用于监视特定变量。
    • 监视所有调用(慢):监视所有函数调用
    • 监视变量(慢):监视每个函数调用(前后)的变量变化。 sim.setDebugWatchList只能用于监视特定变量。
    • 监视调用和变量(慢):监视所有函数调用和变量的更改。 sim.setDebugWatchList只能用于监视特定变量。
6.1.1.5 脚本执行顺序

  脚本不是随机执行的:脚本类型、脚本位置和脚本设置会影响脚本执行时间(相对于其他脚本)。 要记住的一个简单规则是:脚本越重要或更具持久性,它就越晚被调用/执行。

  执行顺序首先基于脚本类型。 我们有以下顺序,从第一次执行到最后执行:

  • 启动/恢复线程子脚本(可以通过sim.setThreadResumeLocation调整顺序)
  • 非线程子脚本
  • 自定义脚本
  • 附加脚本
  • 沙盒脚本

  由于子脚本是模拟脚本,因此它们只会在模拟运行时运行(即它们不是持久性的)。 定制脚本、附加脚本和沙箱脚本不是这种情况,它们在模拟停止后也会运行。 此外,切换到其他场景时,附加脚本和沙箱脚本也将继续运行。 上面的顺序很有意义,因为可以将重要的脚本设计为依靠不太重要的脚本生成的数据并对其进行操作。

  例如,将按以下顺序调用回调sysCall_sensing:首先在子脚本中,然后在自定义脚本中,在附加脚本中,最后在沙箱脚本中。

  在脚本类型中,执行顺序取决于脚本在场景层次结构中的位置及其以下两个脚本设置:

  • 执行优先级:指定脚本的执行优先级。 执行优先级仅与相同类型的脚本有关,并且仅与场景层次结构中的同级脚本有关。
    在这里插入图片描述

  树遍历:指定何时执行脚本(相对于场景层次结构中紧随其后的脚本(其后代脚本))。 如果使用反向,则首先执行后代脚本,使用反向,最后执行后代脚本。 与父级相同使用与第一个祖先脚本相同的树遍历。 树遍历仅与相同类型的脚本有关。 默认为反向。
在这里插入图片描述

  下图说明了一个具体的示例场景:
在这里插入图片描述

6.1.1.6 脚本编辑器

  脚本编辑器允许编辑CoppeliaSim中的各种脚本。 通过在脚本对话框中双击脚本或在场景层次结构中双击脚本图标可以将其打开。
在这里插入图片描述

  脚本编辑器具有以下功能,可简化代码版本:

  • 自动补全
  • 代码编写提示
  • 语法高亮
  • 突出显示所选单词的所有匹配项
  • 源代码折叠/展开
  • 搜索和替换功能
  • 无需显式保存
  • 跳转到特定功能
  • 撤销重做
  • 检查/包含文件的版本
  • 通过上下文菜单在鼠标指针下打开API函数/常量的文档

  可以通过键入前三个字母(通常是“ sim”)来轻松访问API函数。 修改脚本后,无需显式保存更改:关闭脚本编辑器,保存场景或启动仿真会将更改自动应用到脚本。 运行模拟时,对给定脚本的修改仅在启动新的模拟运行后才会生效,但自定义脚本始终会在关闭编辑器后应用更改,定制脚本除外。 用户还可以明确地重新启动/重置给定脚本,以使更改立即生效。

  如果您想从给定脚本访问递归函数,或者只想从外部文件中运行代码,则可以执行以下操作:

require "myExternalLuaFile"
  • 1

  在这种情况下,请确保文件名为myExternalLuaFile.lua,并且不要忘记将其与场景或模型一起分发,因为该代码将不再是CoppeliaSim文件的一部分。 搜索路径通常如下:

  • <CoppeliaSim可执行文件> /
  • <CoppeliaSim可执行文件> / lua /
  • <当前场景路径> /
  • <其他搜索路径> /(可以使用文件系统/usrset.txt中的变量AdditionalLuaPath指定此路径)

  通过在文件名上打开弹出菜单,可以在同一脚本编辑器中打开通过require指令包含的文件。

6.1.1.7 用户参数

  每个也与脚本关联的对象都可以附加用户参数。 这些参数可以用作例如调整特定模型值的快速方法(例如,移动机器人的最大速度或传感器的分辨率)。 启用后,用户参数图标将显示在场景层次结构中的脚本图标旁边:
在这里插入图片描述

  双击图标可打开用户参数对话框:
在这里插入图片描述

  如果用户修改了参数列表中的值,则脚本可以对该修改做出反应并相应地调整其行为。 脚本可以使用sim.getUserParameter函数来检索参数的值,或使用sim.setUserParameter函数来修改参数的值。 当对象的用户参数列表为空时,用户参数图标将不会显示。 要为特定对象启用它,只需键入sim.setUserParameter(objectHandle,‘@ enable’,‘’),并确保该对象与脚本相关联。

以下是用户参数对话框中的项目:

  • 添加新参数:将新参数添加到列表中。
  • 用户参数:参数列表。 可以双击重命名各个参数,并修改其值。
  • 值:参数的字符串值。 该字符串可以包含任何值,包括嵌入的零。
  • 单位:值的单位(如果适用)。 该单元仅用于通知用户。
  • 参数是私有的:如果启用,则所选参数不会在模拟过程中显示(在这种情况下,该参数可能不打算在模拟过程中进行修改)。
  • 参数是持久的:如果启用,则所选参数将不会在仿真结束时恢复为其原始值。

  用户参数代表一种通过对话框调整特定值的简单方法。 通过使用用户配置回调函数可以实现更灵活的行为:如果在附加到对象的自定义脚本中定义了此类回调函数,则双击用户参数图标将改为调用该回调函数,用户可以在其中准备 例如,更复杂的用户交互对话框。

6.2 插件

  插件是一个共享库(例如dll),由CoppeliaSim的主客户端应用程序在程序启动时自动加载,或通过sim.loadModule / sim.unloadModule动态加载/卸载。 它允许通过用户编写的功能来扩展CoppeliaSim的功能(类似于附加组件的方式)。 该语言可以是能够生成共享库并可以调用导出的C函数的任何语言(例如,对于Java,请参阅GCJ和IKVM)。 插件也可以用作运行其他语言编写的代码的包装程序,甚至可以运行其他语言编写的代码(例如,编写用于处理和执行Atmel微控制器代码的插件)。

  插件通常用于自定义模拟器和/或特定的模拟。 通常,插件仅用于提供带有自定义脚本命令的模拟,因此可与脚本结合使用。 有时,插件用于为CoppeliaSim提供特殊功能,需要快速计算能力(脚本通常比编译语言慢)或硬件设备(例如,真实的机器人)的接口。

  每个插件都必须具有以下3个入口点过程:

extern "C" __declspec(dllexport) unsigned char simStart(void* reserved,int reservedInt);
extern "C" __declspec(dllexport) void simEnd();
extern "C" __declspec(dllexport) void* simMessage(int message,int* auxiliaryData,void* customData,int* replyData);
  • 1
  • 2
  • 3

  如果缺少一个过程,则该插件将被卸载且无法运行。 有关插件的加载状态,请在启动时参考控制台窗口。 下面简要介绍以上三个入口点的用途:

simStart

  主客户端应用程序加载插件后,将一次调用此过程。 该程序应:

  • 检查CoppeliaSim的版本是否与开发该插件所使用的版本相同或更高(只需确保支持您在该插件中使用的所有命令!)。
  • 分配内存,并准备与GUI相关的初始化工作(如果需要)。
  • 注册自定义脚本功能(如果需要)。
  • 注册自定义脚本变量(如果需要)。
  • 如果初始化成功,则返回此插件的版本号,否则返回0。如果返回0,则说明该插件已卸载且无法运行。 由于向后兼容,版本号限制为1到255之间的值。为克服此限制,还可以使用以下API函数:simSetModuleInfo / simGetModuleInfo。

simEnd

  在仿真循环退出之前,将一次调用此过程。 该过程应释放自调用simStart以来保留的所有资源。

simMessage

  模拟器运行时,经常会调用此过程。 该过程负责监视感兴趣的消息并对它们做出反应。 重要的是要根据插件的任务对以下事件做出反应(最好通过截获sim_message_eventcallback_instancepass消息):

  • 创建、销毁、缩放或加载模型时,请确保您反映了插件中的更改(即,将插件与场景内容同步)
  • 加载场景或调用撤消/重做功能时:确保擦除并重建链接到场景内容的所有插件对象
  • 切换场景时:确保擦除并重建链接到场景内容的所有插件对象。 除此之外,请记住,场景切换将丢弃以下项的句柄:
    • 通讯管
    • 信号
    • 标语
    • 绘图对象
    • 等等
  • 当模拟器处于编辑模式时:确保禁用插件提供的任何“特殊功能”,直到编辑模式结束。 特别是,请确保不要以编程方式选择场景对象。
  • 启动模拟时:确保根据需要初始化插件元素。
  • 模拟结束时:请确保释放仅在模拟过程中需要的所有内存和插件元素。
  • 当对象选择状态更改或发送对话框刷新消息时:确保您实现了插件显示的对话框。

有关更多详细信息,请参考sim_message_eventcallback_-type的消息。 在编写插件时,必须注意或考虑其他几点:

  • 插件必须与主客户端应用程序放在同一目录中,并遵循以下命名:simExtXXXX.dll(Windows),libsimExtXXXX.dylib(Mac OSX),libsimExtXXXX.so(Linux),其中XXXX是插件的名称。 请至少使用4个字符,并且不要使用下划线,因为该插件会被忽略(但是,当插件本身加载一些其他库(例如simExtXXXX_de.dll等语言资源)时,您应该使用下划线。)
  • 注册自定义脚本函数或脚本变量时,请为模块注册的所有函数和变量使用前缀并坚持使用前缀(例如simLab.testMemory(),simLab.errorValue等)。
  • 在插件中创建的线程应该非常小心地使用,并且永远不要调用任何模拟器命令(将它们用于后台计算或与硬件通信)。

您可以随意使用所需的任何编译器来编译插件。 但是,如果您希望编写Qt插件(即使用Qt框架的插件),则应记住以下几点:

  • 您需要使用与编译CoppeliaSim相同的Qt版本来编译该插件。 查看CoppeliaSim [帮助->关于]菜单栏项目,了解有关Qt版本的详细信息
  • 您应该使用与CoppeliaSim相同的编译器来编译插件

有关插件的更多信息,请参考以下存储库:

  • 您需要使用与编译CoppeliaSim相同的Qt版本来编译该插件。 查看CoppeliaSim [帮助->关于]菜单栏项目,了解有关Qt版本的详细信息
  • 您应该使用与CoppeliaSim相同的编译器来编译插件

有关插件的更多信息,请参考以下存储库:

  • simExtPluginSkeleton:表示一个插件模板,可用于创建自己的插件。 另请参见simExtPluginSkeletonNG
  • simExtVision:处理特定视觉任务的插件(例如,Velodyne传感器的模拟或全向摄像机的模拟)。
  • simExtBubbleRob:说明了如何添加自定义的Lua函数以及如何处理多个特定模型。 另请参阅相关的插件教程。
  • simExtK3:与KheperaIII模型相关的插件。
  • simExtRemoteApi:与旧版远程API功能(服务器端)相关的插件。
  • simExtROSInterface:ROS软件包,可让您构建CoppeliaSim的ROS接口。
  • simExtROS2Interface:ROS 2软件包,使您可以为CoppeliaSim构建ROS 2接口。
  • simExtBlueZero:封装在CoppeliaSim插件中的BlueZero框架。
  • simExtMtb:说明了一个Qt插件,该插件将机器人语言解释器(或其他仿真器)集成到了CoppeliaSim中。 另请参阅其相关教程。

CoppeliaSim插件可以任何许可发布。

6.3 附加组件

  CoppeliaSim中的附加组件与插件非常相似:它在程序启动时自动加载,并允许用户编写的功能扩展CoppeliaSim的功能。 附件是用Lua编写的。 支持两种类型的加载项:

  • 附加功能:附加功能首先出现在附加菜单中。 当用户选择它们时,它们将被视为一次执行的功能。 进口商和出口商可以方便地与他们一起实施。
  • 附加脚本:附加脚本在所有打开的场景中都是持久性的,并不断执行,有效地在后台运行。 每次调用它们时,它们仅应执行简约的代码,否则整个应用程序的运行速度将会降低。 系统经常以精确的顺序调用附加脚本。 加载项脚本与沙盒脚本共享许多属性。

  附加功能和脚本应编写在与主应用程序位于同一文件夹中的文本文件中,并遵循以下命名约定:

  • simAddOnFunc_xxxx.lua,其中xxxx可以是表示附加功能名称的任何字符串。
  • simAddOnScript_xxxx.lua,其中xxxx可以是表示附加脚本名称的任何字符串。 附加脚本将自动启动。 在附加菜单中选择它可以暂停/取消暂停其执行。 该脚本可以通过返回sim.syscb_cleanup请求终止。
  • simAddOnScript-xxxx.lua,其中xxxx可以是表示附加脚本名称的任何字符串。 加载项脚本将不会自动启动。 用户可以在需要时通过在附加菜单中选择它来启动/暂停/取消暂停它。 该脚本可以通过返回sim.syscb_cleanup请求终止。

  仍然可以通过命令行选项加载和运行不遵循上述命名约定的加载项脚本。
  附加脚本应分为几个功能,如以下框架脚本所示:

-- Return sim.syscb_cleanup if you wish to stop the add-on script

function sysCall_init()
end

function sysCall_cleanup()
end

function sysCall_nonSimulation()
end

function sysCall_beforeSimulation()
end

function sysCall_beforeMainScript()
    -- Can be used to step a simulation in a custom manner.
    local outData={doNotRunMainScript=false} -- when true, then the main script won't be executed
    return outData
end

function sysCall_actuation()
end

function sysCall_sensing()
end

function sysCall_afterSimulation()
end

function sysCall_suspend()
end

function sysCall_suspended()
end

function sysCall_resume()
end

function sysCall_addOnScriptSuspend()
end

function sysCall_addOnScriptSuspended()
end

function sysCall_addOnScriptResume()
end

function sysCall_beforeInstanceSwitch()
end

function sysCall_afterInstanceSwitch()
end

function sysCall_beforeCopy(inData)
    for key,value in pairs(inData.objectHandles) do
        print("Object with handle "..key.." will be copied")
    end
end

function sysCall_afterCopy(inData)
    for key,value in pairs(inData.objectHandles) do
        print("Object with handle "..key.." was copied")
    end
end

function sysCall_beforeDelete(inData)
    for key,value in pairs(inData.objectHandles) do
        print("Object with handle "..key.." will be deleted")
    end
    -- inData.allObjects indicates if all objects in the scene will be deleted
end

function sysCall_afterDelete(inData)
    for key,value in pairs(inData.objectHandles) do
        print("Object with handle "..key.." was deleted")
    end
    -- inData.allObjects indicates if all objects in the scene were deleted
end

function sysCall_afterCreate(inData)
    for i=1,#inData.objectHandles,1 do
        print("Object with handle "..inData.objectHandles[i].." was created")
    end
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84

  附件可以调用任何常规API函数,只要文档中未另行说明即可。 他们甚至可以调用由插件注册的自定义Lua函数。 但是,它们有两个限制:

  • 加载项无法调用要求调用者在线程中运行的API函数。 这是因为加载项以非线程方式运行。
  • 当模拟未运行时,附加组件不应调用仅在模拟运行时才有意义的API函数。

  有关附加组件的更多信息,请确保检查安装文件夹中的演示附加组件simAddOnScript-addOnScriptDemo.lua、simAddOnFunc-addOnFunctionDemo.lua和simAddOnScript-b0RemoteApiServer.lua的内容。

6.4 沙盒脚本

  沙盒脚本与附加脚本非常相似:它在程序启动时自动加载,并允许用户编写的一个或多个功能扩展CoppeliaSim的功能。 除此之外,沙箱脚本还广泛用于CoppeliaSim的Lua commander插件(read-eval-print循环)中,该插件向CoppeliaSim状态栏添加了文本输入,从而可以像在终端机中那样快速输入和执行Lua代码。 它在所有打开的场景中都是持久的,并且会不断执行,并在后台有效运行。 出于这个原因,它每次应仅执行极简代码,否则整个应用程序的运行速度将会降低。 系统经常以精确的顺序调用沙箱脚本。

  沙箱脚本是在启动时从system / sndbxscpt.txt加载的,应分为几个功能,如以下框架脚本所示:

function sysCall_init() -- not optional!
    -- do some initialization here
end

function sysCall_cleanup()
    -- do some clean-up here
end

function sysCall_nonSimulation()
    -- is executed when simulation is not running
end

function sysCall_beforeSimulation()
    -- Simulation is about to start
end

function sysCall_beforeMainScript()
    -- Can be used to step a simulation in a custom manner.
    local outData={doNotRunMainScript=false} -- when true, then the main script won't be executed
    return outData
end

function sysCall_actuation()
    -- put some actuation code here.
end

function sysCall_sensing()
    -- put some sensing code here.
end

function sysCall_afterSimulation()
    -- Simulation has just ended
end

function sysCall_suspend()
    -- Simulation is about to be suspended
end

function sysCall_suspended()
    -- Simulation is suspended
end

function sysCall_resume()
    -- Simulation is about to resume
end

function sysCall_beforeInstanceSwitch()
    -- About to switch to another scene
end

function sysCall_afterInstanceSwitch()
    -- Switched to another scene
end

function sysCall_beforeCopy(inData)
    for key,value in pairs(inData.objectHandles) do
        print("Object with handle "..key.." will be copied")
    end
end

function sysCall_afterCopy(inData)
    for key,value in pairs(inData.objectHandles) do
        print("Object with handle "..key.." was copied")
    end
end

function sysCall_beforeDelete(inData)
    for key,value in pairs(inData.objectHandles) do
        print("Object with handle "..key.." will be deleted")
    end
    -- inData.allObjects indicates if all objects in the scene will be deleted
end

function sysCall_afterDelete(inData)
    for key,value in pairs(inData.objectHandles) do
        print("Object with handle "..key.." was deleted")
    end
    -- inData.allObjects indicates if all objects in the scene were deleted
end

function sysCall_afterCreate(inData)
    for i=1,#inData.objectHandles,1 do
        print("Object with handle "..inData.objectHandles[i].." was created")
    end
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85

  沙箱脚本可以调用任何常规API函数,只要文档中未另行说明即可。 它甚至可以调用由插件注册的自定义Lua函数。 但是,它有两个限制:

  • 沙盒脚本无法调用要求调用者在线程中运行的API函数。 这是因为沙箱脚本以非线程方式运行。
  • 当模拟未运行时,沙箱脚本不应调用仅在模拟运行时才有意义的API函数。

6.5 主客户端应用程序

  CoppeliaSim是一个功能库:没有主客户端应用程序(或主应用程序或主循环),CoppeliaSim无法运行。 安装包随附的默认主客户端应用程序是coppeliaSim.exe(Windows)或coppeliaSim(MacOSX和Linux)。 请注意,在MacOSX下,客户端应用程序以及其他几个项目(例如库)包含在软件包或捆绑包(coppeliaSim.app)中:coppeliaSim.app/Contents/MacOS/coppeliaSim。

主客户端应用程序是一个小型可执行文件,处理以下主要任务:

  • 它使用simRunSimulator运行模拟器。
  • 它使用simLoadModule和simUnloadModule加载和卸载插件
  • 它会加载用simLoadScene或simLoadModel双击的场景或模型文件
  • 它使用simHandleMainScript和simAdvanceSimulationByOneStep处理正在运行的模拟

  coppeliaSimClientApplication需要以下文件来编译和运行该应用程序(不过,最简单的方法是将新建的主应用程序简单地复制到CoppeliaSim Pro / CoppeliaSim Edu / CoppeliaSim Player安装文件夹中):

  • simLib.h,simLib.cpp和simConst.h:动态加载并绑定到CoppeliaSim库所需的文件
  • coppeliaSim.dll / libcoppeliaSim.dylib / libcoppeliaSim.so:CoppeliaSim库
  • lua5.1.dll(或类似文件):Lua功能所需的库
  • qscintilla2.dll(或类似文件):scintilla编辑器所需的库
  • QtCore5.dll,QtGui5.dll等(或类似文件):Qt框架的库
  • CoppeliaSim的系统文件夹及其所有内容(需要进行正确的初始化等)

  可以自定义主要客户端应用程序。 但是不建议这样做,仅当编写脚本和/或插件无法满足您的目的时才应使用此方法,因为如果未正确实施,与默认CoppeliaSim行为失去兼容性的风险很高。

6.6 通讯方式

在继续阅读本节之前,请确保您了解模拟器的总体结构及其API框架。

信号

  信号可以看作是全局变量。 当前支持四种类型的信号:整数,浮点,双精度和字符串型信号。 可以定义,重新定义,读取和清除信号。 在仿真结束时,将清除由主脚本或任何子脚本创建的所有信号。 例如:

-- script 1 writes the data to string signal "mySignalName":

local myData={1,2,{"Hello","world",true,{value1=63,value2="aString"}}}
sim.setStringSignal("mySignalName",sim.packTable(myData))
  • 1
  • 2
  • 3
  • 4
-- script 2 reads the data from string signal "mySignalName":

local myData=sim.getStringSignal("mySignalName")
if myData then
    myData=sim.unpackTable(myData)
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

自定义数据块

  定制数据块是可以存储在对象内部或场景内部的数据。 它可以用来存储要与模型或场景一起保存的自定义数据,也可以用作通信手段。 例如:

-- script 1 writes the data to the scene:

local myData={1,2,{"Hello","world",true,{value1=63,value2="aString"}}}
sim.writeCustomDataBlock(sim.handle_scene,"myTag",sim.packTable(myData))
  • 1
  • 2
  • 3
  • 4
-- script 2 reads the data from the scene:

local myData=sim.readCustomDataBlock(sim.handle_scene,"myTag")
if myData then
    myData=sim.unpackTable(myData)
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

ROS

  例如:

-- script subscribes to a topic carrying an image:

function sysCall_init()
    sub=simROS.subscribe('/image', 'sensor_msgs/Image', 'imageMessage_callback')
    simROS.subscriberTreatUInt8ArrayAsString(sub)
end

function imageMessage_callback(msg)
    -- here we have:
    -- msg.data
    -- msg.height
    -- msg.width
    -- etc.
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

BlueZero

  例如:

-- script subscribes to a topic carrying an image:

function sysCall_init()
    b0Node=simB0.create('b0Node')
    sub=simB0.createSubscriber(b0Node,'image','imageMessage_callback')
    simB0.init(b0Node)
end

function sysCall_sensing()
    simB0.spinOnce(b0Node)
end

function imageMessage_callback(msg)
    -- msg is a raw buffer. 
    -- If the image data was encoded in base64, we could have:
    msg=sim.transformBuffer(msg,sim.buffer_base64,1,0,sim.buffer_uint8)
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

远程API

  例如,从基于Python B0的远程API客户端中:

# Receiving an image from CoppeliaSim and sending it back:

import b0RemoteApi
with b0RemoteApi.RemoteApiClient('b0RemoteApi_pythonClient','b0RemoteApi') as client:    
        
    def imageCallback(msg):
        client.simxSetVisionSensorImage(passiveVisionSensorHandle[1],False,msg[2],client.simxDefaultPublisher())
    
    visionSensorHandle=client.simxGetObjectHandle('VisionSensor',client.simxServiceCall())
    passiveVisionSensorHandle=client.simxGetObjectHandle('PassiveVisionSensor',client.simxServiceCall())
    client.simxGetVisionSensorImage(visionSensorHandle[1],False,client.simxDefaultSubscriber(imageCallback))
    client.simxStartSimulation(client.simxDefaultPublisher())
    while True: 
        client.simxSpinOnce()
    client.simxStopSimulation(client.simxDefaultPublisher())
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

  例如,从Python旧版远程API客户端中:

# Receiving an image from CoppeliaSim and sending it back:

import sim
clientID=sim.simxStart('127.0.0.1',19999,True,True,5000,5)
if clientID!=-1:
    res,v0=sim.simxGetObjectHandle(clientID,'Vision_sensor',sim.simx_opmode_oneshot_wait)
    res,v1=sim.simxGetObjectHandle(clientID,'PassiveVision_sensor',sim.simx_opmode_oneshot_wait)

    res,resolution,image=sim.simxGetVisionSensorImage(clientID,v0,0,sim.simx_opmode_streaming)
    while (sim.simxGetConnectionId(clientID)!=-1):
        res,resolution,image=sim.simxGetVisionSensorImage(clientID,v0,0,sim.simx_opmode_buffer)
        if res==sim.simx_return_ok:
            res=sim.simxSetVisionSensorImage(clientID,v1,image,0,sim.simx_opmode_oneshot)
    sim.simxFinish(clientID)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

持久性数据块

  持久性数据块可以看作是持久性全局缓冲区。 持久数据块可以定义,重新定义,读取和清除,并在所有打开的场景之间共享。 它们会一直持续到模拟器结束,但也可能会一直保留在文件中,并在下次启动CoppeliaSim时自动重新加载。

自定义lua函数

  主客户端应用程序或任何插件都可以通过Lua定制API命令注册定制Lua函数。 当从脚本中调用时,自定义Lua命令将在主客户端应用程序或插件中回调已注册的函数。 这对于实现高级Lua命令非常方便(例如,可以想象让一个插件使用单个Lua命令simRobot.moveAndAvoidObstacles()来处理机器人的运动!)

无线通信仿真

  CoppeliaSim允许以非常灵活的方式模拟无线通信:可以将数据发射到特定方向,并经过特定距离。 如果接收器位于指定的发射区域内,则可以接收发射的数据。 有关更多详细信息,请参考常规API中的相应功能。 通过启用“环境”对话框中的“可视化无线发射”和“可视化无线接收”项目,可以可视化无线发射/接收活动。 下图说明了两个移动机器人之间的可视化无线通信:
在这里插入图片描述

串口通信

  CoppeliaSim在API中实现了用于串行端口通信的特定功能。

LuaSocket

  CoppeliaSim附带了一个名为LuaSocket的Lua扩展库(有关此库的确认和荣誉,请参见此处)。 它允许从嵌入式脚本或附加组件中执行各种类型的套接字通信。 以下代码部分说明了线程化的子脚本如何获取网页:

http=require("socket.http")
sim.setThreadIsFree(true) -- Allow real threading from here (to avoid blocking CoppeliaSim)
page=http.request("http://www.google.com")
sim.setThreadIsFree(false) -- Forbid real threading from here
  • 1
  • 2
  • 3
  • 4

  请注意,request命令的阻塞部分是如何放入非阻塞部分的。 有关如何避免外部命令阻塞的更多信息,请参考sim.setThreadIsFree API命令。

  如果您的应用程序需要套接字通信,那么将线程脚本设置为请求服务器并让其他脚本访问它以进行套接字通信非常方便,如下例所示:

  线程请求服务器:

http = require("socket.http")
-- Open a communication tube:
tubeHandle=sim.tubeOpen(0,'http_communication_test',1)

while (sim.getSimulationState()~=sim.simulation_advancing_abouttostop) do
    -- Wait for a message in the tube and reat it:
    data=sim.tubeRead(tubeHandle,true)
    if (data) then
        -- Fetch the page:
        sim.setThreadIsFree(true) -- Allow real threading from here (to avoid blocking CoppeliaSim)
        reply=http.request(data)
        sim.setThreadIsFree(false) -- Forbid real threading from here
        -- Send back the page:
        if (reply) then
            sim.tubeWrite(tubeHandle,reply)
        else
            sim.tubeWrite(tubeHandle,'Page could not be retrieved')
        end
    else
        sim.switchThread() -- explicitely switch thread
    end
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

  下面的非线程子脚本示例可以用于访问套接字信息:

function sysCall_init()
    -- Open a communication tube:
    tubeHandle=sim.tubeOpen(0,'http_communication_test',1)

    urlsToCheck={'http://www.google.com','http://www.yahoo.com','http://www.titech.co.jp'}
    urlIndex=1
end

function sysCall_sensing()
    s,r,w=sim.tubeStatus(tubeHandle)
    if (s==1)and(r==0)and(w==0) then
        -- Send a request for a page:
        sim.tubeWrite(tubeHandle,urlsToCheck[urlIndex])
    end
    if (s==1)and(r==1) then
        -- Read the reply (the page):
        page=sim.tubeRead(tubeHandle)
        print('URL: '..urlsToCheck[urlIndex])
        print(page:sub(1,200)) -- Only print the first 200 chars
        urlIndex=urlIndex+1
        if (urlIndex>3) then
            urlIndex=1
        end
    end
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

Lua扩展库

  通过使用Lua的扩展库机制,可以几乎无限地扩展CoppeliaSim的通信方式。 与使用LuaSocket库(参见上文)一样,您可以添加在线提供的任何其他类型的Lua扩展库。 您只需要按照库的说明将库安装在CoppeliaSim的安装目录中即可。 如上面的LuaSocket所示,如果扩展库命令阻止了CoppeliaSim,请确保通过sim.setThreadIsFree API命令使用了非阻塞部分。

调用脚本函数

  从主客户端应用程序,插件,嵌入式脚本,远程API客户端或ROS节点,可以使用simCallScriptFunctionEx或simxCallScriptFunction调用脚本函数。 被调用的脚本功能可以执行各种任务,然后将数据发送回调用方。

设置脚本变量

  从主客户端应用程序或插件中,可以使用sim.setScriptVariable设置/清除脚本变量(即Lua变量)。

6.7 以编程方式访问对象

  在CoppeliaSim内及其周围进行编程时,您将始终需要引用您希望使用的各种对象,例如场景对象,Ik组,距离对象等。您可以通过各种句柄检索功能来获得这些句柄 对象名称作为输入参数。 例如,从插件中,您将使用以下方式访问对象Cuboid1:

int cuboid1Handle=simGetObjectHandle("Cuboid1");
  • 1

  在嵌入式脚本中,您可以执行以下操作:

cuboid1Handle=sim.getObjectHandle('Cuboid1')
  • 1

从未关联的代码访问

  未关联代码是未附加到任何场景对象的代码。 这包括为插件,附加组件,远程API客户端,外部ROS节点,外部BlueZero节点和主脚本编写的所有代码。

  在这种情况下,对象访问很简单,如以下示例所示:您只需指定对象的全名即可检索其句柄:

// e.g. inside of a c/c++ plugin:
int cuboid1Handle=simGetObjectHandle("Cuboid1"); // handle of object "Cuboid1"
int cuboid2Handle=simGetObjectHandle("Cuboid2"); // handle of object "Cuboid2"
int cuboid1Hash0Handle=simGetObjectHandle("Cuboid1#0"); // handle of object "Cuboid1#0"
int ikGroupHash42Handle=simGetIkGroupHandle("ikGroup#42"); // handle of ik group "ikGroup#42"
  • 1
  • 2
  • 3
  • 4
  • 5
# e.g. inside of a Python legacy remote API client:
opMode=sim.simx_opmode_blocking
res,cuboid1Handle=sim.simxGetObjectHandle(clientId,"Cuboid1",opMode) # handle of object "Cuboid1"
res,cuboid2Handle=sim.simxGetObjectHandle(clientId,"Cuboid2",opMode) # handle of object "Cuboid2"
res,cuboid1Hash0Handle=sim.simxGetObjectHandle(clientId,"Cuboid1#0",opMode) # handle of object "Cuboid1#0"
  • 1
  • 2
  • 3
  • 4
  • 5

从关联代码访问

  关联代码是与场景对象(即,附加到场景对象)相关联的代码。 这包括为子脚本或自定义脚本编写的所有代码。

  在这种情况下,对象访问会有些棘手,这是因为如果关联的对象被复制(例如,当您复制并粘贴机器人模型时),关联的代码将被自动复制: 复制的代码将访问与原始代码相同的对象吗?

  以使用以下代码附加到对象Cuboid1的子脚本为例:

cuboid1Handle=sim.getObjectHandle('Cuboid1')
sim.setShapeColor(cuboid1Handle,nil,0,{math.random(),math.random(),math.random()})
  • 1
  • 2

  上面的代码所做的是将Cuboid1的颜色更改为随机颜色。 复制Cuboid1时,副本将命名为Cuboid1#0。 在那种情况下,重复的脚本与原始脚本保持相同,但是重复的脚本将知道必须访问Cuboid1#0而不是Cuboid1。 因此,复制的长方体也会像原始长方体一样将其颜色更改为随机颜色。

  复制的脚本还将自动访问复制的对象,以确保场景内容的良好且轻松的可伸缩性。 使用自动名称后缀调整机制:

  对象名称始终可以分为基本名称和后缀部分:
在这里插入图片描述

  当关联的代码仅指定用于检索对象句柄的基本名称时,CoppeliaSim将在内部通过添加哈希字符和与脚本关联的对象的后缀来调整名称。 如果指定了全名,则不会自动进行名称调整:
在这里插入图片描述

  可以使用sim.getNameSuffix和sim.setNameSuffix命令更改自动名称调整机制,但是不建议这样做。

6.8 CoppeliaSim API框架

CoppeliaSim API框架将围绕CoppeliaSim的所有接口分组。 它有5 + 1种不同口味:

  • 常规API
  • 远程API
  • ROS接口
  • BlueZero界面
  • 辅助API
  • 其他界面

  虽然可以从模拟器中访问常规API(例如,从嵌入式脚本、附加组件、插件或主客户端应用程序),但是几乎可以从任何可能的位置访问远程API、ROS接口和BlueZero接口。 外部应用程序或硬件(包括真实的机器人、远程计算机等)。 辅助API本身不是接口,而是更多可嵌入的,可独立运行的辅助函数的集合。 其他界面项将用户的所有可能性分组,以扩展可用的界面。 下图说明了各种接口的概述:
在这里插入图片描述

6.8.1 常规API

  常规API是CoppeliaSim API框架的一部分。

  常规API由可以从C / C ++应用程序(插件或主客户端应用程序)或嵌入式脚本调用的数百种功能组成。 CoppeliaSim函数和常量可以很容易地从其“ sim”或“ _sim”前缀(例如sim.handleCollision)中识别出来。 确保不要将常规API(有时也简称为“ API”)与远程API混淆。

  常规API可以通过自定义lua函数(插件或主客户端应用程序注册)进行扩展。 可以从sim * .prefix识别自定义lua函数。

  所有进入或来自API的单位都是米、千克、秒和弧度或它们的组合(除非另有明确说明)。用户界面的单位是米、公斤、秒和度。

6.8.1.1 常规API函数列表(按字母顺序)
6.8.1.2 常规API(按类别)
6.8.1.3 API常量
6.8.1.4 对象参数ID
6.8.1.5 显式和非显式调用

  CoppeliaSim的主要功能通过以下类型的调用或命令来处理:sim.handleXXX或sim.resetXXX(例如sim.handleCollision,sim.resetCollision等)。 该命令所期望的常规参数是常规类型对象的句柄(例如,碰撞对象的句柄,距离对象的句柄等)。 假设您已经通过碰撞检测对话框注册了一个碰撞对象“ robotCollision”。 如果现在希望检查该对象的碰撞状态,则可以编写以下代码:

collisionObjectHandle=sim.getCollisionHandle("robotCollision")
collisionState=sim.handleCollision(collisionObjectHandle)
  • 1
  • 2

  第一行检索名为“ robotCollision”的碰撞对象的句柄(有关句柄检索的确切操作模式,请参阅有关以编程方式访问对象的部分)。 第二行明确地处理碰撞对象(即,对特定的碰撞对象执行碰撞检测)。 在这种情况下,必须将碰撞对象标记为显式处理,否则将生成错误。

  同样,以上两个命令也可以非明确的方式处理对象:除了使用冲突对象句柄作为参数之外,还可以使用sim.handle_all参数:

sim.handleCollision(sim.handle_all)
  • 1

  sim.handle_all参数允许一次处理所有已注册的碰撞对象。 在此,碰撞对象以非明确的方式处理。

  注册通用类型的对象(例如,冲突对象)时,您不需要任何特定的代码行即可处理这些对象,因为主脚本会处理这些问题(通过指定两个应为 检查碰撞,运行模拟,然后将两个实体之一移到另一个实体上:将检测到碰撞,并且两个实体将以不同的颜色显示以指示碰撞)。 实际上,主脚本包含以下默认代码:

sim.handleCollision(sim.handle_all_except_explicit)
sim.handleDistance(sim.handle_all_except_explicit)
sim.handleProximitySensors(sim.handle_all_except_explicit)
...
  • 1
  • 2
  • 3
  • 4

  命令的参数为sim.handle_all_except_explicit。 它具有与sim.handle_all相同的效果,但以下情况除外:不会处理标记为显式处理的对象。 大多数通用类型的对象可以标记为显式处理。 标记为如此时,只有在使用对象句柄(例如sim.handleCollision(collisionObjectHandle))调用命令或使用sim.handle_all参数(例如sim.handleCollision(sim.handle_all))调用命令时,它们才会被处理。 。

  默认情况下,显式处理标志是禁用的,这意味着默认情况下,通用类型的对象由主脚本处理。 但是,如果子脚本希望(或需要)自己处理对象,则应启用显式处理标志,否则将产生错误。 通常,仅当子脚本在同一模拟遍历中多次要求获得新的计算结果时,才明确地处理对象(否则,子脚本可以使用sim.readCollision,sim.readDistance,sim.readProximitySensor等命令)。 另一方面,子脚本在没有充分理由的情况下切勿使用sim.handle_all或sim.handle_all_except_explicit参数! 下图说明了显式和非显式的调用:
在这里插入图片描述

6.8.2 远程API

  远程API是CoppeliaSim API框架的一部分。 它允许CoppeliaSim与外部应用程序(即在不同进程中或在不同计算机上运行的应用程序)之间的通信,跨平台并支持服务调用(即阻塞调用)和双向数据流。 它有两个不同的版本/框架:

  基于B0的远程API:这表示远程API的第二个版本。 它基于BlueZero中间件及其CoppeliaSim的接口插件。 与传统的远程API相比,它更易于使用且更具灵活性,最重要的是,它易于扩展。 目前,它支持以下语言:C ++,Java,Python,Matlab和Lua。

  旧版远程API(或简称为远程API):这表示远程API的第一个版本。 与基于B0的远程API相比,它相对较轻并且具有更少的依赖性。 但是,它不那么直观,也不灵活,并且很难扩展。 它支持以下语言:C / C ++,Java,Python,Matlab,Octave和Lua。

6.8.2.1 基于BØ的远程API

  基于BØ的远程API不应与传统的远程API(或简称为远程API)混合使用,后者是远程API的较旧版本,灵活性较差,并且扩展难度较大。

  基于B0的远程API是CoppeliaSim API框架的一部分。

  基于B0的远程API允许从外部应用程序或远程硬件(例如,真实的机器人,远程计算机等)控制仿真(或仿真器本身)。 基于CoppeliaSim B0的远程API由大约一百个特定功能和一个通用功能组成,可以从C ++应用程序,Python脚本,Java应用程序,Matlab程序或Lua脚本中调用。 基于B0的远程API函数通过BlueZero中间件及其与CoppeliaSim的接口插件与CoppeliaSim进行交互。 所有这些对用户而言都是隐藏的。 远程API可以让一个或多个外部应用程序以同步或异步的方式(默认情况下为异步)与CoppeliaSim进行交互,甚至还支持对模拟器的远程控制(例如,远程加载场景,开始,暂停或停止模拟) 例如)。

  “同步”一词的含义是每个模拟过程都与远程API应用程序同步运行(即,模拟器将等待来自客户端的触发信号,以在时间t + dt处开始下一个模拟过程)。 在阻塞/非阻塞操作的意义上,这与同步/异步不同。 远程API还支持阻止和非阻止操作。

  阅读本节,确保您了解基于B0的远程API的运行方式。 还可以查看外部控制器教程。

  基于B0的远程API功能来自两个单独的实体,它们通过BlueZero框架进行交互:

  • 客户端(即您的应用程序):客户端上基于B0的远程API可用于许多不同的编程语言。 当前支持以下语言:C ++,Python,Java,Matlab和Lua。 您可以轻松地创建其他语言的绑定。 此处提供了有关如何在客户端上启用基于B0的远程API的说明。
  • 服务器端(即CoppeliaSim):服务器端基于B0的远程API是通过CoppeliaSim插件和Lua脚本(lua / b0RemoteApiServer.lua)实现的。 该插件应在启动时由CoppeliaSim加载:simExtBlueZero.dll,libsimExtBlueZero.dylib或libsimExtBlueZero.so。 插件项目文件位于此处。 此处提供了有关如何在服务器端启用远程API的说明。
6.8.2.1.1 启用基于B0的远程API-客户端

  基于BØ的远程API不应与传统的远程API(或简单的远程API)混淆,后者是远程API的旧版本,灵活性较差,更难扩展。

  所有进入或来自API的单位都是米、千克、秒和弧度或它们的组合(除非另有明确说明)。

C++客户端

  要在C ++应用程序中使用基于B0的远程API功能,只需在项目中包括以下文件:

  • 编程/remoteApiBindings/b0Based/cpp/b0RemoteApi.h
  • 编程/remoteApiBindings/b0Based/cpp/b0RemoteApi.cpp
  • 包括编程路径/ remoteApiBindings / b0Based / cpp / msgpack-c / include
  • 包括编程路径/ blueZero / include / b0 / bindings
  • 链接blueZero库(例如b0.dll),并且不要忘记blueZero库本身具有依赖项(例如libzmq,boost_chrono,boost_system,boost_thread等)。

  有关其他详细信息,请查看programming / remoteApiBindings / b0Based / cpp / simpleTest项目文件以及相应的基于B0的演示场景RemoteApiDemo.ttt。

  本页列出并描述了所有受支持的基于C ++ B0的远程API函数。 CoppeliaSim远程API函数可以从其“ simx”前缀轻松识别。

Python客户端

  要在您的Python脚本中使用基于B0的远程API功能,您将需要执行以下操作:

  • 为Python安装MessagePack:pip安装msgpack
  • 编程/remoteApiBindings/b0Based/python/b0RemoteApi.py
  • 编程/remoteApiBindings/b0Based/python/b0.py
  • blueZero库(例如b0.dll),并且不要忘记blueZero库本身具有依赖项(例如libzmq,boost_chrono,boost_system,boost_thread等)。

  有关其他详细信息,请参阅programming / remoteApiBindings / b0Based / python / simpleTest.py程序,以及相应的基于B0的演示场景RemoteApiDemo.ttt。

  本页列出并描述了所有受支持的基于Python B0的远程API函数。 CoppeliaSim远程API函数可以从其“ simx”前缀轻松识别。

Java客户端

  要在Java应用程序中使用基于B0的远程API功能,您将需要执行以下操作:

  • 包编程/ remoteApiBindings / b0Based / java / coppelia / b0RemoteApi
  • 包编程/ remoteApiBindings / b0Based / java / org / msgpack
  • blueZero库(例如b0.dll),并且不要忘记blueZero库本身具有依赖项(例如libzmq,boost_chrono,boost_system,boost_thread等)。

  看一下programming / remoteApiBindings / b0Based / java / simpleTest.java程序,以及相应的演示场景,基于B0的RemoteApiDemo.ttt,以了解更多详细信息。

  本页列出并描述了所有受支持的基于Java B0的远程API函数。 CoppeliaSim远程API函数可以从其“ simx”前缀轻松识别。

Matlab客户端

  要在Matlab程序中使用基于B0的远程API功能,您将需要执行以下操作:

  • 编程/remoteApiBindings/b0Based/matlab/b0RemoteApiProto.m
  • 编程/remoteApiBindings/b0Based/matlab/b0RemoteApi.m
  • 文件夹编程/ remoteApiBindings / b0Based / matlab / msgpack-matlab
  • blueZero库(例如b0.dll),并且不要忘记blueZero库本身具有依赖项(例如libzmq,boost_chrono,boost_system,boost_thread等)。

  请查看programming / remoteApiBindings / b0Based / matlab / simpleTest.m程序,以及相应的基于B0的演示场景RemoteApiDemo.ttt,以了解更多详细信息。

  本页列出并描述了所有受支持的基于Matlab B0的远程API函数。 CoppeliaSim远程API函数可以从其“ simx”前缀轻松识别。

Lua客户端

  要在您的外部Lua脚本中使用基于B0的远程API功能(即不在CoppeliaSim内部),您将需要执行以下操作:

  • 编程/remoteApiBindings/b0Based/lua/b0RemoteApi.lua
  • 文件夹编程/ remoteApiBindings / b0Based / lua / messagePack-lua
  • Lua与BlueZero的绑定库:programming / remoteApiBindings / b0Based / lua / lib / …
  • blueZero库(例如b0.dll),并且不要忘记blueZero库本身具有依赖项(例如libzmq,boost_chrono,boost_system,boost_thread等)。

  请查看programming / remoteApiBindings / b0Based / lua / simpleTest.lua程序,以及相应的基于B0的演示场景RemoteApiDemo.ttt,以了解更多详细信息。

  本页列出并描述了所有受支持的基于Lua B0的远程API函数。 CoppeliaSim远程API函数可以从其“ simx”前缀轻松识别。

6.8.2.1.2 启用基于B0的远程API-服务端

  基于BØ的远程API不应与传统的远程API(或简称为远程API)混合使用,后者是远程API的较旧版本,灵活性较差,并且扩展难度较大。

  基于B0的远程API服务器端基于Lua脚本:lua / b0RemoteApiServer.lua。 该脚本大量使用了BlueZero界面及其功能,正在模型Models / tools / B0远程Api server.ttm的自定义脚本以及附加脚本simAddOnScript-b0RemoteApiServer.lua中使用。 如果您错过某个特定功能,则可以自己在远程API框架中实现它(另请参见有关扩展基于B0的远程API的部分)。

  要在服务器端(即CoppeliaSim端)启用基于B0的远程API,请确保BlueZero插件已在CoppeliaSim启动时成功加载(simExtBlueZero.dll,libsimExtBlueZero.dylib或libsimExtBlueZero.so)(您可以检查 控制台窗口,以获取有关插件加载的信息。

  最后,使用基于B0的远程API有两种可能性:

  • 启动附加脚本simAddOnScript-b0RemoteApiServer.lua:您可以在菜单栏[附加组件-> b0RemoteApiServer]中手动启动它,也可以在CoppeliaSim启动时自动启动它(在这种情况下,将附加组件重命名为 simAddOnScript_b0RemoteApiServer.lua)。 附加脚本为所有场景提供了基于B0的远程API功能,而不会中断。
  • 将模型Models / tools / B0远程Api server.ttm拖放到特定场景中:这样,基于B0的远程API功能将仅适用于该特定场景。
6.8.2.1.3 基于B0的远程API工作方式

  基于BØ的远程API不应与传统的远程API(或简称为远程API)混合使用,后者是远程API的较旧版本,灵活性较差,并且扩展难度较大。

  基于B0的远程API函数的调用方式与常规API函数的调用方式类似,但是有一个主要区别:

  大多数基于B0的远程API函数都需要一个附加参数:用于执行函数调用的主题或通信渠道。 该主题可以是以下5个函数之一的返回值:

  • simxServiceCall:此主题允许以阻止模式执行该功能,即命令将传递到服务器(即CoppeliaSim),在服务器上执行,然后将响应返回给客户端。 仅当从服务器获取命令响应作为一次性操作时才使用此主题(例如,simxGetObjectHandle通常将使用服务调用来执行)。
  • simxDefaultPublisher:该主题允许以非阻塞模式执行该功能,即该功能被发送到服务器(即CoppeliaSim),并且控制权立即返回给客户端(即客户端将不等待服务器的答复) 。 仅在不希望/不需要服务器响应的服务器上发送命令时才使用本主题(例如,simxSetJointPosition通常将使用默认发布者来执行)。
  • simxDefaultSubscriber:此主题通知服务器继续执行功能,并将响应连续流式传输到客户端。 客户端将在回调函数中接收响应。 仅当您希望从服务器端连续执行的同一命令接收响应时,才使用此主题。 (例如simxGetJointForce通常会使用默认订阅者执行)。 定义的回调函数通过simxSpinOnce函数调用(当输入缓冲区中有响应时)。
  • simxCreatePublisher:这与simxDefaultPublisher非常相似,区别在于创建了专用的发布者主题,即创建了专用的发布渠道。 将特定的功能/命令分配给专用的发布者可能很有用,特别是对于大量数据(例如simxSetVisionSensorImage通常会使用专用的发布者来执行)。
  • simxCreateSubscriber:这与simxDefaultSubscriber非常相似,不同之处在于,创建了专用订户主题,即创建了专用订户通道。 将特定的功能/命令分配给专用订户,尤其是处理大量数据(例如,simxGetVisionSensorImage通常将使用专用订户执行)特别有用。

  默认情况下,基于B0的远程API客户端和服务器(即CoppeliaSim)将异步运行。 但是,可以让客户端单独触发每个模拟步骤,以实现同步操作。 以下是同步模式的Python示例:

import b0RemoteApi
import time

with b0RemoteApi.RemoteApiClient('b0RemoteApi_pythonClient','b0RemoteApi') as client:    
    doNextStep=True

    def simulationStepStarted(msg):
        simTime=msg[1][b'simulationTime'];
        print('Simulation step started. Simulation time: ',simTime)
        
    def simulationStepDone(msg):
        simTime=msg[1][b'simulationTime'];
        print('Simulation step done. Simulation time: ',simTime);
        global doNextStep
        doNextStep=True
        
    client.simxSynchronous(True)
    client.simxGetSimulationStepStarted(client.simxDefaultSubscriber(simulationStepStarted));
    client.simxGetSimulationStepDone(client.simxDefaultSubscriber(simulationStepDone));
    client.simxStartSimulation(client.simxDefaultPublisher())
    
    startTime=time.time()
    while time.time()<startTime+5: 
        if doNextStep:
            doNextStep=False
            client.simxSynchronousTrigger()
        client.simxSpinOnce()
    client.simxStopSimulation(client.simxDefaultPublisher())
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
6.8.2.1.4 扩展基于B0的远程API

  基于BØ的远程API不应与传统的远程API(或简称为远程API)混合使用,后者是远程API的较旧版本,灵活性较差,并且扩展难度较大。

  如果希望使用基于B0的远程API未公开的API命令,则基本上有两个选择:

  • 您可以使用通用函数simxCallScriptFunction
  • 您可以扩展基于B0的远程API

  使用通用函数simxCallScriptFunction:可以扩展基于B0的远程API函数simxCallScriptFunction,而不是扩展基于B0的远程API提供的功能:这将调用CoppeliaSim脚本函数,该脚本函数可以随后在本地执行任何类型的操作 ,然后返回数据。 例如:

function myFunctionName(args)
    for i=1,#args,1 do
        print('Argument '..i..' is :')
        print(args[i])
        print('')
    end
    return 'Hello to you too!',42 -- return a string and a number
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

  然后,基于B0的远程API客户端应用程序将以以下方式(例如,通过Python脚本)调用上述脚本函数:

import b0RemoteApi

with b0RemoteApi.RemoteApiClient('b0RemoteApi_pythonClient','b0RemoteApi') as client:    
    args=['Hello World!',[1,2,3],59]
    ret=client.simxCallScriptFunction('myFunctionName@objectName','sim.scripttype_childscript',args,client.simxServiceCall())
    print(ret)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

  确保在尝试调用函数的脚本处进行了初始化:在未运行模拟的情况下,尝试在模拟脚本中调用函数将不起作用。 同样,尝试在已结束(或尚未启动)的线程子脚本中调用函数将不起作用。

  扩展基于B0的远程API:涉及4个简单步骤:

  • 将函数添加到文件编程/remoteApiBindings/b0-based/generate/simxFunctions.xml的末尾
  • 运行以下命令:python generate.py --gen-simx-all --xml-file simxFunctions.xml ./genic
  • 作为上一步的结果,/ generate将包含基于客户端的基于B0的远程API代码以及所生成的文档。 将这些文件复制到位。
  • 最后,在文件lua / b0RemoteApiServer.lua的开头将服务器端对应项添加到新功能中
6.8.2.1.5 基于B0的远程API函数列表
6.8.2.1.6 基于B0的远程API函数(C/C++)
6.8.2.1.7 基于B0的远程API函数(Python)
6.8.2.1.8 基于B0的远程API函数(Java)
6.8.2.1.9 基于B0的远程API函数(Matlab)
6.8.2.1.10 基于B0的远程API函数(Lua)
6.8.2.2 旧版远程API

  旧版远程API或远程API不应与基于BØ的远程API混合使用,后者是远程API的更新版本,它更灵活,更易于使用,最重要的是,更易于扩展。

  远程API是CoppeliaSim API框架的一部分。

  远程API允许从外部应用程序或远程硬件(例如,真实的机器人,远程计算机等)控制仿真(或仿真器本身)。 CoppeliaSim远程API由大约一百个特定功能和一个通用功能组成,可以从C / C ++应用程序,Python脚本,Java应用程序,Matlab / Octave程序或Lua脚本中调用。 远程API函数通过套接字通信(或可选地通过共享内存)与CoppeliaSim进行交互。 所有这些对用户而言都是隐藏的。 远程API可以让一个或多个外部应用程序以同步或异步的方式(默认情况下为异步)与CoppeliaSim进行交互,甚至还支持对模拟器的远程控制(例如,远程加载场景,开始,暂停或停止模拟))。

  “同步”一词的含义是每个模拟过程都与远程API应用程序同步运行(即,模拟器将等待来自客户端的触发信号,以在时间t + dt处开始下一个模拟过程)。 在阻塞/非阻塞操作的意义上,这与同步/异步不同。 远程API还支持阻止和非阻止操作。

  阅读本节,确保您了解远程API的运行方式。 还可以查看外部控制器教程。

  远程API功能来自2个独立的实体,它们通过套接字通信进行交互:

  • 客户端(即您的应用程序):客户端的远程API可用于许多不同的编程语言。 当前支持以下语言:C / C ++,Python,Java,Matlab,Octave和Lua。 您可以轻松地创建其他语言的绑定。 此处提供了有关如何在客户端上启用远程API的说明。
  • 服务器端(即CoppeliaSim):服务器端的远程API是通过CoppeliaSim插件实现的,该插件默认情况下由CoppeliaSim加载:simExtRemoteApi.dll,libsimExtRemoteApi.dylib或libsimExtRemoteApi.so。 插件项目文件位于此处。 此处提供了有关如何在服务器端启用远程API的说明。
6.8.2.2.1 启用远程API-客户端

  远程API或旧式远程API不应与基于BØ的远程API混合使用,后者是远程API的更新版本,它更灵活,更易于使用,最重要的是,更易于扩展。

  到达或来自API的所有单位均以米,千克,秒和弧度或它们的组合(除非另有明确说明)。

C/C++客户端

  要在C / C ++应用程序中使用远程API功能,只需在项目中包括以下C语言文件:

  • extApi.h
  • extApi.c
  • extApiPlatform.h(包含平台特定的代码)
  • extApiPlatform.c(包含平台特定的代码)

  上面的文件位于CoppeliaSim的安装目录中,位于programming / remoteApi下。 确保已将NON_MATLAB_PARSING和MAX_EXT_API_CONNECTIONS = 255(以及可选的DO_NOT_USE_SHARED_MEMORY)定义为预处理程序定义。 要在客户端(即您的应用程序)上启用远程API,请调用simxStart。 有关示例,请参见编程目录中的bubbleRobClient项目。 本页列出并描述了所有受支持的C / C ++远程API函数。 CoppeliaSim远程API函数可以从其“ simx”前缀轻松识别。

Python客户端

  要在您的Python脚本中使用远程API功能,您将需要以下三项:

  • sim.py
  • simConst.py
  • remoteApi.dll,remoteApi.dylib或remoteApi.so(取决于您的目标平台)

  以上文件位于CoppeliaSim的安装目录中,位于programming / remoteApiBindings / python下。 如果尚未构建remoteApi共享库,则可能必须自己构建(使用remoteApiSharedLib.vcproj或makefile)。 在这种情况下,请确保已将NON_MATLAB_PARSING和MAX_EXT_API_CONNECTIONS = 255(以及可选的DO_NOT_USE_SHARED_MEMORY)定义为预处理器定义。

  将以上元素放在Python已知的目录中后,请调用import sim加载库。 要在客户端(即您的应用程序)上启用远程API,请调用sim.simxStart。 有关示例,请参见programming / remoteApiBindings / python目录中的simpleTest.py脚本。 本页列出并描述了所有受支持的Python远程API函数。 CoppeliaSim远程API函数可以从其“ simx”前缀轻松识别。

Java客户端

  要在Java应用程序中使用远程API功能,您将需要以下两项:

  • 软件包coppelia(包含12个Java类)
  • remoteApiJava.dll,libremoteApiJava.dylib或libremoteApiJava.so(取决于目标平台)

  以上文件位于CoppeliaSim的安装目录中,位于programming / remoteApiBindings / java下。 如果尚未构建remoteApiJava共享库,则可能必须自己构建(使用remoteApiSharedLibJava.vcproj或remoteApiSharedLibJava_Makefile)。 在这种情况下,请确保已将NON_MATLAB_PARSING和MAX_EXT_API_CONNECTIONS = 255(以及可选的DO_NOT_USE_SHARED_MEMORY)定义为预处理器定义。

  在Java已知的目录中拥有以上元素之后,使用带有javac myAppName.java的myAppName.java编译应用程序。 在您的应用程序中,确保导入与import coppelia.className一起使用的类,然后调用remoteApi sim = new remoteApi()来加载库。 要在客户端(即您的应用程序)上启用远程API,请调用sim.simxStart。 有关示例,请参见programming / remoteApiBindings / java目录中的simpleTest.java程序。此页面列出并描述了所有受支持的Java远程API函数。 CoppeliaSim远程API函数可以从其“ simx”前缀轻松识别。

  您可能还必须将文件夹添加到系统路径。 例如,在Linux中,您可以在执行Java应用程序之前调用:export LD_LIBRARY_PATH = $ LD_LIBRARY_PATH:pwd

Matlab客户端

  要在Matlab程序中使用远程API功能,您将需要以下三项:

  • remoteApiProto.m
  • remApi.m
  • remoteApi.dll,remoteApi.dylib或remoteApi.so(取决于您的目标平台)

  以上文件位于CoppeliaSim的安装目录中,位于programming / remoteApiBindings / matlab下。 如果尚未构建remoteApi共享库,则可能必须自己构建(使用remoteApiSharedLib.vcproj或remoteApiSharedLib_Makefile)。

  在Matlab当前文件夹中具有以上元素之后,调用sim = remApi(‘remoteApi’)来构建对象并加载库。 要在客户端(即您的应用程序)上启用远程API,请调用sim.simxStart。 有关示例,请参见programming / remoteApiBindings / matlab目录中的simpleTest.m程序。此页面列出并描述了所有受支持的Matlab远程API函数。 CoppeliaSim远程API函数可以从其“ simx”前缀轻松识别。

  确保您的Matlab使用与remoteApi库相同的位架构:带32位remoteApi库的64位Matlab将不起作用,反之亦然!

  如果必须重建remoteApi库,则可能必须重新生成原型文件(remoteApiProto.m):首先,请确保您具有Matlab可以识别的编译器。 您可能需要调用mex -setup。 然后,键入loadlibrary(‘remoteApi’,‘extApi.h’,‘mfilename’,‘remoteApiProto’)

Octave客户端

  要在Octave程序中使用远程API功能,您将需要以下两项:

  • remApiSetup.m
  • remApi.oct

  以上文件位于CoppeliaSim的安装目录中,位于programming / remoteApiBindings / octave下。 如果尚未构建remApi.oct共享库,则可能必须自己构建。 在这种情况下,请确保在从Octave命令行调用buildWin,buildLin或buildMac之前,将programming / remoteApi和programming / include的所有内容放入该目录。

  在Octave的当前文件夹中具有上述元素后,请调用sim = remApiSetup()来加载库并绑定函数。 要在客户端(即您的应用程序)上启用远程API,请调用simxStart。 有关示例,请参阅programming / RemoteApiBindings / octave目录中的simpleTest.m程序。此页面列出并描述了所有受支持的Octave远程API函数。 CoppeliaSim远程API函数可以从其“ simx”前缀轻松识别。

  确保八度使用与remoteApi库相同的位架构:带32位remoteApi库的64位八度将不起作用,反之亦然!

Lua客户端

  要在外部Lua脚本中使用远程API功能(即不在CoppeliaSim内部),您需要以下各项:

  • remoteApiLua.dll,remoteApiLua.dylib或remoteApiLua.so(取决于您的目标平台)

  上面的文件位于CoppeliaSim的安装目录中,位于programming / remoteApiBindings / lua下。 如果尚未构建remoteApi共享库,则可能必须自己构建(使用remoteApiLua.vcproj或makefile)。 在这种情况下,请确保已将NON_MATLAB_PARSING和MAX_EXT_API_CONNECTIONS = 255(以及可选的DO_NOT_USE_SHARED_MEMORY)定义为预处理器定义。

  在Lua已知的目录中拥有上述元素之后,调用require’remoteApiLua’加载库。 要在客户端(即您的应用程序)上启用远程API,请调用simxStart。 有关示例,请参见programming / remoteApiBindings / lua目录中的simpleTest.lua脚本。 本页列出并描述了所有受支持的Lua远程API函数。 CoppeliaSim远程API函数可以从其“ simx”前缀轻松识别。

6.8.2.2.2 启用远程API-服务端

  远程API或旧式远程API不应与基于BØ的远程API混合使用,后者是远程API的更新版本,它更灵活,更易于使用,最重要的是,更易于扩展。

  远程API服务器端是通过基于常规API的CoppeliaSim插件实现的。 远程API插件项目位于此处。 如果您错过了一个特定的功能,则可以自己在远程API框架中实现它(另请参见有关扩展远程API的部分)。

  要在服务器端(即CoppeliaSim端)启用远程API,请确保在CoppeliaSim启动时成功加载了远程API插件(simExtRemoteApi.dll,libsimExtRemoteApi.dylib或libsimExtRemoteApi.so)(您可以检查控制台窗口) 有关与插件加载相关的信息)。 远程API插件可以根据需要启动任意数量的服务器服务(每个服务将在不同的端口上监听/通信)。 可以通过两种不同的方式启动服务器服务:

  • 在CoppeliaSim启动时(连续的远程API服务器服务)。 远程API插件将尝试读取名为remoteApiConnections.txt的配置文件,并根据其内容启动适当的服务器服务。 查看配置文件以了解详细信息。 使用此方法可以对模拟器本身进行远程控制。 使用这种方法,即使没有运行模拟,远程API函数也将始终在服务器端执行(下面的下一种方法并不总是这样)。 还有另一种通过命令行启动连续远程API服务器服务的方法。
  • 从脚本内(临时远程API服务器服务)。 在大多数情况下,这是启动远程API服务器服务的首选方法。 服务启动或停止时由用户控制。 但是,当从模拟脚本启动临时远程API服务器服务时,该服务将在模拟结束时自动停止。 可以使用以下两个自定义Lua函数来启动或停止临时的远程API服务器服务(这两个函数由插件导出):simRemoteApi.start、simRemoteApi.stop。

  您可以使用以下自定义Lua函数(该函数由插件导出)来收集有关任何远程API服务器服务的信息:simRemoteApi.status

  您可以使用以下自定义Lua函数(该函数由插件导出)重置(即销毁并重新创建)任何远程API服务器服务:simRemoteApi.reset

6.8.2.2.3 远程API模块operandi
6.8.2.2.4 扩展远程API

  远程API或旧式远程API不应与基于BØ的远程API混合使用,后者是远程API的更新版本,它更灵活,更易于使用,最重要的是,更易于扩展。

  可以使用通用的远程API函数simxCallScriptFunction来代替扩展远程API提供的功能:这将调用CoppeliaSim脚本函数,该脚本函数可以在本地执行任何类型的操作,然后返回数据。 被调用的脚本函数应始终具有以下输入/输出形式:

function myFunctionName(inInts,inFloats,inStrings,inBuffer)
	-- inInts, inFloats and inStrings are tables
	-- inBuffer is a string
    
    -- Perform any type of operation here.

	-- Always return 3 tables and a string, e.g.:
	return {},{},{},''
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

  然后,远程API客户端应用程序将以以下方式(例如,通过Python脚本)调用上述脚本函数:

inputInts=[1,2,3]
inputFloats=[53.21,17.39]
inputStrings=['Hello','world!']
inputBuffer=bytearray()
inputBuffer.append(78)
inputBuffer.append(42)
res,retInts,retFloats,retStrings,retBuffer=sim.simxCallScriptFunction(clientID,'objectName',sim.sim_scripttype_childscript,
                'myFunctionName',inputInts,inputFloats,inputStrings,inputBuffer,sim.simx_opmode_blocking)
if res==sim.simx_return_ok:
    print (retInts)
    print (retFloats)
    print (retStrings)
    print (retBuffer)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

  确保在尝试调用函数的脚本处进行了初始化:在未运行模拟的情况下,尝试在模拟脚本中调用函数将不起作用。 同样,尝试在已结束(或尚未启动)的线程子脚本中调用函数将不起作用。

  有关其他用法示例,请参考文件夹programming / remoteApiBindings中名为complexCommandTest。*的各种文件。

6.8.2.2.5 远程API函数列表
6.8.2.2.6 远程API常量
6.8.2.2.7 远程API函数(C/C++)
6.8.2.2.8 远程API函数(Python)
6.8.2.2.9 远程API函数(Java)
6.8.2.2.10 远程API函数(Matlab)
6.8.2.2.11 远程API函数(Octave)
6.8.2.2.12 远程API函数(Lua)

6.8.3 ROS接口

  CoppeliaSim有几种可用的ROS接口。 每个人都提供特定的行为,功能或操作方式:

  • ROS接口:ROS接口以良好的保真度复制了C/C++ ROS API。 这使其成为通过ROS进行非常灵活的通信的理想选择,但可能需要更多了解各种消息和ROS的运行方式。 该接口有两种形式:ROS接口和ROS 2接口。
  • ROS插件框架:这表示一个框架项目,可用于为CoppeliaSim创建新的ROS插件。 在尝试编辑此项目之前,请确保先查看ROS接口源代码。
  • 其他人开发的ROS接口:我们不直接支持这些接口。 例如,CoppeliaSim ROS桥。

  所有ROS接口通常可以并行运行,但是我们强烈建议您首先尝试使用ROS接口,因为这是最灵活,最自然的方法。 上面列出的ROS接口的前两个软件包位于此处和此处。 使用catkin工具构建那些软件包,否则您可能会遇到困难。

  还可以查看ROS教程和外部控制器教程。

6.8.3.1 ROS/ROS2接口

  ROS接口是CoppeliaSim API框架的一部分,由Federico Ferri提供。 确保不要将ROS接口与RosPlugin混淆,后者是CoppeliaSim中较旧的,已弃用的接口。 ROS接口以很高的保真度复制了C / C ++ ROS API。 这使其成为通过ROS进行非常灵活的通信的理想选择,但可能需要更多了解各种消息和ROS的运行方式。 对于ROS和ROS 2,它有两种形式。您可以从simROS-或simROS2-前缀中识别ROS接口API函数。

  ROS是一种分布式伪操作系统,可以轻松管理和连接在网络中的多台计算机之间进行通信。 有关ROS的详细信息,请参阅官方ROS文档。

  CoppeliaSim可以充当ROS节点,其他节点可以通过ROS服务,ROS发布者和ROS订户与之通信。

  CoppeliaSim中的ROS接口功能通过以下插件启用:libsimExtROSInterface。或libsimExtROS2Interface。。 这些插件可以轻松地适应您自己的需求。 插件在启动CoppeliaSim时加载,但是加载操作仅在roscore当时运行时才能成功。 确保检查CoppeliaSim的控制台窗口或终端以获取有关插件加载操作的详细信息。

  查看以下仿真场景/模型,以快速入门ROS接口:

  • rosInterfaceTopicPublisherAndSubscriber.ttt
  • controlTypeExamples.ttt(专注于红色机器人)
  • Models / tools / rosInterface辅助工具tool.ttm(允许在同步模式下操作CoppeliaSim的模型,例如,以便手动进行仿真)

  还可以查看ROS教程和外部控制器教程。

6.8.3.1.1 ROS API接口
6.8.3.1.2 ROS2 API接口

6.8.4 BlueZero接口

  BlueZero接口是CoppeliaSim API框架的一部分。 它将BlueZero(BØ)框架包装到CoppeliaSim插件中。 您可以从simB0前缀识别BlueZero API函数。

  BØ是一个跨平台的中间件,它提供了用于互连在多个线程,多个进程甚至多个计算机中运行的软件的工具。 它与ROS有一些相似之处,尽管它只专注于提供通信范例(客户端/服务器和发布者/订阅者)和消息传输(基于ZeroMQ),而与消息序列化格式或通用协议和数据结构无关。

  CoppeliaSim可以充当一个或多个BØ节点,其他节点可以通过BØ服务,BØ发布者和BØ订户与之通信。

  CoppeliaSim中的BlueZero接口功能通过以下插件启用:simExtBlueZero.dll,libsimExtBlueZero.so或libsimExtBlueZero.dylib。 可以在此处找到该插件的代码,可以轻松地适应您自己的需求。 启动CoppeliaSim时会加载该插件,但您应确保运行b0_ resolver。 确保检查CoppeliaSim的控制台窗口或终端以获取有关插件加载操作的详细信息。 可以在CoppeliaSim文件夹中找到各种BlueZero工具。

  查看以下模拟场景,以快速开始使用BlueZero界面:

  • blueZeroDemo1.ttt
  • blueZeroDemo2.ttt
  • controlTypeExamples.ttt

  还可以查看外部控制器教程

6.8.4.1 BlueZero插件API相关

6.8.5 辅助API

  辅助API是CoppeliaSim API框架的一部分。

  辅助API本身不是接口,而是更多的辅助函数集合,可以嵌入您自己的代码中并且可以独立运行。 提供以下辅助API类别:

  • Coppelia运动学例程:这是C ++函数的集合,这些函数提供与CoppeliaSim中相同的运动学计算
6.8.5.1 Coppelia几何例程(辅助API)

  该功能集合允许您执行与CoppeliaSim内部相同的几何计算,即,碰撞检测,最小距离计算以及对网格,八叉树和点云的接近传感器模拟。

  有关API详细信息,请参阅Coppelia几何例程存储库中的geom.h文件(目前)。

  Coppelia几何例程源代码不是CoppeliaSim的直接一部分,并且带有单独的许可条件。 有关详细信息,请参考源代码。

6.8.5.2 Coppelia运动学例程(辅助API)

  该功能集合允许您执行与CoppeliaSim中相同的运动学计算。

  这个想法通常是在CoppeliaSim中构建运动学任务,然后导出场景的运动学内容,然后可以将其直接与下面的可嵌入函数一起使用。 所需的源代码位于此处。 确保将所有文件包括在项目中,并在需要访问功能的文件中包括ik.h。 还请确保您首先知道如何从CoppeliaSim内部使用运动学功能! 如果您可以访问常规API,那么您将不需要此辅助API,因为所有以下函数都具有与其等效的常规API。

  Coppelia运动学例程源代码不是CoppeliaSim的直接一部分,并且带有单独的许可条件。 有关详细信息,请参考源代码。

  请按照以下方法在您自己的外部应用程序中执行运动学计算:

  • 在CoppeliaSim中建立运动学任务。 测试他们。
  • 使用[菜单栏->文件->导出->运动学内容…]导出场景的运动学内容。
  • 在您自己的应用程序中包括Coppelia运动学例程。
  • 在应用程序启动时调用ikLaunch,在应用程序结束时调用ikShutDown。
  • 调用ikStart导入先前导出的文件。 可以根据需要多次调用ikStart来重置运动场景。 运动场景类似于CoppeliaSim中的场景,不同之处在于,它除去了所有非运动场景。
  • 调用各种函数以移动/旋转目标虚拟对象(例如,使用ikSetObjectTransformation),或移动非活动关节,即不处于IK模式的关节(例如,使用ikSetJointPosition)。
  • 调用ikHandleIkGroup进行一次计算(即有效地将虚拟提示引入其目标)。 如果您要搜索特定的机器人配置,或者需要立即跳转到新的末端执行器姿势,请致电ikGetConfigForTipPose。
  • 根据需要重复上述最后2个步骤。 确保检查返回值以检测错误。
  • 如果同一机器人具有多个实例,则可以多次调用ikLaunch来初始化嵌入式运动学的多个实例。 然后,您可以使用ikSwitch从一个实例切换到另一个实例。

  另请参阅以下示例:standAloneKinematicsDemo1,standAloneKinematicsDemo2,standAloneKinematicsDemo3。 这些演示应用程序使用此处描述的Coppelia运动学例程,结合远程API功能,以反向/正向运动学模式控制两个不同的机器人。 演示场景standAloneKinematicsDemo1.ttt,standAloneKinematicsDemo2.ttt和standAloneKinematicsDemo3.ttt会分别自动启动standAloneKinematicsDemo1,standAloneKinematicsDemo2和standAloneKinematicsDemo3应用程序。

6.8.6 其他API

  为了提供不直接属于CoppeliaSim的接口,可以以各种方式扩展CoppeliaSim API框架。 通常,这是通过插件发生的,但其他选项也可用(加载项,远程API客户端,ROS节点,BlueZero节点等)。 以下列出了其中的一些:

  • OMPL插件,由Federico Ferri提供。 此处有更多详细信息。
  • 自定义UI插件,由Federico Ferri提供。 此处有更多详细信息。
  • CGAL插件,由Federico Ferri提供。
  • ICP插件
  • 图片插件
  • CoppeliaSim / V-REP ROS桥,由Riccardo Spica和Giovanni Claudio提供。
  • 由Antoine Rennuit提供的CoppeliaSim / V-REP中的ROS控制回路。
  • 由Yuki Suga提供的CoppeliaSim / V-REP的OpenRTM-aist接口。
  • CoppeliaSim / V-REP的界面,由Ilya Nemihin提供
  • 通往OpenCV的桥梁,由Marco Bellaccini提供
  • 共享内存通信,由Diego Daniel Santiago提供
  • PeterMaÄiÄka提供的旧版远程API的Labview接口
  • 由Hendrik Wiese提供的旧版远程API的C#包装器
  • 由NAOqi Python SDK控制NAO的项目,由Pierre Jacquot和Gurvan Le Bleis提供
  • Xbox 360控制器插件,用于CoppeliaSim / V-REP,由Nicola Di Pol提供
  • Boris Bogaerts开发了CoppeliaSim / V-REP的VR接口。 它可以在兼容SteamVR(openVR)的VR设备中可视化任何CoppeliaSim / V-REP场景,并将用户操作返回给CoppeliaSim。 安装程序文件在此处可用(无需编译,仅Windows)。 此处提供了有关如何使用该界面的说明。
  • 由Andrei Florea和Catalin Buiu开发的电子冰球的ROS扩展在此处提供。
6.8.6.1 路径和运动计划

  CoppeliaSim通过包装OMPL库的插件提供路径/运动计划功能。 该插件由Federico Ferri提供。

  准备路径/运动计划任务时,应考虑以下几点:

  • 决定开始状态和目标状态。 当路径规划对象是串行操纵器时,通常提供目标姿势(或末端执行器位置/方向),而不是目标状态。 在那种情况下,可以使用sim.getConfigForTipPose函数查找满足所提供目标姿势的一个或多个目标状态。

  • 使用simOMPL.createTask创建路径规划任务。

  • 使用simOMPL.setAlgorithm选择一种算法。

  • 创建所需的状态空间,该状态空间可以组成一个复合对象:simOMPL.createStateSpace和simOMPL.setStateSpace。

  • 指定不允许哪些实体与simOMPL.setCollisionPairs冲突。

  • 使用simOMPL.setStartState和simOMPL.setGoalState指定开始状态和目标状态。

  • 使用simOMPL.compute计算一条或多条路径。

  • 使用simOMPL.destroyTask销毁路径规划任务。

  • 通常,路径规划是与逆运动学结合使用的:例如,在放置任务中,最终方法通常应为一条直线路径,可以使用sim.generateIkPath生成该路径。

  上述程序是常规方法,有时缺乏灵活性。 此外,可以设置以下回调函数:

  • imOMPL.setStateValidationCallback
  • simOMPL.setProjectionEvaluationCallback
  • simOMPL.setGoalCallback

  simOMPL.compute提供的路径通常只是无数其他可能路径中的一个路径,并且不能保证返回的路径是最佳解决方案。 因此,通常先计算几条不同的路径,然后选择更好的路径(例如,较短的路径)。

  以类似的方式,如果必须从一个目标姿势计算出目标状态,则通常会测试几个目标状态,因为并非所有目标状态都可以到达或足够接近(就状态空间距离而言)。 一种常见的做法是先找到几个目标状态,然后根据它们到开始状态的状态空间距离对它们进行排序。 然后执行到最接近的目标状态的路径规划计算,然后执行到下一个最接近的目标状态等,直到找到满意的路径。

  请确保参考以下演示场景以获取更多详细信息:

  • scenes/ik_fk_simple_examples/8-computingJointAnglesForRandomPoses.ttt
  • scenes/3DoFHolonomicPathPlanning.ttt
  • scenes/6DoFHolonomicPathPlanning.ttt
  • motionPlanningDemo1
  • motionPlanningAndGraspingDemo.ttt
6.8.6.2 自定义用户界面

  Federico Ferri提供的自定义UI插件(simExtCustomUI)提供了基于Qt框架的功能。 该插件的源代码可以在这里找到。

  自定义UI插件提供Qt样式的小部件,例如集成按钮,编辑框,滑块,标签,图像等的对话框。对自定义UI的任何操作(例如,按钮单击,文本编辑,滑块移动)都将报告为脚本回调 。 其他功能可通过相关的API函数调用来访问。 这样可以在很大程度上自定义仿真。 以下显示了一个典型的自定义UI:
在这里插入图片描述

7. 仿真

  CoppeliaSim中可以使用[菜单栏->模拟->开始/暂停/停止模拟]或通过相关的工具栏按钮来启动、暂停和停止模拟:
在这里插入图片描述

  在内部,模拟器将使用其他中间状态,以正确告知脚本或程序接下来将发生的情况。以下状态图说明了模拟器的内部状态:
在这里插入图片描述

  脚本和程序应始终根据当前系统调用功能以及可能的模拟状态进行反应,以便正确运行。 优良作法是将每个控制代码至少分为4个系统调用函数(例如,用于非线程子脚本):

  • 初始化函数:sysCall_init:仅在脚本初始化时才调用该函数。
  • 驱动函数:sysCall_actuation:应在驱动发生时调用该函数。
  • 传感函数:sysCall_sensing:应在传感发生时调用此函数。
  • 清理函数:sysCall_cleanup:该函数在脚本未初始化之前被调用(例如在仿真结束时或脚本被销毁时)。

  有关如何安排典型脚本的示例,请参考主脚本,子脚本和自定义脚本页面。

仿真循环

  模拟器通过以恒定的时间步长推进模拟时间来进行操作。下图说明了主要的仿真循环:
在这里插入图片描述

  通过尝试使仿真时间与实时保持同步来支持实时仿真:
在这里插入图片描述

以下是一个非常简化的主客户端应用程序(为清晰起见,已省略了消息,插件处理和其他详细信息):

void initializationCallback
{
    // do some initialization here
}

void loopCallback
{
    if ( (simGetSimulationState()&sim_simulation_advancing)!=0 )
    {
        if ( (simGetRealTimeSimulation()!=1)||(simIsRealTimeSimulationStepNeeded()==1) )
        {
            if ((simHandleMainScript()&sim_script_main_script_not_called)==0)
                simAdvanceSimulationByOneStep();
        }
    }
}

void deinitializationCallback
{
    // do some clean-up here
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

  取决于仿真的复杂性,计算机的性能和仿真设置,实时仿真可能并不总是可能的。

仿真速度

  在非实时模拟中,模拟速度(即感知速度)主要取决于两个因素:模拟时间步长和一个渲染通道的模拟通道数量(更多信息,请参见模拟对话框)。 在实时仿真的情况下,仿真速度主要取决于实时乘法系数,而且在一定程度上取决于仿真时间步长(太小的仿真时间步长可能与实时时间不兼容)。 由于计算机的计算能力有限,因此无法进行仿真。 在模拟过程中,可以使用以下工具栏按钮来调整模拟速度:
在这里插入图片描述

  以某种方式调整模拟速度,以使初始模拟时间步长永远不会增加(例如,这可能因此导致机制中断)。 以下两个图说明了模拟速度调整机制:
在这里插入图片描述

默认情况下,每个模拟周期由以下顺序操作组成:

  • 执行主脚本
  • 渲染场景

线程渲染

  渲染操作将始终增加仿真周期的持续时间,从而也降低了仿真速度。可以定义每个场景渲染的主脚本执行次数(请参阅后面的内容),但这在某些情况下还不够,因为渲染仍然会减慢每个第x个仿真周期的时间(这可能会限制实时性)。在这种情况下,可以通过用户设置或以下工具栏按钮激活线程渲染模式:
在这里插入图片描述

  激活线程渲染模式后,模拟周期将仅包括执行主脚本,因此模拟将以最大速度运行。渲染将通过不同的线程进行,并且不会减慢模拟任务的速度。然而,必须考虑缺点。激活线程渲染后:

  • 渲染将与仿真循环异步进行,并且可能会出现视觉故障
  • 录像机将无法以恒定速度运行(可能会跳过某些帧)
  • 应用程序的稳定性可能会降低
  • 某些操作(例如,擦除对象等)需要等待渲染线程完成工作,然后才能执行,反之亦然。在那些情况下,循环可能比顺序渲染模式花费更多的时间。

7.1 仿真对话框

  仿真对话框可以通过[菜单栏->仿真->仿真设置]或单击以下工具栏按钮来访问:
在这里插入图片描述

  • 时间步长:模拟时间步长。每次执行主脚本时,模拟时间都会增加一个模拟时间步长。使用较大的时间步长会导致快速但不准确/不稳定的仿真。另一方面,较小的时间步长(通常)会导致更精确的仿真,但会花费更多时间。强烈建议保留默认时间步长。
  • 每帧模拟遍数(ppf):一个渲染过程的模拟过程数。值为10表示刷新屏幕之前,主脚本已执行10次(10个模拟步骤)。如果您的图形卡较慢,则可以选择仅显示两幅中的一幅。
  • 当仿真时间高于以下时间时暂停:允许指定暂停仿真的仿真时间(例如,能够在特定仿真时间分析某些结果)。
  • 脚本错误暂停:如果启用,则在脚本错误发生时暂停仿真。
  • 模拟开始时全屏:如果启用,则模拟以全屏模式开始。请注意,在全屏模式下,对话框和消息将不会出现或不可见,只有鼠标左键处于活动状态。因此,仅在正确配置场景并最终确定场景后才建议使用该模式。可以使用esc键保留全屏模式,并在仿真过程中通过布尔参数sim_booparam_fullscreen进行切换。 Linux和MacOS可能仅部分支持全屏模式,并且在某些系统上切换回普通模式可能会失败。
  • 实时仿真,倍增因子:如果选择,则仿真时间将尝试跟随实时。 X的乘数将使仿真运行比实时快X倍。
  • 在落后时尝试赶上:在实时仿真过程中,仿真时间可能无法实时跟踪(例如,由于某些瞬时繁重的计算)。在这种情况下,如果选中此复选框,则模拟时间将尝试赶上损失的时间(例如,当计算负荷再次减少时),从而明显加快速度。
  • 将场景重置为初始状态:选中后,所有对象都将重置为其初始状态:这包括对象的局部位置,局部方向及其父对象(只要未进行其他修改(例如,缩放),以及路径的固有位置,浮动视图的位置和大小等。这意味着除非进行了重大更改(形状缩放,对象移除等),否则下一次模拟运行将以与上一次相同的方式执行。此项目将忽略一些次要设置。
  • 删除新对象:选中后,在仿真运行期间添加的场景对象将在仿真结束时被删除。

8. 其他

8.1 视频录制

  CoppeliaSim具有嵌入式视频记录器,允许用户将页面,场景层次结构和模型浏览器区域记录为视频文件(单击此处和此处获取与视频记录功能相关的字幕)。不记录对话框,菜单栏,工具栏和弹出菜单。默认情况下,录像机将记录每个渲染的帧并生成每秒30帧的电影。请记住,根据所使用的编解码器,录制过程可能会大大降低模拟速度。可以通过[菜单栏->工具->录像机]或单击以下工具栏按钮来访问录像机对话框:
在这里插入图片描述

  • 在下一次模拟开始时启动:下次按下模拟开始按钮时将启动记录过程。
  • 立即录制:立即开始录制视频文件。按停止录制!结束录制过程。
  • 记录窗口内容:专门记录CoppeliaSim主窗口的内容(不记录任何对话框)。
  • 记录桌面内容:记录桌面内容。对于具有多个监视器的系统,可以通过文件system / usrset.txt中的变量desktopRecordingIndex指定桌面索引。
  • 显示光标:如果启用,则光标将在视频文件中可见。
  • 显示按钮状态:如果启用,则鼠标按钮状态以及ctrl和Shift键状态将在视频文件中可见。
  • 隐藏信息文本:如果启用,则主视图顶部的覆盖文本将不可见。
  • 选择:允许选择视频文件存放位置。
  • 输出类型:将用于视频的编码器/容器。提供的选择取决于系统上的编码器。安装视频播放器(例如VLC)通常会有所帮助,视频播放器会自动安装一些其他编码器。
  • X显示的帧产生1个记录的帧:允许指定应记录的帧。
  • 自动帧频:将以某种方式调整帧频,以使x模拟秒对应于x视频秒。
  • 帧频:指示视频文件在一秒钟内将显示的帧数。

8.2 URDF导入插件

  URDF文件格式(* .urdf)通过Ignacio Tartavull的插件cosutey实现导入操作:simExtUrdf。如果正确加载了URDF插件,则可以通过[菜单栏->插件-> URDF导入…]访问插件对话框:
在这里插入图片描述

  • 将碰撞链接分配给第9层:隐藏第9层中的碰撞链接(默认情况下第9层处于关闭状态)。
  • 将关节分配给第10层:隐藏第10层中的关节(默认情况下第10层处于禁用状态)。
  • 凸分解非凸碰撞链接:将非凸可响应形状分解为凸形状。由于在动态碰撞响应计算过程中凸形的执行速度更快且更稳定,因此应始终检查此项。
  • 显示凸分解对话框:允许调整凸分解的操作方式。
  • 如果没有,则创建视觉链接:如果链接未定义一个视觉元素,则创建一个人造视觉元素。
  • 将模型置于地面上方:将导入的模型置于地面上方。
  • 如果可行,请准备模型定义:将根对象标记为“模型库”,并相应地调整其他对象(例如,单击模型中的对象将选择整个模型,而不是单个对象)。
  • 备用局部响应蒙版:选中此选项后,将交替使用局部碰撞蒙版,以使相邻的响应形状不会对碰撞产生反应。如果不加约束,则所有可响应形状只会对与模型外部对象的碰撞做出反应。
  • 启用旋转和棱柱关节的位置控制:如果选中,则旋转和棱柱关节将处于位置控制模式。联合力/扭矩和上调速度取自URDF文件。
  • 导入:触发导入操作。

8.3 SDF导入插件

  支持SDF文件格式(* .sdf)通过插件simExtSDF进行导入操作。如果正确加载了SDF插件,则可以通过[菜单栏->插件-> SDF导入…]访问导入操作和对话框:
在这里插入图片描述

  • 忽略缺少的必需值(SDF解析器):忽略缺少的必需元素(如果不遵守某些格式规范)。
  • 将碰撞链接分配给第9层:隐藏第9层中的碰撞链接(默认情况下第9层处于关闭状态)。
  • 将关节分配给第10层:隐藏第10层中的关节(默认情况下第10层处于禁用状态)。
  • 凸分解非凸碰撞链接:将非凸可响应形状分解为凸形状。由于在动态碰撞响应计算过程中凸形的执行速度更快且更稳定,因此应始终检查此项。
  • 显示凸分解对话框:允许调整凸分解的操作方式。
  • 如果没有,则创建视觉链接:如果链接未定义一个视觉元素,则创建一个人造视觉元素。
  • 将模型置于地面上方:将导入的模型置于地面上方。
  • 如果可行,请准备模型定义:将根对象标记为“模型库”,并相应地调整其他对象(例如,单击模型中的对象将选择整个模型,而不是单个对象)。
  • 备用局部响应蒙版:选中此选项后,将交替使用局部碰撞蒙版,以使相邻的响应形状不会对碰撞产生反应。如果不加约束,则所有可响应形状只会对与模型外部对象的碰撞做出反应。
  • 启用旋转和棱柱关节的位置控制:如果选中,则旋转和棱柱关节将处于位置控制模式。联合力/扭矩和上调节速度取自SDF文件。
  • 导入:触发导入操作。

8.4 Collada导入/导出插件

  支持collada文件格式(* .dae)通过插件simExtCollada进行导入/导出操作。 当前,仅三角形/多边形网格被导入/导出。 将来的版本还将支持读/写与物理相关的信息。 如果collada插件已正确加载,则可以通过[插件-> COLLADA导入/导出…]访问插件对话框:
在这里插入图片描述

  • 合并导入的网格:如果选中,则所有导入的网格将合并为一个简单的形状。如果collada文件包含太多单独的网格(例如,超过1000-2000个),则此选项很有用。
  • 仅导入网格:默认情况下,插件会导入所有可用节点:网格变为形状,变换变为虚拟,旋转变为旋转关节,平移变为棱柱形关节。为了仅导入网格,请选中此项目(在这种情况下,将不保留节点层次结构)。
  • 取消组合形状的分组:如果启用,则组合网格将表示为单个简单形状。
  • 缩放因子(collada导入):导入操作期间使用的缩放因子。例如,如果collada文件中的网格以mm表示,并且将缩放系数保持为1,则所有内容将显得太大,甚至在场景视图中根本不会出现。
  • 导入:从collada文件导入数据。
  • 导出动画:如果启用,则每个渲染的场景将在模拟过程中作为不同的场景导出。该文件将在模拟结束时写入。
  • 仅导出形状:默认情况下,所有可见对象都将被导出为collada文件中的节点。选中此项目将仅导出形状。
  • 比例因子(collada导出):在导出操作期间使用的比例因子。
  • 导出:导出包含所有可见对象的单个场景。

8.5 Lua速成班

8.6 XML格式

CoppeliaSim支持两种不同的XML格式,每种格式都有不同的目标:

  • 穷举格式:穷举格式是无损格式,这意味着在保存和加载操作之间,所有场景或模型信息都是相同的。但是,缺点是不应手动修改文件,因为损坏文件的风险很高。例如,此格式非常适合版本控制。
  • 简单格式:简单格式是有损格式,这意味着将不会保存场景或模型中包含的所有信息,而只会保存其中的一部分。但是,优点是可以手动创建和/或修改它。此格式是创建初始模型或在不同应用程序中创建CoppeliaSim导出器的理想选择。

两种格式都有两个变量(在文件系统/usrset.txt中定义),可以控制输出XML文件:

  • xmlExportSplitSize:表示子集数据大小阈值,在该阈值中将引用数据并将其写入单独的文件。如果要创建单个文件,请设置为0。
  • xmlExportKnownFormats:如果为true,则导出生成多个文件,然后使用图像的png格式和网格的dae格式。但是,仅简单xml格式支持dae格式。

9. 教程

9.1 BubbleRob教程

  本教程将在设计简单的移动机器人BubbleRob时尝试介绍很多CoppeliaSim功能。 与本教程相关的CoppeliaSim场景文件位于CoppeliaSim的安装文件夹的tutorials / BubbleRob文件夹中。 下图说明了我们将设计的仿真场景:

在这里插入图片描述

  由于本教程将跨越许多不同的方面,因此请确保也看看其他教程,主要是有关构建仿真模型的教程。首先,启动CoppeliaSim。仿真器显示默认场景。我们将从BubbleRob的主体开始。

  使用[菜单栏->添加->基本形状->球体]向场景中添加直径为0.2的基本球体。将X大小调整为0.2,然后单击“确定”。默认情况下,创建的球体将显示在可见性图层1中,并且是动态且可响应的(因为已启用“创建动态且可响应的形状”项)。这意味着BubbleRob的身体会掉落,并且能够对与其他可响应形状的碰撞做出反应(即由物理引擎仿真)。我们可以看到这是形状动力学属性:启用了“身体可响应”和“身体是动态”项。开始仿真(通过工具栏按钮,或在场景窗口中按<control-space>),然后复制并粘贴创建的球体(使用[菜单栏->编辑->复制所选对象],然后[菜单栏->编辑->粘贴缓冲区],或先按<control-c>,再按<control-v>):这两个球将对碰撞做出反应并滚动。停止仿真:重复的球体将自动删除。可以在模拟对话框中修改此默认行为。

  我们还希望BubbleRob的主体可以被其他计算模块(例如最小距离计算模块)使用。因此,如果尚未启用,则可以在该形状的对象公共属性中启用Collidable、Measurable、Renderable和Detectable。如果需要,现在还可以在形状属性中更改球体的视觉外观。

  现在,打开“位置”对话框的“平移”选项,选择表示BubbleRob身体的球体,然后“沿着Z”输入0.02。确保将“相对对象”设置为“世界”。然后我们点击平移选项。这会将所有选定对象沿绝对Z轴平移2 cm,并有效地将球体抬高了一点。在场景层次结构中,双击球体的名称,以便可以编辑其名称。输入bubbleRob,然后按回车确认。

  接下来,将添加一个接近传感器,以便BubbleRob知道它何时接近障碍物:选择[菜单栏->添加->接近传感器->圆锥类型]。在“方向”对话框的“旋转”选项卡上,“Around Y”和“Around Z”输入90,然后单击“旋转选择”(其实旋转顺序是Z-Y-X)。在位置对话框的“位置”选项卡上,X坐标输入0.1, Z坐标为0.12。现在,接近传感器已相对于BubbleRob的身体正确定位。我们在场景层次中双击接近传感器的图标以打开其属性对话框。单击显示体积参数以打开接近传感器体积对话框。将Offset调整为0.005,Angle调整为30,Range调整为0.15。然后,在接近传感器属性中,单击“显示检测参数”。这将打开接近传感器检测参数对话框。取消选中“如果距离小于0.1,不允许检测”项,然后再次关闭该对话框。在场景层次中,双击接近传感器的名称,可以编辑其名称。输入bubbleRob_sensingNose并按回车键。

  选择bubbleRob_sensingNose,然后按住Control键选择bubbleRob,然后单击[菜单栏->编辑->将最后选择的对象设为父对象]。这会将传感器连接到机器人的身体。还可以将bubbleRob_sensingNose拖动到场景层次中的bubbleRob上。这就是现在得到的:

在这里插入图片描述

  接下来,我们将关注BubbleRob的车轮。使用[菜单栏->文件->新场景]创建一个新场景。通常,跨多个场景工作非常方便,以便可视化并仅对特定元素进行工作。添加一个尺寸为(0.08,0.08,0.02)的纯原始圆柱体。对于BubbleRob的主体,如果尚未启用,则在该圆柱的对象公共属性中启用Collidable、Measurable、Renderable和Detectable。然后,将圆柱的绝对位置设置为(0.05,0.1,0.04),并将其绝对方向设置为(-90,0,0)。名称更改为bubbleRob_leftWheel。复制并粘贴滚轮,然后将副本绝对Y坐标设置为-0.1,重命名为bubbleRob_rightWheel。选择两个轮子,复制,然后切换回场景1,粘贴轮子。

  现在,需要为车轮添加接头(或电机)。单击[菜单栏->添加->关节->旋转]将旋转关节添加到场景。在大多数情况下,将新对象添加到场景时,该对象将出现在世界的原点处。保持关节处于选中状态,然后按住Control并选择bubbleRob_leftWheel。在位置对话框的“位置”选项卡上,单击“应用到选择”按钮:这将关节定位在左轮的中心。然后,在“方向”对话框中的“方向”选项卡上,执行相同的操作:这将关节与左轮定向的方向相同。将关节重命名为bubbleRob_leftMotor。现在,在场景层次中双击关节的图标以打开关节属性对话框。然后,单击“显示动态参数”以打开关节动力学属性对话框。启用电动机,并选中项目“目标速度为零时锁定电动机”。现在,对右马达重复相同的过程,并将其重命名为bubbleRob_rightMotor。现在,将左轮连接到左马达,将右轮连接到右马达,然后将两个马达连接到bubbleRob。这就是得到的:

在这里插入图片描述

  运行仿真,发现机器人向后倒下。我们仍然缺少与地板的第三个接触点。现在,添加一个小滑块(或脚轮)。在一个新场景中,添加一个直径为0.05的纯原始球体,并使该球体可碰撞、可测量、可渲染和可检测(如果尚未启用),然后将其重命名为bubbleRob_slider。在形状动力学属性中将Material设置为noFrictionMaterial。为了将滑块与机器人的其余部分牢固地链接在一起,使用[菜单栏->添加->力传感器]添加了力传感器对象。将其重命名为bubbleRob_connection并将其上移0.05。将滑块连接到力传感器,然后复制两个对象,切换回场景1并粘贴它们。然后,将力传感器沿绝对X轴移动-0.07,然后将其安装到机器人主体上。如果现在运行模拟,会注意到滑块相对于机器人主体略微移动:这是因为两个对象(即bubbleRob_slider和bubbleRob)彼此碰撞。为了避免在动力学模拟过程中产生奇怪的影响,必须通知CoppeliaSim两个对象不会相互运行碰撞,可以通过以下方式进行此操作:在形状动力学属性中,对于bubbleRob_slider,将本地可响应蒙版设置为00001111,对于bubbleRob,将本地可响应掩码设置为11110000。如果再次运行仿真,我们会注意到两个对象不再相互干扰。这就是现在得到的:

在这里插入图片描述

  再次运行仿真,发现即使在电机锁定的情况下,BubbleRob也会轻微移动。我们还尝试使用不同的物理引擎运行仿真:结果将有所不同。动态模拟的稳定性与所涉及的非静态形状的质量和惯性紧密相关。有关此效果的说明,请务必仔细阅读本节和该节。现在,我们尝试纠正这种不良影响。选择两个轮子和滑块,然后在“形状动力学”对话框中单击3次M = M * 2(用于选择)。结果是所有选定形状的质量都将乘以8。对3个选定形状的惯性进行相同的操作,然后再次运行仿真:稳定性得到了改善。在关节动力学对话框中,将两个电机的目标速度都设置为50。运行仿真:BubbleRob现在向前移动并最终从地板掉落。将两个电机的目标速度重置为零。
  对象bubbleRob位于所有稍后将形成BubbleRob模型的对象的基础上。我们将在稍后定义模型。同时,我们要定义代表BubbleRob的对象的集合。为此,我们定义了一个集合对象。单击[菜单栏->工具->集合]以打开集合对话框。或者,也可以通过单击相应的工具栏按钮来打开对话框:

在这里插入图片描述

  在集合对话框中,单击添加新集合。 一个新的集合对象出现在下面的列表中。 目前,新添加的集合仍为空(未定义)。 在列表中选择新的集合项时,在场景层次中选择bubbleRob,然后在集合对话框中单击“添加”。 现在,我们的集合被定义为包含从bubbleRob对象开始的层次结构树的所有对象(集合的组成显示在“组成元素和属性”部分中)。 要编辑集合名称,请双击它,并将其重命名为bubbleRob_collection。 关闭收集对话框。
  在此阶段,我们希望能够跟踪BubbleRob与任何其他对象之间的最小距离。 为此,使用[菜单栏->工具->计算模块属性]打开距离对话框。 或者,也可以使用相应的工具栏按钮打开计算模块属性对话框:

在这里插入图片描述

  在距离对话框中,单击“添加新的距离对象”并选择一个距离对:[collection] bubbleRob_collection- all other measurable objects in the scene。这只是添加了一个距离对象,该距离对象将测量集合bubbleRob_collection(即该集合中的任何可测量对象)与场景中任何其他可测量对象之间的最小距离。通过双击其名称将距离对象重命名为bubbleRob_distance。关闭距离对话框。现在,当运行仿真时,不会看到任何区别,因为距离对象将尝试测量(并显示)BubbleRob与场景中任何其他可测量对象之间的最小距离段。问题在于,在此阶段场景中没有其他可测量的对象(定义地板的形状默认情况下已禁用其可测量的属性)。在本教程的后续阶段,我们将为场景添加障碍。
  接下来,将向BubbleRob添加一个图形对象,以显示以上最小距离,同时还显示BubbleRob随时间的轨迹。单击[菜单栏->添加->图],并将其重命名为bubbleRob_graph。将图形附加到bubbleRob,并将图形的绝对坐标设置为(0,0,0.005)。现在,通过在场景层次结构中双击其图标来打开图形属性对话框。取消选中“显示XYZ平面”,然后单击“添加新数据流以进行记录”,然后选择“对象:数据流类型的绝对x位置”,并选择“ bubbleRob_graph”作为要记录的对象/项目。数据流记录列表中出现了一个项目。该项目是bubbleRob_graph的绝对x坐标的数据流(即,将记录bubbleRobGraph的对象的绝对x位置)。现在,我们还想记录y和z位置:我们以与上述类似的方式添加这些数据流。现在,我们有3个数据流,分别表示BubbleRob的x,y和z轨迹。我们将再添加一个数据流,以便能够跟踪机器人与其环境之间的最小距离:单击“添加新数据流”进行记录,然后选择“距离:数据流类型的段长度”和“bubbleRob_distance”作为要记录的对象/项目。在数据流记录列表中,现在将Data重命名为bubbleRob_x_pos,将Data0重命名为bubbleRob_y_pos,将Data1重命名为bubbleRob_z_pos,将Data2重命名为bubbleRob_obstacle_dist。

  我们在“数据流记录列表”中选择bubbleRob_x_pos,在“时间图属性”部分中,取消选中“可见”。对bubbleRob_y_pos和bubbleRob_z_pos都执行相同的操作。这样,在时间图中只能看到bubbleRob_obstacle_dist数据流。以下是应该得到的:

在这里插入图片描述

  接下来,将建立一个显示BubbleRob轨迹的3D曲线:单击“编辑3D曲线”以打开XY图形和3D曲线对话框,然后单击“添加新曲线”。 在弹出的对话框中,将X值选择bubbleRob_x_pos,Y值选择bubbleRob_y_pos,Z值选择bubbleRob_z_pos。 将新添加的曲线从Curve重命名为bubbleRob_path。 最后,我们检查“相对于世界”项目并将“曲线宽度”设置为4:

在这里插入图片描述

  关闭与图有关的所有对话框。 现在将一个电机目标速度设置为50,运行仿真,然后将看到BubbleRob的轨迹显示在场景中。 然后,停止仿真并将电动机目标速度重置为零。

  添加具有以下尺寸的纯原始圆柱体:(0.1, 0.1, 0.2)。 我们希望此圆柱体是静态的(即不受重力或碰撞的影响),但仍会对非静态的可响应形状施加一些碰撞响应。 为此,在形状动力学属性中禁用“主体是动态的”。 我们还希望圆柱体是可碰撞的、可测量的、可渲染的和可检测的。 在对象的公共属性中执行此操作。 现在,在仍然选择圆柱体的情况下,我们单击对象平移工具栏按钮:

在这里插入图片描述

  现在可以拖动场景中的任何点:圆柱体将跟随运动,同时始终受约束以保持相同的Z坐标。 将圆柱体复制并粘贴几次,然后将其移动到BubbleRob周围的位置(从顶部查看场景时执行该操作最方便)。 在对象移动期间,按住Shift键可以执行较小的移动步骤。 按住ctrl键可以在与常规方向正交的方向上移动。 完成后,再次选择相机平移工具栏按钮:

在这里插入图片描述

  将左侧电机的目标速度设置为50并运行仿真:现在,图形视图显示了到最近障碍物的距离,并且距离段在场景中也可见。 停止仿真并将目标速度重置为零。

  现在,我们需要完成BubbleRob作为模型定义。 选择模型基础(即对象bubbleRob),然后选中“对象是模型基础”,然后选择”对象/模型可以转移或接受对象共同属性中的DNA“:现在有一个点状边界框,它包含模型层次结构中的所有对象。选择两个关节、接近传感器和图形,然后勾选Don’t show as inside model selection,然后在同一对话框中单击“应用于选择”( 注: 在CoppeliaSim中已经改成 Ignored by model bounding box,且无需Apply to selection):模型边界框现在将忽略两个关节和接近传感器。 仍然在同一对话框中,禁用摄像机可见性层2,并为两个关节和力传感器启用摄像机可见性层10:这有效地隐藏了两个关节和力传感器,因为默认情况下禁用了9-16层。 我们可以随时修改整个场景的可见性层。 为了完成模型定义,选择接近传感器、两个轮子、滑块和图形,然后启用“Select base of model instead”选项:如果现在尝试在场景中选择模型中的对象,则整个模型将被选择,这是一种将整个模型作为单个对象进行处理和操纵的便捷方法。此外,这可以防止模型受到意外修改。 仍然可以在场景中选择模型中的单个对象,方法是通过按住Shift的方式单击选择它们,或者在场景层次中选择它们。 最后,将模型树折叠到场景层次中。 这就是得到的:

在这里插入图片描述

  接下来,将在与BubbleRob接近传感器相同的位置和方向上添加视觉传感器。 再次打开模型层次结构,然后单击[菜单栏->添加->视觉传感器->透视类型],然后将视觉传感器连接到接近传感器,并将视觉传感器的相对接近传感器的位置和方向设置为(0,0,0)。 我们还确保视觉传感器不是不可见,不是模型边界框的一部分,并且如果单击该模型,则会选择整个模型。 为了自定义视觉传感器,打开其属性对话框。 将“远裁剪平面”项设置为1,将“分辨率x”和“分辨率y”项设置为256和256。向场景中添加一个浮动视图,并在新添加的浮动视图上,右键单击[弹出菜单->视图 ->将视图与选定的视觉传感器关联](确保在该过程中选择了视觉传感器)。

  通过单击[菜单栏->添加->关联的子脚本->非线程],将非线程的子脚本附加到视觉传感器。 双击场景层次结构中视觉传感器旁边出现的小图标,这将打开刚刚添加的子脚本。 我们将以下代码复制并粘贴到脚本编辑器中,然后将其关闭:

function sysCall_vision(inData)
    simVision.sensorImgToWorkImg(inData.handle) -- copy the vision sensor image to the work image
    simVision.edgeDetectionOnWorkImg(inData.handle,0.2) -- perform edge detection on the work image
    simVision.workImgToSensorImg(inData.handle) -- copy the work image to the vision sensor image buffer
end

function sysCall_init()
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

  为了能够看到视觉传感器的图像,开始仿真,然后再次停止。

  我们场景所需的最后一件事是一个小的子脚本,它将控制BubbleRob的行为。 选择bubbleRob并单击[菜单栏->添加->关联的子脚本->非线程]。 双击场景层次结构中bubbleRob名称旁边显示的脚本图标,然后将以下代码复制并粘贴到脚本编辑器中,然后将其关闭:

function speedChange_callback(ui,id,newVal)
    speed=minMaxSpeed[1]+(minMaxSpeed[2]-minMaxSpeed[1])*newVal/100
end

function sysCall_init()
    -- This is executed exactly once, the first time this script is executed
    bubbleRobBase=sim.getObjectAssociatedWithScript(sim.handle_self) -- this is bubbleRob's handle
    leftMotor=sim.getObjectHandle("bubbleRob_leftMotor") -- Handle of the left motor
    rightMotor=sim.getObjectHandle("bubbleRob_rightMotor") -- Handle of the right motor
    noseSensor=sim.getObjectHandle("bubbleRob_sensingNose") -- Handle of the proximity sensor
    minMaxSpeed={50*math.pi/180,300*math.pi/180} -- Min and max speeds for each motor
    backUntilTime=-1 -- Tells whether bubbleRob is in forward or backward mode
    -- Create the custom UI:
        xml = '<ui title="'..sim.getObjectName(bubbleRobBase)..' speed" closeable="false" resizeable="false" activate="false">'..[[
        <hslider minimum="0" maximum="100" οnchange="speedChange_callback" id="1"/>
        <label text="" style="* {margin-left: 300px;}"/>
        </ui>
        ]]
    ui=simUI.create(xml)
    speed=(minMaxSpeed[1]+minMaxSpeed[2])*0.5
    simUI.setSliderValue(ui,1,100*(speed-minMaxSpeed[1])/(minMaxSpeed[2]-minMaxSpeed[1]))
end

function sysCall_actuation()
    result=sim.readProximitySensor(noseSensor) -- Read the proximity sensor
    -- If we detected something, we set the backward mode:
    if (result>0) then backUntilTime=sim.getSimulationTime()+4 end 

    if (backUntilTime<sim.getSimulationTime()) then
        -- When in forward mode, we simply move forward at the desired speed
        sim.setJointTargetVelocity(leftMotor,speed)
        sim.setJointTargetVelocity(rightMotor,speed)
    else
        -- When in backward mode, we simply backup in a curve at reduced speed
        sim.setJointTargetVelocity(leftMotor,-speed/2)
        sim.setJointTargetVelocity(rightMotor,-speed/8)
    end
end

function sysCall_cleanup()
	simUI.destroy(ui)
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

  运行仿真。 现在,BubbleRob会前进,同时尝试避开障碍物(非常基本的方式)。 在仿真仍在运行时,更改BubbleRob的速度,然后将其复制/粘贴几次。 在仿真仍在运行时,也尝试扩展其中的一些。 请注意,根据环境的不同,最小距离计算功能可能会严重降低仿真速度。 您可以通过选中/取消选中“启用所有距离计算”项来在“距离”对话框中打开和关闭该功能。

  使用脚本控制机器人或模型只是一种方法。 CoppeliaSim提供了许多不同的方法(也可以结合使用),请参阅外部控制器教程。

9.2 构建干净的模型教程

  本教程将指导您逐步构建机器人或任何其他项目的清晰仿真模型。为了拥有美观、快速显示、快速仿真和稳定的仿真模型,这是一个非常重要的主题,也许是最重要的方面。

  为了说明模型的构建过程,我们将构建以下机械臂:

在这里插入图片描述

构建可见的形状

  在构建新模型时,首先,我们仅处理它的视觉方面:动态方面(其不讨人喜欢的地方更加简化了)/优化模型)、关节、传感器等,将在稍后阶段进行处理。

  现在,可以使用[菜单栏->添加->基本形状-> …]在CoppeliaSim中直接创建基本形状。这样做时,可以选择创建纯形状或常规形状。纯形状将针对动态互动进行优化,也可以直接动态启用(例如,跌落、碰撞,但可以在以后禁用)。基本形状将是简单的网格,对于我们的应用程序,可能没有足够的细节或几何精度。在这种情况下,我们的另一个选择是从外部应用程序导入网格。

  从外部应用程序导入CAD数据时,最重要的是确保CAD模型不会太重,即不包含太多三角形。这项要求很重要,因为重型模型的显示速度很慢,而且还会减慢以后可能使用的各种计算模块(例如最小距离计算或动力学)。以下示例通常是不可行的(即使有,如我们稍后将看到的那样,也有一些方法可以简化CoppeliaSim中的数据):

在这里插入图片描述

  CAD数据上方非常繁重:它包含许多三角形(大于47’000),如果我们只在空的场景中使用单个实例,就可以了。但是大多数时候,您将需要模拟同一机器人的多个实例,安装各种类型的抓手,并可能使这些机器人与其他机器人,设备或环境进行交互。在这种情况下,模拟场景可能很快变得太慢。通常,我们建议对不超过2万个三角形的机器人进行建模,但是在大多数情况下,5000至10000个三角形也可以。请记住:几乎在所有方面,少即是好。

  是什么使上述模型如此沉重?首先,包含孔和小细节的模型将需要更多的三角形面才能正确表示。因此,如果可能,请尝试从原始模型数据中删除所有的孔,螺钉,物体的内部等。如果您将原始模型数据表示为参数化曲面/对象,则通常在大多数情况下只需选择并删除它们即可(例如在Solidworks中)。第二个重要步骤是以有限的精度导出原始数据:大多数CAD应用程序都允许您指定导出的网格的细节级别。当工程图由大小的对象组成时,分几步导出对象可能也很重要。这是为了避免大对象定义太精确(三角形太多)和小对象定义太粗(三角形太少):先简单地导出大对象(通过调整所需的精度设置),然后导出小对象(通过调整精度设置)。

  CoppeliaSim当前支持以下CAD数据格式:OBJ、STL、DXF、3DS(仅Windows)和Collada。还支持URDF,但由于它不是基于纯网格的文件格式,因此此处未提及。

  现在,假设我们已经按照上一节中的描述进行了所有可能的简化。导入后,我们可能最终仍然会留下一个过重的网格:

在这里插入图片描述

  您会注意到整个机器人都是作为单个网格导入的。稍后我们将看到如何对其进行适当划分。还要注意导入的网格的方向错误:最好保持其方向不变,直到构建整个模型为止,因为如果在以后的阶段中我们要导入与同一机器人相关的其他项目,它们将自动具有相对于原始网格的正确位置/方向。

在此阶段,我们可以使用几种功能来简化网格:

  • 自动网格划分:允许为未通过公共边链接在一起的所有元素生成新形状。这并不总是适用于选定的网格,但是总是值得一试的,因为与必须同时处理所有元素相比,处理网格元素可以为我们提供更多的控制权。可以通过[菜单栏->编辑->分组/合并->分割所选形状]访问该功能。有时,网格划分会超出预期。在那种情况下,只需将逻辑上属于一起的元素(即,具有相同的视觉属性并且属于同一链接的一部分)合并回一个单一形状([菜单栏->编辑->分组/合并->合并选定的形状])。
  • 提取凸包:通过将其转换为凸包来简化网格。可以通过[菜单栏->编辑->将选择变形为凸形]来访问该功能。
  • 抽取网格:减少网格中包含的三角形数量。可以通过[菜单栏->编辑->缩小所选形状…]访问该功能。
  • 删除网格的内部:允许通过删除其内部来简化网格。此功能基于视觉传感器,根据所选设置可能会或多或少地令人满意。可以通过[菜单栏->编辑->提取选定形状的内部]访问该功能。

  没有/可以应用上述功能的预定义顺序(列表中的第一项除外,应始终首先尝试该项),这在很大程度上取决于我们要简化的网格的几何形状。下图说明了应用于导入的网格的上述函数(假设列表中的第一项对我们不起作用):

在这里插入图片描述

  注意,凸包在现阶段是如何对我们没有帮助的。我们决定首先使用网格抽取功能,然后运行两次该功能,以将三角形的数量除以总共50个。完成后,我们提取简化形状的内部并将其丢弃。我们最终得到的网格总共包含2’660个三角形(原始导入的网格包含超过136’000个三角形!)。形状包含的三角形/顶点的数量可以在形状几何对话框中看到。对于整个机器人模型,2’660三角形是极少的三角形,因此视觉外观可能会因此受到影响。

  在这一阶段,我们可以开始将机器人划分为单独的链接(请记住,我们目前整个机器人只有一个形状)。您可以通过两种不同的方式执行此操作:

  • 自动网格划分:此功能已在上一节中进行了描述,它将检查形状并为未通过公共边链接在一起的所有元素生成新形状。这并不总是有效,但总是值得尝试的。可以通过[菜单栏->编辑->分组/合并->分割所选形状]访问该功能。
  • 手动网格划分:通过三角形编辑模式,您可以手动选择逻辑上不属于逻辑的三角形,然后单击“提取形状”。这将在场景中生成新形状。完成该操作后,删除选定的三角形。

对于我们的网格,方法1可以正常工作:

在这里插入图片描述

9.3 BubbleRob巡线教程

  在本教程中,我们旨在扩展BubbleRob的功能,以使它跟随地面上的曲线。 确保您已经阅读并理解了第一个BubbleRob教程。 本教程由Eric Rohmer提供。

  加载CoppeliaSim安装目录下的tutorials / BubbleRob文件夹中第一个BubbleRob教程的场景。 与本教程相关的场景文件位于tutorials / LineFollowingBubbleRob中。 下图说明了我们将设计的仿真场景:

在这里插入图片描述

  首先,创建3个视觉传感器,并将其附加到bubbleRob对象。 选择[菜单栏->添加->视觉传感器->正交类型]。 通过双击场景层次中新创建的视觉传感器图标来编辑其属性,并更改参数为以下对话框:

在这里插入图片描述

  视觉传感器必须面向地面,因此选择它,然后在“方向”对话框中的“方向”选项卡上,将“ Alpha-Beta-Gamma”项设置为[180; 0; 0]。

  我们有几种可能性可以读取视觉传感器。 由于视觉传感器只有一个像素,并且操作简单,因此只需查询视觉传感器读取的图像的平均强度值即可。 对于更复杂的情况,可以设置视觉回调函数。 现在,将视觉传感器复制并粘贴两次,并将其名称调整为leftSensor、middleSensor和rightSensor。 将bubbleRob设为父级(即,将其附加到bubbleRob对象)。 现在,您的传感器在场景层次中应如下所示:

在这里插入图片描述

  让我们正确放置传感器。 为此,请使用位置对话框,在位置选项卡上,并设置以下绝对坐标:

  • 左传感器:[0.2; 0.042; 0.018]
  • 中间传感器:[0.2; 0; 0.018]
  • 右传感器:[0.2; -0.042; 0.018]

  现在让我们修改环境。 我们可以移去BubbleRob前面的几个圆柱体。 接下来,我们将构建机器人将尝试遵循的路径。 从现在开始最好切换到顶视图:通过页面选择器工具栏按钮选择页面4。 然后单击[菜单栏->添加->路径->圆圈类型]。 使用鼠标启用对象移动。 您可以通过两种方式调整路径的形状:

  • 选择路径(并且只有路径)后,按住Ctrl并单击其控制点之一。 然后可以将它们拖到正确的位置。
  • 选择路径后,进入路径编辑模式。 在这里,您可以灵活地调整各个路径控制点。

  一旦对路径的几何形状满意(可以随时在以后修改),请选择它,然后取消选中路径属性中的“显示点的方向”,“显示路径线”和“显示路径上的当前位置”。 然后单击显示路径形状对话框。 这将打开路径形状对话框。 单击启用路径形状,将类型设置为水平线段,并将缩放比例设置为4.0。 最后将颜色调整为黑色。 我们必须对路径进行最后的重要调整:当前,路径的z位置与地板的z位置重合。 结果是,有时我们会看到路径,有时会看到地板(这种效果在openGL行话中被称为“ z-fighting”)。 这不仅影响我们所看到的,而且还会影响视觉传感器所看到的。 为了避免与z-fighting有关的问题,只需将路径对象的位置向上移动0.5毫米即可。

  最后一步是调整BubbleRob的控制器,使其也将遵循黑色路径。 打开附加到bubbleRob的子脚本,并将其替换为以下代码:

function speedChange_callback(ui,id,newVal)
    speed=minMaxSpeed[1]+(minMaxSpeed[2]-minMaxSpeed[1])*newVal/100
end

function sysCall_init()
    -- This is executed exactly once, the first time this script is executed
    bubbleRobBase=sim.getObjectAssociatedWithScript(sim.handle_self)
    leftMotor=sim.getObjectHandle("leftMotor")
    rightMotor=sim.getObjectHandle("rightMotor")
    noseSensor=sim.getObjectHandle("sensingNose")
    minMaxSpeed={50*math.pi/180,300*math.pi/180}
    backUntilTime=-1 -- Tells whether bubbleRob is in forward or backward mode
    floorSensorHandles={-1,-1,-1}
    floorSensorHandles[1]=sim.getObjectHandle("leftSensor")
    floorSensorHandles[2]=sim.getObjectHandle("middleSensor")
    floorSensorHandles[3]=sim.getObjectHandle("rightSensor")
    -- Create the custom UI:
        xml = '<ui title="'..sim.getObjectName(bubbleRobBase)..' speed" closeable="false" resizeable="false" activate="false">'..[[
        <hslider minimum="0" maximum="100" οnchange="speedChange_callback" id="1"/>
        <label text="" style="* {margin-left: 300px;}"/>
        </ui>
        ]]
    ui=simUI.reate(xml)
    speed=(minMaxSpeed[1]+minMaxSpeed[2])*0.5
    simUI.setSliderValue(ui,1,100*(speed-minMaxSpeed[1])/(minMaxSpeed[2]-minMaxSpeed[1]))
end

function sysCall_actuation()
    result=sim.readProximitySensor(noseSensor)
    if (result>0) then backUntilTime=sim.getSimulationTime()+4 end

    -- read the line detection sensors:
    sensorReading={false,false,false}
    for i=1,3,1 do
        result,data=sim.readVisionSensor(floorSensorHandles[i])
        if (result>=0) then
            sensorReading[i]=(data[11]<0.3) -- data[11] is the average of intensity of the image
        end
        print(sensorReading[i])
    end

    -- compute left and right velocities to follow the detected line:
    rightV=speed
    leftV=speed
    if sensorReading[1] then
        leftV=0.03*speed
    end
    if sensorReading[3] then
        rightV=0.03*speed
    end
    if sensorReading[1] and sensorReading[3] then
        backUntilTime=sim.getSimulationTime()+2
    end

    if (backUntilTime<sim.getSimulationTime()) then
        -- When in forward mode, we simply move forward at the desired speed
        sim.setJointTargetVelocity(leftMotor,leftV)
        sim.setJointTargetVelocity(rightMotor,rightV)
    else
        -- When in backward mode, we simply backup in a curve at reduced speed
        sim.setJointTargetVelocity(leftMotor,-speed/2)
        sim.setJointTargetVelocity(rightMotor,-speed/8)
    end
end

function sysCall_cleanup()
	simUI.destroy(ui)
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68

  您可以轻松地调试以下巡线视觉传感器:选择一个,然后在场景视图中选择[右键->添加->浮动视图],然后在新添加的浮动视图中选择[右键->视图- ->将视图与选定的视觉传感器关联]。

  最后,删除第一个BubbleRob教程中添加的辅助项:删除图像处理视觉传感器,其关联的浮动视图,表示障碍清除的浮动视图。 通过距离对话框也删除距离计算对象。

9.4 逆运动学教程

  本教程介绍了在构建7 自由度冗余机械手时如何使用CoppeliaSim的逆运动学功能。 但是在此之前,请确保在文件夹scenes / ik_fk_simple_examples中查看与IK和FK相关的各种简单示例场景。

  本教程分为四个部分:

  • 建立冗余机械手的简单仿真模型
  • 建立和处理逆运动学任务,方法1(完全通过编程)
  • 建立和处理逆运动学任务,方法2(主要通过GUI)
  • 运行仿真

简单的仿真模型

  对于本教程,我们将构建一个非动态操纵器,该操纵器仅使用逆运动学,而不使用任何物理引擎功能。 与本教程相关的CoppeliaSim CAD数据(redundantManipulator.stl)位于CoppeliaSim的安装文件夹cadFiles中。 与本教程相关的CoppeliaSim场景可以在CoppeliaSim的安装文件夹tutorials / InverseKinematics中找到。 单击[菜单栏->文件->导入->网格…],然后选择要导入的文件。 另请参阅有关如何导入/导出形状的部分。 弹出对话框,其中包含各种导入选项。 单击导入。 导入了一个简单的形状,该形状位于场景的中间。 该形状也出现在主窗口左侧的场景层次中。 根据原始CAD数据的导出方式,导入的CAD数据可以处于不同的比例,位于不同的位置,甚至可以细分为多种形状。 下图显示了导入的形状:

在这里插入图片描述

  如您所见,导入操作给我们留下了一个形状,这是我们预期的几个形状。 这意味着我们必须自己划分操纵器对象:选择对象(只需在场景或场景层次中单击它),然后单击[菜单栏–>编辑–>分组/合并–>分割选定的形状]。 以下是您应该具备的条件:

在这里插入图片描述

  原始图形被分为多个子图形(另请参见场景层次)。 形状分割算法通过对由公共边连接的所有三角形进行分组来操作。 根据原始网格的创建或导出方式,无法执行此类分割过程。 在这种情况下,您必须在三角形编辑模式或外部编辑器中手动提取形状。

  接下来,我们将更改各种对象的颜色,以获得良好的视觉外观。 首先双击场景层次中的形状图标。 此时将打开形状属性对话框。 选择形状时,单击对话框中的调整颜色:这将允许您调整所选形状的各种颜色分量。 现在,只需调整形状的环境光/漫反射颜色分量即可。 要将一个形状的颜色传递到另一个形状,请选择这两个形状,并确保最后选择的形状(用白色边界框指示)是您要从中提取颜色的形状,然后只需单击形状对话框的颜色部分中的应用于选定内容按钮。 上色完成后,可能会出现以下情况:

在这里插入图片描述

  在下一步中,我们将添加操纵器的7个关节。 执行此操作的一种方法是将关节添加到场景中,然后指定它们的适当位置和方向(通过位置对话框和方向对话框)。 但是,当您不知道关节的确切位置(如我们的示例)时,这是不可能的,因此我们必须从我们拥有的形状中提取它们:

  选择所有导入的形状,并点击[Menu bar–>Edit–>Reorient Bound Box–>With Reference Frame of World]。 此操作可确保我们的边界框与绝对参考帧对齐,并且在给定当前操纵器配置的情况下,表示最小的边界框。 点击[菜单栏–>添加–>关节–>旋转]将旋转关节插入到场景中。 默认位置为(0,0,0),其默认方向为垂直,因此关节被操纵器的基础圆柱体隐藏。 在关节仍处于选定状态时,按住Ctrl键并选择基础圆柱体,然后打开位置选项卡上的位置对话框,并单击应用于选择。 这只是将关节定位在与基础圆柱体完全相同的坐标上(但是,此操作仅略微调整了关节的垂直位置,因为它几乎已经就位)。 现在对操纵器中的所有其他关节重复该过程(记住总共应该有7个关节)。 现在所有关节都已就位,但有些关节方向错误。 选择应与世界Y轴对齐的所有关节,然后输入(90,0,0)作为方向对话框中Alpha、Beta和Gamma项的值,然后在方向选项卡上单击应用于选择按钮。 接下来,选择应该与世界X轴对齐的关节,然后为Alpha、Beta和Gamma输入(0,90,0)。 现在,所有关节都具有正确的位置和方向。

  现在可以在关节属性对话框(可以通过双击场景层次中的关节图标打开该对话框)中调整关节大小(检查长度和直径项)。 确保所有关节都清晰可见。 这是你应该拥有的:

在这里插入图片描述

  本教程的下一步是对属于同一刚性实体的形状进行分组。 选择属于链接1的5个形状(基础圆柱体为链接0),然后单击[菜单栏–>编辑–>分组/合并–>分组选定的形状]。 将形状分组为复合形状后,可以将其边界框与世界重新对齐,但此步骤不是必需的(并且仅具有视觉效果)。 对逻辑上属于一起的所有形状重复相同的过程。 在本教程中,我们不会启动手爪的手指,因此只需将它们与最后一个链接进行严格的分组。 当所有要分组的形状共享相同的视觉属性时,请尝试将它们合并在一起:[菜单栏–>编辑–>分组/合并–>合并选定的形状]。

  此时,您可以按以下方式重命名场景中的所有对象(从基础到末端):redundantRobot-redundantRob_joint1-redundantRob_link1-redundantRob_joint2等。只需双击场景层次中的对象名称即可对其进行编辑。

  现在,我们可以构建运动链,从末端到基础:选择对象redundantRob_link7,然后按住Ctrl键选择对象redundantRob_link7,并单击[菜单栏–>编辑–>使最后选定的对象为父项]。 或者,可以将一个对象拖动到场景层次中的另一个对象上,以实现类似的操作。 接下来,对object redundantRob_joint7和object redundantRob_link6执行相同的操作。 以相同的方式继续,直到构建完操纵器的整个运动链。 这是您应该拥有的(请注意场景层次结构):

在这里插入图片描述

  选择所有关节,然后在关节对话框中选择被动模式,然后单击应用于选择。 保持关节处于选定状态,然后打开对象通用属性,并在可见性层区域中,禁用层2并启用层10,然后单击相关的应用于选择按钮。 这只是将所有关节发送到可见层10,从而有效地使它们不可见。 如果您希望临时启用/禁用某些层,请查看层选择对话框。

  在CoppeliaSim中,IK任务至少需要指定以下元素:

  • 使用尖端虚拟对象和基础对象描述的运动链。
  • 尖端虚拟对象将被约束跟随的目标虚拟对象。

  我们已经有了基础对象(Object RedundantRobot)。 添加一个虚拟对象,将其重命名为redundantRob_Tip,并使用坐标和变换对话框将其位置设置为(0.324,0,0.62)。 接下来,将虚拟对象附加到RedundantRob_Link7(选择RedundantRob_Tip,然后选择RedundantRob_Link7,然后选择[菜单栏–>编辑–>使最后选择的对象为父项])。

  现在让我们准备目标虚拟对象:复制并粘贴redundantRob_Tip,并将副本重命名为redundantRob_target。 目标虚拟对象已准备好。

  现在,我们将添加一种轻松操作机器人的方法,而不必担心移动错误的对象会将其弄坏。 因此,我们将其定义为模型。 首先,将redundantRob_Tip和redundantRob_target移到第11层,使两个虚拟对象都不可见。 然后按住Shift键并选择场景视图中的所有可见对象,按住Ctrl键并单击场景层次中的对象redundantRobot以将其从选择中移除,然后打开对象公用属性对话框。 选中Select base of model而不是Item,然后选中Related Apply to Selection按钮。 使用<Esc>清除选择,然后选择RedundantRobot。 在同一对话框中,选中对象是模型基础项,然后关闭对话框。 请注意,点画边界框现在如何包围整个操纵器:

在这里插入图片描述

  单击操纵器上的任何对象,请注意如何始终选择基础对象RedundantRobot。

  接下来,添加一个操作球,使用它来操作机器人的抓手位置/方向。 点击[Menu bar–>Add–>Primitive Shape–>Sphere]打开Primitive Shape对话框,X-Size、Y-Size和Z-Size指示0.05,然后取消选中Create Dynamic and Responsible Shape项并点击OK。 将新添加的球体的位置调整为与redundantRob_target相同(使用坐标和变换对话框)。 球体现在显示在操纵器的尖端。 将球体重命名为redundantRob_ManipSphere,然后使其成为redundantRob_target的父对象。 使redundantRob_ManipSphere成为rendundantRob_ManipSphere的冗余Robot父对象:目标虚拟对象和操纵球现在也是机器人模型的一部分。 折叠场景层次中的RedundantRobot树。 冗余机械手模型准备好了!

IK via method 1

  向机器人模型添加反向运动学功能的推荐方法是完全通过脚本调用适当的API命令来完成此操作:其思想是通过运动学插件提供的函数构建等效的运动学模型。 该方法使用IK组和IK元素的概念和术语。

  选择Object RedundantRobot,然后选择[菜单栏–>添加–>关联子脚本–>非线程化],将非线程化的子脚本附加到该对象。 双击对象名称旁边显示的脚本图标,并将脚本内容替换为以下代码:

function sysCall_init()
    -- Take a few handles from the robot:
    simBase=sim.getObjectHandle('redundantRobot')
    simTip=sim.getObjectHandle('redundantRob_tip')
    simTarget=sim.getObjectHandle('redundantRob_target')
    simJoints={}
    for i=1,7,1 do
        simJoints[i]=sim.getObjectHandle('redundantRob_joint'..i)
    end

    -- Now build a kinematic chain and 2 IK groups (undamped and damped) inside of the IK plugin environment,
    -- based on the kinematics of the robot in the scene:
    ikJoints={}
    -- create an IK environment:
    ikEnv=simIK.createEnvironment()
    -- create a dummy in the IK environemnt: 
    ikBase=simIK.createDummy(ikEnv) 
    -- set that dummy into the same pose as its CoppeliaSim counterpart:
    simIK.setObjectMatrix(ikEnv,ikBase,-1,sim.getObjectMatrix(simBase,-1)) 
    local parent=ikBase
    -- loop through all joints:
    for i=1,#simJoints,1 do
        -- create a joint in the IK environment: 
        ikJoints[i]=simIK.createJoint(ikEnv,simIK.jointtype_revolute)
        -- set it into IK mode: 
        simIK.setJointMode(ikEnv,ikJoints[i],simIK.jointmode_ik) 
        -- set the same joint limits as its CoppeliaSim counterpart joint:
        local cyclic,interv=sim.getJointInterval(simJoints[i])
        simIK.setJointInterval(ikEnv,ikJoints[i],cyclic,interv)
        -- set the same joint position as its CoppeliaSim counterpart joint: 
        simIK.setJointPosition(ikEnv,ikJoints[i],sim.getJointPosition(simJoints[i]))
        -- set the same object pose as its CoppeliaSim counterpart joint: 
        simIK.setObjectMatrix(ikEnv,ikJoints[i],-1,sim.getObjectMatrix(simJoints[i],-1))
        -- set its corresponding parent: 
        simIK.setObjectParent(ikEnv,ikJoints[i],parent,true) 
        parent=ikJoints[i]
    end
    -- create the tip dummy in the IK environment:
    ikTip=simIK.createDummy(ikEnv)
    -- set that dummy into the same pose as its CoppeliaSim counterpart: 
    simIK.setObjectMatrix(ikEnv,ikTip,-1,sim.getObjectMatrix(simTip,-1))
    -- attach it to the kinematic chain: 
    simIK.setObjectParent(ikEnv,ikTip,parent,true)
    -- create the target dummy in the IK environment: 
    ikTarget=simIK.createDummy(ikEnv)
    -- set that dummy into the same pose as its CoppeliaSim counterpart: 
    simIK.setObjectMatrix(ikEnv,ikTarget,-1,sim.getObjectMatrix(simTarget,-1))
    -- link the two dummies: 
    simIK.setLinkedDummy(ikEnv,ikTip,ikTarget)
    -- create an IK group: 
    ikGroup_undamped=simIK.createIkGroup(ikEnv)
    -- set its resolution method to undamped: 
    simIK.setIkGroupCalculation(ikEnv,ikGroup_undamped,simIK.method_pseudo_inverse,0,6)
    -- make sure the robot doesn't shake if the target position/orientation wasn't reached: 
    simIK.setIkGroupFlags(ikEnv,ikGroup_undamped,1+2+4+8)
    -- add an IK element to that IK group: 
    local ikElementHandle=simIK.addIkElement(ikEnv,ikGroup_undamped,ikTip)
    -- specify the base of that IK element: 
    simIK.setIkElementBase(ikEnv,ikGroup_undamped,ikElementHandle,ikBase)
    -- specify the constraints of that IK element: 
    simIK.setIkElementConstraints(ikEnv,ikGroup_undamped,ikElementHandle,simIK.constraint_pose)
    -- create another IK group: 
    ikGroup_damped=simIK.createIkGroup(ikEnv)
    -- set its resolution method to damped: 
    simIK.setIkGroupCalculation(ikEnv,ikGroup_damped,simIK.method_damped_least_squares,1,99)
    -- add an IK element to that IK group: 
    local ikElementHandle=simIK.addIkElement(ikEnv,ikGroup_damped,ikTip)
    -- specify the base of that IK element: 
    simIK.setIkElementBase(ikEnv,ikGroup_damped,ikElementHandle,ikBase)
    -- specify the constraints of that IK element: 
    simIK.setIkElementConstraints(ikEnv,ikGroup_damped,ikElementHandle,simIK.constraint_pose) 
end

function sysCall_actuation()
    -- reflect the pose of the target dummy to its counterpart in the IK environment:
    simIK.setObjectMatrix(ikEnv,ikTarget,ikBase,sim.getObjectMatrix(simTarget,simBase)) 

    -- try to solve with the undamped method:
    if simIK.handleIkGroup(ikEnv,ikGroup_undamped)==simIK.result_fail then 
        -- the position/orientation could not be reached.
        -- try to solve with the damped method:
        simIK.handleIkGroup(ikEnv,ikGroup_damped) 
        if not ikFailedReportHandle then
            -- We display a IK failure report message:
            ikFailedReportHandle=sim.displayDialog("IK failure report","IK solver failed.",
                sim.dlgstyle_message,false,"",nil,{1,0.7,0,0,0,0})
        end
    else
        if ikFailedReportHandle then
            -- We close any report message about IK failure:
            sim.endDialog(ikFailedReportHandle)
            ikFailedReportHandle=nil
        end
    end
    
    for i=1,#simJoints,1 do
        -- apply the joint values computed in the IK environment to their CoppeliaSim joint counterparts:
        sim.setJointPosition(simJoints[i],simIK.getJointPosition(ikEnv,ikJoints[i]))
    end
end 

function sysCall_cleanup()
    -- erase the IK environment: 
    simIK.eraseEnvironment(ikEnv) 
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105

  上面的脚本从CoppeliaSim模型创建等效运动学模型,然后在每个仿真步骤中,读取CoppeliaSim目标的位置/方向,将其应用于等效运动学模型的目标,运行IK解算器,最后读取等效运动学模型的关节角度,并将它们应用于CoppeliaSim模型的关节。 要处理单个配置和目标无法到达的情况,我们首先尝试使用非阻尼解算器,如果它失败,我们将恢复到阻尼解算器(使用阻尼解算器,当阻尼较大时,分辨率会变得更稳定,但收敛速度会更慢)。

  解算IK的方法1很优雅,因为它允许很好地将IK功能与CoppeliaSim模型分开(例如,IK解算器也可以在空场景中运行)。 但是,这并不总是那么方便,特别是当IK任务很复杂并且涉及几十个关节时(例如,在并行运动学计算机的情况下),在这种情况下,方法2可能更好:

IK via method 2

  向机器人模型添加反向运动学功能的推荐方法是通过方法1完全通过编程进行处理。但是,这并不总是那么方便,特别是当IK任务很复杂且涉及数十个关节时(例如,在并行运动学机器的情况下)。 在这种情况下,方法2会派上用场:它包括适当地准备CoppeliaSim模型,然后通过GUI创建运动学任务。 此方法也使用IK组和IK元素的概念和术语。

  选择所有关节,然后在关节对话框中选择反向运动学模式,然后单击应用于选择。 接下来,通知CoppeliaSim、RedundantRob_Tip和redundantRob_target是用于逆运动学解析的尖端-目标对。 打开虚拟对象属性对话框,并在虚拟对象链接部分中,将redundantRob_target指定为链接的虚拟对象。 在同一对话框中,链接类型已经是默认值IK,TIP-TARGET。 这就是你现在应该显示的东西:

在这里插入图片描述

  在此阶段,定义反向运动学任务的所有元素都已准备就绪,我们只需要将任务注册为IK组。 打开反向运动学对话框,然后单击添加新的IK组。 IK组列表中将显示一个新项目:IK_Group。 双击它将其重命名为RedundantRobot_UnDamping。 选中该项目后,单击编辑IK元素以打开IK元素对话框。 在添加新IK元素WITH TIP按钮旁边,在下拉框中选择redundantRob_TIP,然后单击Add new IK Element With TIP按钮。 这只是添加了一个显示在列表中的IK元素。 再往下,将RedundantRobot指示为基础。 最后,确保约束部分中的所有项都已选中(选中ALS Alpha-Beta和Gamma)。 实际上,我们希望尖端虚拟对象在位置和方向上跟随目标虚拟对象:

在这里插入图片描述

  关闭IK元素对话框。 它应该是这样的:

在这里插入图片描述

  在这个阶段,IK分辨率不会那么强,因为围绕单个配置的计算,或者不可能达到目标的情况,都会导致不稳定。 若要避免这种情况,请添加第二个IK组,将其重命名为redundantRobot_Damping,然后选择DLS作为计算方法,阻尼为1.0,最大迭代次数为99(使用阻尼解算器,当阻尼较大时,分辨率会变得更稳定,但收敛速度会更慢)。 单击编辑IK元素,然后添加与前面相同的IK元素。 选择未阻尼IK组,然后单击编辑条件参数。 在修道院如果…。 部分中,选中这两个复选框。 然后选择阻尼IK组,单击编辑条件参数并调整Perform If…。 执行了到redundantRobot_UNDAMPED的部分,但失败。

  两个IK组的显式处理标志均未选中:这意味着它们将通过主脚本进行处理,即在每个模拟步骤中将自动计算这两个IK组。 如果希望在您希望的时间手动处理计算,请选中这两个IK组的显式处理复选框:从现在开始,只有在显式调用sim.handleIkGroup时才会计算这些IK组。

运行仿真

  我们的逆运动学任务准备好了! 来测试一下吧。 运行仿真,然后选择绿色操纵球体。 接下来,选择对象翻译工具栏按钮:

在这里插入图片描述

  现在用鼠标拖动对象:操纵器应该跟随。 另请尝试对象旋转工具栏按钮:

在这里插入图片描述

  在操作过程中也尝试按住CTR-或SHIFT-键。 切换回对象平移工具栏按钮,并尝试将对象拖动到尽可能远的位置,请注意反向运动学任务是如何非常健壮的,这要归功于阻尼组件。 停止模拟,然后禁用阻尼IK组并重试。 另请尝试禁用相应IK元素中的各个约束,并注意操纵器在模拟期间的行为。

  运行仿真,在操纵器上复制粘贴几次,然后左右移动/旋转副本,还可以通过拖动其操纵球体来更改其配置。 请注意,关于IK,每个操纵器实例是如何完全起作用的。

9.5 六足动物教程

  在本教程中,我们将构建一个六足步行机器人。 在开始学习本教程之前,请确保已阅读BubbleRob教程和有关导入和准备刚体的教程。 由Lyall Randell提供的与本教程相关的CoppeliaSim CAD数据(“heapod.dxf”)位于CoppeliaSim的安装文件夹的“cadFiles”文件夹中。 与本教程相关的已完成模型可以在CoppeliaSim的模型浏览器中找到。 单击[菜单栏–>文件–>导入–>网格…]。 然后选择要导入的文件。 另请参阅有关如何导入/导出形状的部分。 将弹出一个对话框,询问有关网格缩放和网格方向的信息。 单击确定。 导入了几个形状,它们位于场景的中间。 这些形状还会显示在主窗口左侧的场景层次中。 根据原始CAD数据的导出方式,导入的CAD数据可能具有不同的比例、不同的位置,甚至可以分组为单个形状。 导入形状的指定颜色是随机的。 下图显示了导入的形状:

在这里插入图片描述

  如你所见,这个六足机器人有6条完全相同的腿。 因此,我们将搭建一条腿,而不是分别搭建每条腿,当它完成后,只需将其复制并粘贴到正确的位置! 在场景中选择要删除的图形(按住Ctrl键和/或Shift键并单击以执行选择过程),然后按Delete键(确保腿部指向世界x轴):

在这里插入图片描述

  导入操作给我们留下了每条腿3个形状,因此我们需要进一步细分这3个形状,以便为腿部连杆和腿部伺服电机提供不同的形状。 选择3个形状,然后单击[菜单栏–>编辑–>分组/合并–>分割所选形状]。 您可以看到,此操作已将距离机器人身体最近的形状划分为3个子形状,其中我们只预期有2个子形状。 要更正此问题,请选择左链接元素和右链接元素,如下图所示:

在这里插入图片描述

  在两个形状仍处于选中状态时,单击[菜单栏–>编辑–>分组/合并–>合并所选形状],将它们合并为一个形状。

  在下一步中,我们将添加腿部机构所需的关节。 执行此操作的一种方法是将关节添加到场景中,然后指定它们的适当位置和方向(通过位置对话框或方向对话框)。 但是,当您不知道关节的确切位置(如我们的示例)时,这是不可能的,因此我们必须从我们拥有的形状中提取它们:

  选择代表伺服电机的3个形状,复制它们(ctrl-c或[菜单栏–>编辑–>复制所选对象]),然后用[菜单栏–>文件–>新建场景]创建一个新场景,然后粘贴形状(ctrl-v或[菜单栏–>编辑–>粘贴缓冲区])。 我们现在是在另一个场景(场景2),无论我们在这里做什么都不会影响我们原来的场景。 现在保持选中3个伺服电机,点击[菜单栏–>编辑–>分组/合并–>合并所选形状],将它们合并为一个形状。 然后进入三角形编辑模式。 现在,构成我们形状的所有三角形都会显示出来,并且可以对其进行操作。

  切换到第2页,使用Fit-to-view工具栏按钮将相机移动到离形状更近的位置:

在这里插入图片描述

  按住Shift键并选择组成伺服电机输出轴之一的所有三角形,如下图所示:

在这里插入图片描述

在这里插入图片描述

  然后单击形状编辑模式对话框中的提取形状按钮。 对另外两个伺服电机输出轴执行相同的操作(对于第三个伺服电机,您必须切换到第3页才能按住Shift键选择其输出轴):

在这里插入图片描述

  切换回第1页,离开编辑模式,然后删除代表3个伺服电机的形状。 现在,提取的竖井变得清晰可见:

在这里插入图片描述

  我们可以使用提取的形状来精确定位关节。 点击[菜单栏–>添加–>关节–>旋转]将旋转关节插入到场景中。 默认位置为(0,0,0),默认方向为垂直。 在运动类型仍处于选定状态时,按住Ctrl键并选择表示垂直输出轴的形状,然后打开位置对话框,移动到位置选项卡,然后单击应用于选择按钮。

  这只是将关节定位在与垂直输出轴完全相同的坐标上! 现在,对另外两个关节和两个水平轴重复该过程。 所有关节现在都已就位,但是,只有第一个添加的关节具有正确的方向。 选择最后添加的两个关节,然后输入(-90,0,0)作为方向对话框中Alpha、Beta和Gamma项的值,然后在方向选项卡上单击应用于选择按钮。 现在,所有关节都具有正确的位置和方向。 选择所有3个关节,将它们复制到缓冲区(ctrl-c),切换回初始场景(场景1),然后粘贴缓冲区(ctrl-v)。 这是你应该得到的:

在这里插入图片描述

  现在可以在关节属性对话框(可以通过双击场景层次中的关节图标打开该对话框)中调整关节大小(检查关节长度和关节直径项)。 此外,我们希望两个水平运动类型位于y坐标0,而垂直运动类型位于与其伺服电机类似的z坐标:

在这里插入图片描述

  将关节重命名为“HEXA_joint1”、“HEXA_joint2”和“HEXA_joint3”(从身体到脚)。 可以通过双击场景层次中的关节名称来执行此操作。 现在,让我们设置初始关节值,以便当腿部水平拉伸时,所有关节值都为零:在关节对话框的项目位置中,为“hexa_joint2”和“hexa_joint3”分别设置-30和+120。 请注意关节是如何处于力矩/力模式的。 在我们的示例中,我们希望能够在反向运动学模式下控制这些关节,但也希望反向运动学结果作为动力学位置控制值应用。 为此,我们将在反向运动学模式下设置关节(在关节模式部分中选择“关节处于反向运动学模式”)。 除此之外,我们还激活了该关节的混合操作(这告诉CoppeliaSim反向运动学计算结果将作为动态目标值应用于关节)。 单击“应用于选择”以将刚刚执行的更改应用到其他两个选定的关节。

  此时,我们应该准备图形的视觉外观,如果需要将它们分组,并为运动模拟准备相应的纯图形。 此处不会详细说明如何完成此操作,因为该过程与教程中有关导入和准备刚体的操作非常相似。 完成此步骤后,您应该得到以下内容(可见部分和隐藏部分):

在这里插入图片描述

在这里插入图片描述

  上面的场景是“tutorials\Hexapod\Sixapod intermediate step.ttt”。 请注意以下事项:

  • 对象已适当命名/重命名。
  • 用于动力学的六足动物的身体(“heabodyResponsible”)被简化为3个长方体(中间的空间已经填满)。
  • 腿尖表示为纯球体,因为对于动力学模拟而言,纯球体最稳定/最快。
  • 质量、惯性属性等已设置为最佳性能。 它总是需要一些(很多!)。 测试以找到良好的(即稳定的)参数。
  • 连续的链接元素不会导致碰撞响应(形状动态属性对话框中的可响应遮罩已相应设置)。

  接下来,我们将链接该机制的当前元素。 选择“HEXA_Link3Responsible”,然后选择“HEXA_joint3”,然后单击[Menu Bar–>Edit–>Make Last Selected Object Parent]。 同样的操作包括:“HEXA_joint3”和“HEXA_Link2Responsible”、“HEXA_Link2Responsible”和“HEXA_joint2”、“HEXA_joint2”和“HEXA_Link1Respondable”、“HEXA_Link1Responsible”和“HEXA_joint1”,最后是“HEXA_joint1”和“Hexapod”。 我们已经组装好了第一条腿。 开始模拟并观察刚性对象是如何受关节约束的。 另请注意,关节不是保持固定位置,而是缓慢漂移:这是因为我们尚未为表示腿部的运动链定义反向运动学任务。 这就是我们下一步要做的。

  使用[Menu Bar–>Add–>Dummy]添加虚拟对象。 将其重命名为“HEXA_FootTip”。 现在将虚拟人放置在腿部尖端,即腿部与地板接触的位置。 您可以通过使用对象操纵工具栏按钮或使用位置对话框来实现这一点。 在前一种情况下,通过切换到第2页、第3页、第4页、第5页或第6页上的正交投影视图,可以逐步调整位置。完成后,虚拟人的绝对位置应接近(0.204,0,0)。 接下来,将虚拟对象附加到“HEXA_Link3”(将虚拟对象拖动到场景层次中的“HEXA_Link3”上)。 然后复制并粘贴“HEXA_FootTip”,并将副本重命名为“HEXA_FootTarget”。

  在此阶段,我们想要的是在使附加机构自动调整(即自动计算新关节位置)的同时,让“HEXA_FootTip”跟在“Hexa_FootTarget”之后。 我们必须定义逆运动学任务:

  首先,让我们通知这两个虚拟人,它们是用于反向运动学解析的尖端-目标对。 双击场景层次中的虚拟图标“HEXA_FootTip”:这将打开虚拟属性对话框。 在Dummy-Dummy链接部分中,将“HEXA_FootTarget”指定为链接的Dummy。 请注意两个虚拟对象是如何通过场景层次中的红色点划线链接的(两个虚拟对象在场景中也通过红线链接,但由于两个虚拟对象重合,因此无法看到该线)。 在同一对话框中,链接类型已经是默认值“IK,TIP-TARGET”。 这就是你现在应该拥有的东西:

在这里插入图片描述

  现在打开反向运动学对话框,然后单击添加新的IK组。 IK组列表中将显示一个新项目:“IK_Group”。 选中该项目后,打开IK元素对话框(单击编辑IK元素),并在右侧的下拉框中指示“HEXA_FootTip”,以添加新的IK元素和提示。 然后单击Add new IK Element with Tip。 关闭对话框,并将“HEXA_FootTarget”附加到“六脚架”。 我们的反向运动学任务已经为这条腿做好了准备! 我们来测试一下吧。

  首先,通过打开常规动力学属性对话框临时禁用动力学,然后取消选中启用动力学。 接下来,开始仿真,并在场景层次中选择“HEXA_FootTarget”。 使用鼠标四处移动“HEXA_FootTarget”:腿部应该紧随其后。 停止仿真并再次启用动力学。

  我们将通过生成一条腿的运动来控制六足机器人,并以延迟的方式将其应用于所有6条腿,其中每条腿都有不同的延迟。 我们通过让一个子脚本为一条腿生成移动序列,并让另外6个子脚本以延迟的方式应用该移动序列来实现这一点。 选择“HEXA_joint1”并点击[菜单栏–>添加–>关联子脚本–>非线程化]。 我们刚刚将一个子脚本附加到“HEXA_JOINT1”对象。

  现在我们将重复这条腿5次。 选择组成腿部的所有对象(也包括隐藏对象):从“HEXA_joint1”到“HEXA_FootTip”。 同时选择“HEXA_FootTarget”。 确保没有选择“HEXA_bodyRespondable”和“Hexa_Body”,然后复制并粘贴对象。 打开方向对话框,移动到方向选项卡,并为环绕Z项目输入“60”。 确保变换将相对于世界,然后单击Z旋转当前选择。 再次粘贴初始腿部,然后将围绕Z轴项目调整为120,然后再次单击Z轴旋转当前选择。 对其余3条腿重复该过程(确保将旋转角度调整为180、240,最后调整为300度)。 这是你应该拥有的:

在这里插入图片描述

  让我们把所有的腿都连接到身体上。 选择“HEXA_joint1#0”至“HEXA_JOINT1#4”,然后选择“六脚架”,点击[菜单栏–>编辑–>使最后选择的对象为父对象]。 在场景层次中,通过单击腿部树的“-”图标来折叠所有腿部树。 然后选择“Hexa_FootTarget#0”到“Hexa_FootTarget#4”,然后选择“六脚架”,然后点击[菜单栏–>编辑–>将最后选择的对象设为父对象]。

  打开反向运动学对话框。 请注意反向运动学任务也是如何复制的。 这是你应该拥有的:

在这里插入图片描述

  关闭反向运动学对话框。 使用[Menu Bar–>Tools–>Selection]或按相应的工具栏按钮打开对象选择对话框。 单击清除当前选择,然后单击Dummies:0/13。场景中的所有虚拟对象都已被选中。 取消选择(按住Ctrl键并单击)不属于六足机器人的虚拟对象。 现在我们已经选择了六足动物上的12个假人。 现在打开对象的公共属性。 在Visibility Layers区域中,禁用Layer 3并启用Layer 11,然后单击相关的Apply to Selection按钮。 这只是将所有虚拟对象发送到可见层11,有效地使它们不可见。 如果您希望临时启用/禁用某些层,请查看层选择对话框。

  接下来,按照与上面相同的步骤将所有关节发送到可见层10。

  然后将新虚拟对象添加到场景中,并将其重命名为“HEXA_BASE”。 同时将其发送到可见层11,然后将其设置为“六足”的子级。 “HEXA_BASE”表示我们选择为(0,0,0)的六足机器人的位置。

  现在让我们定义六足动物模型。 正如您可能已经注意到的,当您单击六足机器人上的对象时,只有该对象会被选中。 但我们现在想要的是保护单个对象不被修改,而是选择整个机器人。 对于组成六足机器人的每个对象(对象“Sixpod”除外),启用对象通用属性对话框中的Select base of model,而不是项。 清除选择,然后选择“六脚架”。 在同一对话框中,启用Object is model base item。 现在单击六足机器人上的任何对象:现在将选择整个机器人:

在这里插入图片描述

  现在将一个非线程子脚本附加到“六足”。 创建另一个场景(场景3),并打开“heapod.ttm”模型文件。 双击对象“heatapod”的子脚本图标以打开脚本编辑器。 复制脚本,切换回原始场景(场景1),双击对象“六脚架”的子脚本图标,粘贴脚本。 对与每个分支相关联的子脚本重复相同的过程。 请注意,与六足动物腿相关联的所有子脚本是如何完全相同的。 最后一个元素仍然缺失:我们需要每条腿应用具有不同时间延迟的运动序列。 如果仔细查看分支的子脚本,它们每个都使用以下指令从其用户参数中读取延迟值:

modulePos=sim.getUserParameter(sim.handle_self,'modulePosition')
  • 1

  通过双击子脚本或自定义脚本右侧的图标,可以打开用户参数对话框。 添加与其他场景中的六足完全相同的参数。 运行仿真。 六足机器人现在应该可以行走了。

  使用脚本控制机器人或模型只是一种方法。 CoppeliaSim提供了许多不同的方式(也是组合的),看看外部控制器教程,或者插件教程。

9.6 外部控制器教程

在CoppeliaSim中有几种方法可以控制机器人或仿真:

  • 最方便的方法是编写一个子脚本来处理给定机器人或模型的行为。 这是最方便的方式,因为子脚本直接附加到场景对象,它们将与关联的场景对象一起复制,它们不需要使用外部工具进行任何编译,它们可以在线程或非线程模式下运行,它们可以通过自定义Lua函数或通过Lua扩展库进行扩展。 使用子脚本的另一个主要优点是:没有本节中提到的后3种方法的通信延迟(即使用常规API),并且子脚本是应用程序主线程的一部分(固有的同步操作)。 然而,编写脚本有几个缺点:您不能选择编程语言,不能拥有最快的代码,不能直接访问外部函数库,除了Lua扩展库。
  • 另一种控制机器人或仿真的方式是编写插件。 插件机制允许回调机制、自定义Lua函数注册,当然还允许访问外部函数库。 插件通常与子脚本结合使用(例如,插件注册自定义的Lua函数,当从子脚本调用这些函数时,将回调特定的插件函数)。 使用插件的一个主要优势还在于,没有本节中提到的后3种方法的通信延迟(即使用常规API),并且插件是应用程序主线程的一部分(固有的同步操作)。 插件的缺点是:它们的编程更加复杂,而且它们也需要使用外部程序进行编译。 另请参阅插件教程。
  • 控制机器人或仿真的第三种方式是编写依赖于远程API的外部客户端应用程序。 如果您需要从外部应用程序、从机器人或从另一台计算机运行控制代码,这是一种非常方便和简单的方法。 这还允许您使用与运行真实机器人的代码完全相同的代码来控制模拟或模型(例如 仿真机器人)。 远程API有两个版本:基于B0的远程API和传统的远程API。
  • 控制机器人或仿真的第五种方式是通过ROS节点。 与远程API类似,ROS是让多个分布式进程相互通信的便捷方式。 虽然远程API非常轻量级且速度很快,但它只允许与CoppeliaSim通信。 另一方面,ROS允许几乎任何数量的进程彼此连接,并且有大量的兼容库可用。 但是,它比远程API更重、更复杂。 有关详细信息,请参阅ROS接口。
  • 控制机器人或仿真的第六种方式是通过BlueZero(BØ)节点。 与ROS类似,BlueZero是一种让多个分布式进程相互通信的便捷方式,并且是一个轻量级的跨平台解决方案。 具体请参考BlueZero接口。
  • 控制机器人或仿真的第七种方式是编写通过各种方式(例如,管道、插座、串行端口等)进行通信的外部应用程序。 使用CoppeliaSim插件或CoppeliaSim脚本。 两大优势是编程语言的选择(可以是任意语言)和灵活性。 在这里,控制代码也可以在机器人上运行,也可以在不同的计算机上运行。 然而,这种控制模拟或模型的方法比使用远程API的方法更加繁琐。

与本教程相关的场景文件有8个:

  • Scenes/controlTypeExamples/controlledViaScript:一个机器人通过非线程化的子脚本进行控制,另一个通过线程化的子脚本进行控制。
  • Scenes/controlTypeExamples/controlledViaPlugin:机器人是通过插件控制的。
  • Scenes/controlTypeExamples/controlledViaB0RemoteApi:机器人通过基于B0的远程应用编程接口进行控制。
  • Scenes/controlTypeExamples/controlledViaLegacyRemoteApi:机器人通过传统的远程api进行控制。
  • Scenes/control TypeExamples/ControledViaB0:通过BlueZero接口控制机器人。
  • Scenes/ControTypeExamples/ControledViaRos:通过ROS接口控制机器人。
  • Scenes/control Type示例/ControledViaRos2:通过ROS 2接口控制机器人。
  • Scenes/ControTypeExamples/ControledViaTcp:通过LuaSocket和TCP控制机器人。

在这里插入图片描述

  在所有8种情况下,都使用子脚本,主要用于建立与外部世界的链接(例如,启动正确的客户端应用程序,并将正确的对象句柄传递给它)。 还有另外两种方法可以控制机器人、模拟器或模拟器本身:使用定制脚本或附加组件。 但是,不建议将它们用于控制,而应在模拟不运行时使用它们来处理功能。

例如,链接到场景控制中的机器人ViaB0RemoteApi.ttt的子脚本具有以下主要任务:

  • 使用一些对象句柄作为参数启动控制器应用程序(BubbleRobClient_B0RemoteApi)。 基于b0的远程API的服务器功能由对象b0RemoteApiServer提供。

作为另一个示例,链接到场景ControledViaRos.ttt中的机器人的子脚本具有以下主要任务:

  • 检查是否加载了CoppeliaSim的ROS接口/ROS 2接口。
  • 使用一些主题名称或对象句柄作为参数启动控制器应用程序(rosBubbleRob/ros2BubbleRob)。

作为另一个示例,链接到场景控制中的机器人ViaTcp.ttt的子脚本具有以下主要任务:

  • 搜索空闲套接字连接端口。
  • 使用所选的连接端口作为参数启动控制器应用程序(BubbleRobServer)。
  • 本地连接到控制器应用程序。
  • 在每次模拟过程中,将传感器数值发送到控制器,并从控制器读取所需的马达数值。
  • 在每次模拟过程中,将所需的马达值应用于机器人的关节。

  运行仿真,并复制和粘贴机器人:您将看到复制的机器人将直接可操作,因为它们附加的子脚本负责启动各自外部应用程序的新实例,或调用适当的插件函数。

9.7 插件教程

  本教程将尝试解释如何为CoppeliaSim编写插件。 与本教程相关的CoppeliaSim场景文件位于CoppeliaSim的安装文件夹的tutorials/BubbleRobExt中。 本教程的插件项目文件可以在这里找到。

  在程序启动时,CoppeliaSim会自动加载其文件夹(即安装文件夹,或与包含coppeliaSim.exe的文件夹相同的文件夹)中可以找到的所有插件。 CoppeliaSim使用以下掩码识别插件文件:Windows上的“simExt*.dll”、Mac OS上的“libsimExt*.dylib”和Linux上的“libsimExt*.so”。 另外,插件的文件名不应该包含任何下划线(显然除了开头的那个)。 本教程的插件文件是simExtBubbleRob.dll。 测试时,请确保在CoppeliaSim启动时正确加载:通过取消选中用户设置对话框([菜单栏–>工具–>设置])中的隐藏控制台窗口项,将控制台窗口切换为可见。 此选项仅在Windows版本中可用。 在Mac上,查看系统的控制台,在Linux上,尝试从控制台中启动CoppeliaSim。 控制台窗口应显示与以下内容类似的内容:

在这里插入图片描述

  正如您已经了解的,这个插件是在BubbleRob教程中为BubbleRob编写的。 加载相关场景文件(Tutorials\BubbleRobExt\BubbleRobExt.ttt)。 BubbleRob插件添加了4个新的Lua命令(自定义Lua命令应遵循约定:“simXXX.YYY”作为名称,例如simRob.start):

在这里插入图片描述

  现在,在场景中打开附加到BubbleRob模型的线程子脚本(例如,双击场景层次中对象bubbleRob旁边的脚本图标)。 检查代码:

function sysCall_threadmain()
    -- Check if the required plugin is there:
    moduleName=0
    moduleVersion=0
    index=0
    bubbleRobModuleNotFound=true
    while moduleName do
        moduleName,moduleVersion=sim.getModuleName(index)
        if (moduleName=='BubbleRob') then
            bubbleRobModuleNotFound=false
        end
        index=index+1
    end
    if (bubbleRobModuleNotFound) then
        sim.displayDialog('Error','BubbleRob plugin was not found. (simExtBubbleRob.dll)&&nSimulation will not run properly',
            sim.dlgstyle_ok,true,nil,{0.8,0,0,0,0,0},{0.5,0,0,1,1,1})
    else
        local jointHandles={sim.getObjectHandle('leftMotor'),sim.getObjectHandle('rightMotor')}
        local sensorHandle=sim.getObjectHandle('sensingNose')
        local robHandle=simBubble.create(jointHandles,sensorHandle,{0.5,0.25}) -- create a BubbleRob instance
        if robHandle>=0 then
            simBubble.start(robHandle,20) -- control happens here
            simBubble.stop(robHandle)
            simBubble.destroy(robHandle) -- destroy the BubbleRob instance
        end
    end
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

  代码的第一部分负责检查运行此脚本所需的插件(即simExtBubbleRob.dll)是否可用(即是否找到并成功加载)。 如果没有,则会显示一条错误消息。 否则,将检索关节和传感器句柄,并将其提供给自定义Lua函数,该函数在插件中创建BubbleRob的控制器实例。 如果调用成功,则可以调用simBubble.moveAndAvoid。 该函数指示插件在避开障碍物的同时移动BubbleRob模型,持续时间为20秒。 该函数是阻塞的(缺省情况下,省略的第三个参数为false),这意味着在函数完成之前(即20秒之后),调用才会返回。 运行仿真:BubbleRob移动20秒,然后如预期的那样停止。 现在离开CoppeliaSim。 暂时将插件重命名为temp_simExtBubbleRob.dll,以便CoppeliaSim不再加载它,然后再次启动CoppeliaSim。 加载上一个场景并运行模拟:现在将显示一条错误消息,指示找不到所需的插件。 再次离开CoppeliaSim,将插件重新命名为simExtBubbleRob.dll,然后再次启动CoppeliaSim。

  让我们来看看插件是如何注册和处理上述4个自定义Lua函数的。 打开BubbleRob插件项目,查看文件simExtBubbleRob.cpp;

  注意3个必需的插件入口点:simStart、simEnd和simMessage;simStart在插件加载(初始化)时调用一次,simEnd在插件卸载(清理)时调用一次,simMessage使用几种类型的消息定期调用。

  在初始化阶段,插件加载CoppeliaSim库(以便访问CoppeliaSim的所有API函数),然后注册4个自定义Lua函数。 通过指定以下内容注册自定义Lua函数:

  • 函数名
  • 调用提示字符串
  • 预期参数的列表
  • 回调地址

  当脚本调用指定的函数名时,CoppeliaSim将尝试将提供的参数转换为回调所需的参数,然后调用回调地址。 回调函数中最困难的任务是正确读取输入参数,并正确写入输出值。 为了简化任务,使用了两个帮助器类,它们将负责该任务:CLuaFunctionData和CLuaFunctionDataItem,它们位于Programming/common和Programming/Include中。
  在编写您自己的自定义Lua函数时,请尝试使用与文件simExtBubbleRob.cpp中相同的代码布局/框架。
  BubbleRob实例的控制不会发生在4个自定义Lua函数回调中的任何一个中:回调只是初始化/销毁/更新数据结构。 控制发生在simMessage中,消息为sim_message_eventcallback_module eHandle;当主脚本调用sim.handleModule(sim.handleall,false)时,所有插件都会调用该消息,每次模拟过程都会发生一次。
  通常,回调例程应该尽可能快地执行,然后应该将控制权交还给CoppeliaSim,否则整个仿真器将暂停。

9.8 机器人语言解释器集成教程

  CoppeliaSim的功能大部分时间需要开发插件。 在继续学习本教程之前,请确保您已经阅读了关于插件的教程和关于外部控制器的教程。
  与本教程相关的CoppeliaSim场景文件位于CoppeliaSim的安装文件夹Scenes\robotLanguageControl.ttt中。 您可以在这里找到插件项目文件,在这里可以找到服务器应用程序项目文件。
  首先,我们先加载相关场景文件Scenes\robotLanguageControl.ttt:

在这里插入图片描述

  MTB机器人是一个假想的机器人(MTB代表机器类型B),它将用一种假想的机器人语言进行控制。

  如前所述,所使用的机器人语言是假想的,而且非常非常简单。 支持以下命令(每行一个命令,输入区分大小写):

"REM" starts a comment line
"SETLINVEL v": sets the prismatic joint velocity for next movements (v is in m/s)
"SETROTVEL v": sets the revolute joint velocity for next movements (v is in degrees/s)
"MOVE p1 p2 p3 p4": moves to joint positions (p1;p2;p3;p4) (in degrees except for p3 in meters)
"WAIT x": waits x milliseconds
"SETBIT y": sets the bit at position y (1-32) in the robot output port
"CLEARBIT y": clears the bit at position y (1-32) in the robot output port
"IFBITGOTO y label": if bit at position y (1-32) in the robot input port is set, jump to "label"
"IFNBITGOTO y label": if bit at position y (1-32) in the robot input port is not set, jump to "label"
"GOTO label": jumps to "label"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

  任何不同于“REM”、“SETLINVEL”、“SETROTVEL”、“MOVE”、“WAIT”、“SETBIT”、“CLEARBIT”、“IFBITGOTO”、“IFNBITGOTO”和“GOTO”的单词都被认为是标签。 现在运行模拟。 如果未找到相关插件,将显示以下消息(消息的显示在附加到对象MTB_Robot和MTB_Robot#0的子脚本中处理):

在这里插入图片描述

  如果找到相关插件,则MTB插件将启动一个服务器应用程序(即mtbServer),该应用程序基本上代表机器人语言解释器和控制器。 不直接需要服务器应用程序,mtbServer功能也可以直接在MTB插件中运行。 在服务器应用程序中使用该功能的主要优势是:

  • MTB插件可以根据需要充当任意多个不同语言的中间语言,也可以充当那些尚未开发的语言的中间语言:MTB插件将根据所使用的机器人/语言简单地启动适当的服务器。
  • 如果机器人语言解释器/控制器崩溃,CoppeliaSim不会崩溃,因为这两个进程是截然不同的独立进程。

目前,MTB服务器主要负责两项任务:

  • 从MTB插件接收程序代码(即缓冲区),编译它,并初始化机器人控制器。
  • 施加输入信号,逐步执行程序代码(步骤持续时间可以不同),并返回输出信号和关节角度。

  如果MTB服务器在编译程序代码期间检测到错误,它将向插件返回一条错误消息,并将其移交给调用子脚本(即,在我们的示例中,是附加到对象MTB_Robot和MTB_Robot#0的子脚本),该脚本将显示(例如):

在这里插入图片描述

  如果编译成功,则机器人开始执行它们各自的程序。 仿真是最高速度仿真,但可以通过切换相关工具栏按钮切换到实时仿真:

在这里插入图片描述

  通过多次按下相应的工具栏按钮,可以进一步加快执行速度:

在这里插入图片描述

  每个MTB机械手程序都可以通过其显示的自定义对话框(即自定义用户界面)随时单独暂停、停止或重新启动:

在这里插入图片描述

  上面的自定义UI是MTB机器人的用户界面,可以完全定制。 如果MTB机器人被复制,那么它的自定义UI也将被复制。 除了能够控制程序执行状态之外,自定义UI还显示当前程序行(命令)和MTB的当前连接值。 用户还可以更改机器人的输入端口位,并读取机器人的输出端口位。 输入和输出端口可由机器人语言程序分别读取和写入。 输入和输出端口也可以由外部设备(例如,机器人的抓取器或吸盘)通过使用适当的函数调用来写入和读取(见下文)。
  有两个子脚本附加到MTB_Robot和MTB_Robot#0对象。 他们负责处理自定义对话框并与MTB插件通信。 子脚本中的大多数代码也可以由插件处理。 打开附加到两个MTB机器人之一的子脚本(例如,双击场景层次中机器人模型旁边的脚本图标)。在脚本的顶部,您将看到机器人语言代码。
  尝试修改MTB机器人的程序,使其执行不同的运动序列。 做一点实验。
  MTB机器人的操作方式如下:

  • 实际的机器人语言程序由“mtbServer”应用程序编译和执行。 该应用程序还保存MTB机器人的状态变量。 对于模拟场景中的每个MTB机器人,将有一个由simExtMtb插件启动的mtbServer应用程序的实例。
  • SimExtMtb插件负责提供自定义Lua函数,并在需要时启动mtbServer应用程序,并通过套接字通信与其通信。
  • 附加到mtb_Robot和mtb_Robot#0的子脚本检查是否加载了simExtMtb插件,为每个机器人更新基于OpenGL的自定义UI,并处理与插件的通信。

  MTB机器人及其简单的机器人语言是一个简单的原型,旨在演示如何将机器人语言解释器集成到CoppeliaSim中。 将当前的功能扩展到更复杂的机器人或机器人语言非常容易。 所需要的只是:

  • 建立机器人的模型。 这包括导入CAD数据、添加运动类型等。此步骤完全可以在CoppeliaSim中完成。
  • 编写插件以本地处理新机器人,即通过解释其自己的机器人语言来处理新机器人。 任何能够访问C-API函数并能够包装在DLL中的语言都可以用来创建插件(但最好使用c/c++)。 机器人语言解释器可以直接嵌入到插件中,也可以像本教程中那样作为外部应用程序(MtbServer)启动。
  • 编写一个小的子脚本,负责处理自定义对话框,并将机器人与插件链接起来。 此步骤完全可以在CoppeliaSim中完成。

  现在我们来看一下MTB的插件项目。 在CoppeliaSim中嵌入机器人语言解释器(或其他仿真器)有一个先决条件:

  • 机器人语言解释器应该能够并行执行几次。 这意味着应该支持几个解释器实例,以便支持几个相同的、并行操作的机器人。 这可以通过为每个新的机器人启动一个新的解释器来最容易地处理,就像本教程中所做的那样。

  在编写任何插件时,请确保插件只能从主线程(或从由CoppeliaSim创建的线程)访问CoppeliaSim的常规API! 插件可以启动新线程,但在这种情况下,这些新线程不应该用于访问CoppeliaSim(但是,它们可以用于与服务器应用程序通信、与某些硬件通信、执行后台计算等)。

  现在让我们看一下附加到MTB机器人的子脚本。 代码可能看起来相当长或很复杂。 但是,在子脚本中处理的大多数功能也可以在插件中直接处理,从而使子脚本变得更小/更整洁。 处理子脚本中的大多数功能的优势在于,无需重新编译插件即可执行修改!

  以下是MTB机械手的子脚本主要功能:

  • 检查插件是否已加载。 如果不是,则输出错误消息。
  • 与插件通信。 这意味着使用自定义Lua函数向MTB插件发送信息和从MTB插件接收信息。
  • 将新计算的关节值应用于MTB机器人模型。 这也可以在MTB的插件中处理。
  • 对自定义对话框上的事件(如按钮按下)做出反应。
  • 更新自定义对话框的状态。

  以下3个自定义Lua函数是主要感兴趣的(其他函数由插件导出):

number mtbServerHandle,string message=simMTB.startServer(string mtbServerExecutable,
    number portNumber,charBuffer program,table_4 jointPositions, table_2 velocities)
number result,string message=simMTB.step(number mtbServerHandle,number timeStep)
table_4 jointValues=simMTB.getJoints(number mtbServerHandle)
  • 1
  • 2
  • 3
  • 4
  • SimMTB.startServer:在指定端口上启动服务器应用程序(例如mtbServer),连接到它,并向其发送机器人语言代码、初始关节位置和初始速度。 反过来,该函数返回一个服务器句柄(如果成功)和一条消息(通常是编译错误消息)。
  • SimMTB.step:以指定的时间步长遍历机器人语言程序,并返回一个结果值和一条消息(通常是当前正在执行的代码)。
  • SimMTB.getJoints:检索当前关节位置。 调用simMTB.step时会自动更新关节位置。

  您还可以设想稍微修改STEP函数,并添加一个额外的函数,以便能够处理由机器人语言程序执行触发的中间事件。 在这种情况下,每个模拟步骤都必须执行以下脚本代码(在非线程化的子脚本中):

local dt=sim.getSimulationTimeStep()
while (dt>0) do
    result,dt,cmdMessage=simMTB.step(mtbServerHandle,dt) -- where the returned dt is the remaining dt
    local event=simMTB.getEvent()
    while event~=-1 do
        -- handle events here
        event=simMTB.getEvent()
    end
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

9.9 传送带/履带教程

  在本教程中,我们将从A到Z构建传送带(或履带,在这种情况下,请查看本教程的最后部分)。下图说明了我们将设计的仿真场景:

在这里插入图片描述

  我们将构建一个传送带,它的行为应该与真实传送带完全一样,其中每个传送带垫都是单独动态模拟的。 例如,这意味着较小的物体可能会被困在两个相邻的垫子之间。 这种类型的模拟可能计算非常密集,并会减慢整个模拟过程。 还有一种替代的、简化的方法来模拟传送带,该方法也将在本教程中进行说明,并明确标记为方法B(与模拟单个垫的方法A形成对比)。

  首先,启动CoppeliaSim。 上图中的传送带基本上由沿其轨迹驱动多个垫的路径对象构成。 用[Popup Menu–>Add–>Path–>Circle type]添加循环路径。 要从上方查看路径,请切换到第6页。使用布满视图工具栏按钮将摄像机拉近:

在这里插入图片描述

  选择路径对象后,请注意路径是如何由蓝点定义的,在蓝点之间执行Bezier插值。 您还可以区分表示路径位置的红色球体,它不是路径位置,而是沿路径的位置。

  在将焊盘连接到路径之前,让我们准备正确的路径大小和形状。 您可以导入路径,也可以修改和编辑现有路径,我们将选择第二种方法。 选择路径时,通过单击路径编辑模式工具栏按钮进入路径编辑模式:

在这里插入图片描述

  现在处于路径编辑模式。 我们想设计一条厚10厘米,宽20厘米,长1米的传送带。 组成传送带的单个垫厚度为5毫米。

  在路径编辑模式对话框中,检查路径是否平坦,并保持x向上。 选择所有路径点,然后打开位置对话框,在位置缩放选项卡上,在右侧输入比例因子“0.19”的3倍,然后单击“缩放位置”。 这只是适当地缩放了路径。 使用鼠标滚轮靠近路径。 选择最上面的路径点。 使用ctrl-c复制它。 然后再次选择它,并使用ctrl-v将缓冲区粘贴到所选位置之后。 我们刚刚创建了一个与最上面的路径点重合的路径点:我们复制了路径点#13,并在随后粘贴了它的副本。 新路径点是路径点#14,如下图所示:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  现在,对最低的路径点重复相同的过程。 现在我们已经复制了两个中间路径点,我们可以拉伸路径,即将左侧和右侧部分分开:在场景层次中选择路径点#6到路径点#14,然后在位置对话框的平移选项卡上,对于沿X的项目,输入“-0.5”,然后单击X-Translate Selection。 现在稍微缩小一点。 这是应该得到的:

在这里插入图片描述

  现在选择左侧的路径点,并以类似的方式将它们朝正x坐标偏移0.5米。 路径已就绪:

在这里插入图片描述

  离开路径编辑模式,选择路径,并在主窗口的信息文本部分注意到行“Last Selected Object type:Path(Bezier曲线点计数=270,总长度=2.2985,p=+0.0000,vn=+0.0000)”。 这告诉我们这条小路的长度是2.2985米。 现在我们可以计算出我们需要多少个焊盘,它们的宽度,以及焊盘之间的距离应该是多少。 我们有40个焊盘,宽度为5厘米,因此焊盘之间的距离是0.75厘米。

  单击[弹出菜单–>添加–>基本体形状–>长方体]。 此时将显示基本体形状对话框,您可以在其中调整各种参数。 输入(0.05,0.005,0.18)作为x、y和z大小。

方法A。

只需单击“确定”即可。 这添加了一个动态的、可响应场景的纯形状。 切换到第1页。现在您可以看到添加的形状。 将其重命名为“pad0”(可以通过在场景层次中双击任何对象的名称来重命名该对象)。 使用位置对话框将焊盘的绝对z坐标设置为0。 双击场景层次中的PAD图标以打开图形属性对话框。 调整其颜色(单击调整外部颜色项)。 然后,在形状动态属性对话框中,检查静态项,以便在模拟过程中不会使垫子掉落。 在对象通用属性中,选中以下项目:改为选择模型基础、可碰撞、可测量、可渲染和所有可检测属性。 在Visibility Layers部分中,同时启用Layer 9(但同时保持Layer 1处于启用状态)。

方法B。

取消选中Create Dynamic and Responsible Shape项,然后单击OK。 这将为场景添加一个静态的纯形状。 切换到第1页。现在您可以看到添加的形状。 将其重命名为“pad0”(可以通过在场景层次中双击任何对象的名称来重命名该对象)。 使用位置对话框将焊盘的绝对z坐标设置为0。 双击场景层次中的PAD图标以打开图形属性对话框。 调整其颜色(单击调整外部颜色项)。 在对象通用属性中,选中以下项目:改为选择模型基础、可碰撞、可测量、可渲染和所有可检测属性。 在方法B中,传送带垫没有动力功能!

  接下来,如果使用sim.setPathPosition修改了路径的固有位置,我们希望将焊盘附加到路径,以便它自动跟随路径的轨迹。 对于此任务,我们需要一个辅助对象:一个虚拟对象。 单击[弹出菜单–>添加–>虚拟]。 将虚拟对象重命名为“padLink0”。 通过方向选项卡上的方向对话框将虚拟对象的方向调整为(0,-90,0)。 通过点击焊盘,然后点击虚拟对象,然后点击[Popup Menu–>Edit–>Make Last Selected Object Parent],将焊盘连接到虚拟对象。 接下来,以类似的方式将虚拟对象附加到路径(也可以通过在场景层次中拖放来实现为父对象)。 双击场景层次中的虚拟图标以打开虚拟属性对话框。 检查Follow Parent Path(Only Direct Parent)项:注意虚拟对象和焊盘是如何跳到路径的红色球体位置的。 在虚拟人仍处于选中状态时,将复制增量项设置为“0.0575”。 这表示如果复制虚拟对象,则其在路径上的偏移将自动递增0.0575米,即焊盘宽度加上焊盘间距离。 在对象公用属性对话框中,隐藏层11中的虚拟对象(取消激活层3并激活层11)。

  现在我们将添加剩余的39个焊盘。 选择虚拟对象和焊盘,然后使用ctrl-c复制所选内容。 然后按ctrl-v键39次,准确粘贴缓冲区39次。 用Esc-键清除选择,然后在场景层次结构中选择“padLink1”到“padLink39”(确保您没有选择焊盘,而只选择了Dummies!),然后选择路径,然后点击[Popup Menu–>Edit–>Make Last Selected Object Parent]。 这是你应该拥有的:

在这里插入图片描述

  下一步,我们将为传送带添加一个简化的外壳。 将尺寸为(0.12,0.12,0.2)的纯圆柱体添加到场景中。 将其z位置设置为0,将x位置设置为0.5,然后调整其颜色。 复制并粘贴,然后将副本移动到x坐标-0.5米。 添加一个带有维度(1.0,0.09,0.18)的纯长方体。 将其z位置设置为0,并调整其颜色。 选择刚才添加的两个圆柱体和长方体,然后单击[弹出菜单–>编辑–>分组/合并–>分组选定的形状]。 将生成的形状重命名为“传送带”。 在图形动态属性对话框中,将“传送带”设为静态,并在对象的通用属性中,选中“Colliable”、“Measable”、“Renderable”和“All Detect”属性。 同时按可见层按钮9。然后将路径连接到“传送带”上。

方法B。

添加一个带有维度(1.0,0.1,0.18)的纯长方体。 将其z位置设置为0。 将生成的形状重命名为“transportorForwarder”。 在Shape对话框中,将“TransportorForwarder”设为静态,然后在Object通用属性对话框中将对象发送到可见层9(禁用按钮1和启用按钮9)。 然后在“传送带”上贴上“传送带”。 “ConveyorForwarder”是一个物体,它将移动躺在上面的其他物体和一个小把戏(见下文)。

在这里插入图片描述

  现在将传送带主体绕绝对x轴旋转90度,并将其坐标设置为(0.0,0.0,0.5)。 选择路径,然后在路径属性中取消选中显示路径线、显示点方向和显示路径上的当前位置。 选择“传送带”,在对象通用属性对话框中,检查对象是否为模型基项。 单击编辑模型属性,然后在模型内容确认/信息部分中,添加您希望在每次加载传送带模型时显示的一些文本。 最后从“传送带”开始折叠层次树。 我们的模型差不多准备好了:

在这里插入图片描述

  请注意,单击传送带模型上的任何对象,整个模型都会被选中。 如果要选择单个对象,仍可以在场景层次中或通过按住Shift和Ctrl键(同时使用这两个键!)。 单击对象时。

  还可以通过API函数sim.setPathPosition修改路径的固有位置(因此,路径的移动)。 有关如何执行此操作的示例,请查看模型浏览器中的其他传送带模型。

方法A。

选择“传送带”对象,点击[菜单栏–>添加–>关联子脚本–>非线程化]。 这只是将一个非线程化的子脚本附加到模型库。 双击场景层次中的子脚本图标以打开子脚本。 将该脚本替换为以下代码:

function sysCall_init()
    pathHandle=sim.getObjectHandle("Path")
    sim.setPathTargetNominalVelocity(pathHandle,0) -- for backward compatibility
end

function sysCall_cleanup()
 
end 

function sysCall_sensing()

end 

function sysCall_actuation()
    beltVelocity=0.1 -- in meters/seconds
    local dt=sim.getSimulationTimeStep()
    local pos=sim.getPathPosition(pathHandle)
    pos=pos+beltVelocity*dt
    sim.setPathPosition(pathHandle,pos) -- update the path's intrinsic position
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

上述代码有效地完成了以下工作:在每个模拟通道中,修改路径的固有位置,以生成传送带的固有运动。

方法B。

选择“传送带”对象,点击[菜单栏–>添加–>关联子脚本–>非线程化]。 这只是将一个非线程化的子脚本附加到模型库。 双击场景层次中的子脚本图标以打开子脚本。 将该脚本替换为以下代码:

function sysCall_init()
    pathHandle=sim.getObjectHandle("Path")
    forwarder=sim.getObjectHandle('conveyorForwarder')
    sim.setPathTargetNominalVelocity(pathHandle,0) -- for backward compatibility
end

function sysCall_cleanup()
 
end 

function sysCall_sensing()

end 

function sysCall_actuation()
    beltVelocity=0.1 -- in meters/seconds
    local dt=sim.getSimulationTimeStep()
    local pos=sim.getPathPosition(pathHandle)
    pos=pos+beltVelocity*dt
    sim.setPathPosition(pathHandle,pos) -- update the path's intrinsic position

    -- Here we "fake" the transportation pads with a single
    -- static cuboid that we dynamically reset at each
    -- simulation pass (while not forgetting to set its initial
    -- velocity vector) :

    local relativeLinearVelocity={-beltVelocity,0,0}
    -- Reset the dynamic cuboid from the simulation
    -- (it will be removed and added again):
    sim.resetDynamicObject(forwarder)
    -- Compute the absolute velocity vector:
    local m=sim.getObjectMatrix(forwarder,-1)
    m[4]=0 -- Make sure the translation component is discarded
    m[8]=0 -- Make sure the translation component is discarded
    m[12]=0 -- Make sure the translation component is discarded
    local absoluteLinearVelocity=sim.multiplyVector(m,relativeLinearVelocity)
    -- Now set the initial velocity of the dynamic cuboid:
    sim.setObjectFloatParameter(forwarder,3000,absoluteLinearVelocity[1])
    sim.setObjectFloatParameter(forwarder,3001,absoluteLinearVelocity[2])
    sim.setObjectFloatParameter(forwarder,3002,absoluteLinearVelocity[3])
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

上述代码有效地完成了以下工作:在每个模拟通道中,修改路径的固有位置,以生成传送带的固有运动。 同时,在每个模拟过程中,都会动态重置(从动力学模拟中移除,然后再次直接将其直接添加到动力学模拟中)和初始速度集。 初始速度就是使躺在上面的其他物体移动的东西! 这比单独模拟每个传送带衬垫的计算强度要小得多。

  为结束本教程,我们将保存刚刚创建的模型,以便它将显示在CoppeliaSim的模型浏览器中。 选择模型,然后单击[菜单栏–>文件–>模型另存为…]。 此时将显示一个对话框,允许调整模型缩略图。 一旦您对缩略图感到满意,单击OK并导航到CoppeliaSim的“Models/Equipment”文件夹并保存模型。

制作履带

  在CoppeliaSim中,履带是通过沿着履带使用几个动态圆柱体来模拟的,以给人一种履带正在移动车辆的印象,而实际上是这些圆柱体在移动! 这意味着履带垫只是“眼花缭乱”,动态不启用(它们应该是静态的和无响应的)。 创建履带的方法实际上非常类似于使用方法B制作传送带。下图说明了CoppeliaSim中的履带概念:

在这里插入图片描述

在这里插入图片描述

9.10 ROS教程

9.10.1 ROS教程

  本教程将尝试以一种简单的方式来说明如何基于ROS Melodic和Catkin build来启用CoppeliaSim ROS。

  首先,您应该确保已学过官方ROS教程(至少是初学者部分),并且已安装Catkin工具。然后,我们假设您正在运行最新的Ubuntu,已安装ROS,并且已设置工作空间文件夹。在此还请参阅有关ROS安装的官方文档。

  通过ROS接口(libsimExtROSInterface.so)支持CoppeliaSim中的常规ROS功能。 Linux发行版应包括已经在CoppeliaSim / compiledROSPlugins中编译的文件,但首先需要将其复制到CoppeliaSim /,否则将不会被加载。但是,根据系统的特性,您可能会遇到插件加载问题:请确保始终检查CoppeliaSim的终端窗口以获取有关插件加载操作的详细信息。启动CoppeliaSim时将加载插件。ROS插件只有在roscore正在运行的情况下才能成功加载和初始化。(roscore是ROS 主控程序)。另外,在运行CoppeliaSim之前,请确保获取ROS环境。

  如果无法加载该插件,则应自己重新编译。它是开源的,可以根据需要进行任意修改,以支持特定功能或扩展其功能。如果需要支持特定的消息、服务等,请确保在重新编译之前编辑simExtROSInterface/meta/中的文件。有2个软件包:

  • simExtROSInterface:此软件包是ROS接口,将被编译为一个.so文件,供CoppeliaSim使用。
  • ros_bubble_rob:这是一个非常简单的机器人控制器的软件包,该控制器通过ROS接口连接到CoppeliaSim。该节点将负责控制演示场景中的红色机器人controlTypeExamples/controlledViaRos.ttt

  以上软件包应复制到您的catkin_ws / src文件夹中。确保ROS知道这些软件包,即可以使用以下命令切换到以上软件包文件夹:

$ roscd sim_ros_interface
$ roscd ros_bubble_rob
  • 1
  • 2

  为了生成软件包,请打开catkin_ws文件夹并输入:

$ export COPPELIASIM_ROOT_DIR=~/path/to/coppeliaSim/folder
$ catkin build --cmake-args -DCMAKE_BUILD_TYPE=Release -DLIBPLUGIN_DIR=$COPPELIASIM_ROOT_DIR/programming/libPlugin
  • 1
  • 2

  就这样,软件包应该已经生成并编译为可执行文件或库。 将创建的文件复制并粘贴到CoppeliaSim安装文件夹。 插件现在可以使用了!

  现在打开一个终端,使用以下命令启动ROS主机:

$ roscore
  • 1

  打开另一个终端,移动到CoppeliaSim安装文件夹并启动CoppeliaSim。显示如下:

$ ./coppeliaSim.sh
...
Plugin 'ROSInterface': loading...
Plugin 'ROSInterface': load succeeded.
...
  • 1
  • 2
  • 3
  • 4
  • 5

  成功加载ROS接口后,检查可用节点将得到以下信息:

$ rosnode list
/rosout
/sim_ros_interface
  • 1
  • 2
  • 3

  在空的CoppeliaSim场景中,选择一个对象,然后使用[菜单栏->添加->关联的子脚本->非线程]将非线程的子脚本附加到该对象。 打开该脚本的脚本编辑器,并将内容替换为以下内容:

function subscriber_callback(msg)
    -- This is the subscriber callback function
    sim.addStatusbarMessage('subscriber receiver following Float32: '..msg.data)
end

function getTransformStamped(objHandle,name,relTo,relToName)
    -- This function retrieves the stamped transform for a specific object
    t=sim.getSystemTime()
    p=sim.getObjectPosition(objHandle,relTo)
    o=sim.getObjectQuaternion(objHandle,relTo)
    return {
        header={
            stamp=t,
            frame_id=relToName
        },
        child_frame_id=name,
        transform={
            translation={x=p[1],y=p[2],z=p[3]},
            rotation={x=o[1],y=o[2],z=o[3],w=o[4]}
        }
    }
end

function sysCall_init()
    -- The child script initialization
    objectHandle=sim.getObjectAssociatedWithScript(sim.handle_self)
    objectName=sim.getObjectName(objectHandle)
    rosInterfacePresent=simROS

    -- Prepare the float32 publisher and subscriber (we subscribe to the topic we advertise):
    if rosInterfacePresent then
        publisher=simROS.advertise('/simulationTime','std_msgs/Float32')
        subscriber=simROS.subscribe('/simulationTime','std_msgs/Float32','subscriber_callback')
    end
end

function sysCall_actuation()
    -- Send an updated simulation time message, and send the transform of the object attached to this script:
    if rosInterfacePresent then
        simROS.publish(publisher,{data=sim.getSimulationTime()})
        simROS.sendTransform(getTransformStamped(objectHandle,objectName,-1,'world'))
        -- To send several transforms at once, use simROS.sendTransforms instead
    end
end

function sysCall_cleanup()
    -- Following not really needed in a simulation script (i.e. automatically shut down at simulation end):
    if rosInterfacePresent then
        simROS.shutdownPublisher(publisher)
        simROS.shutdownSubscriber(subscriber)
    end
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

  上面的脚本将发布模拟时间,并同时订阅它。 它还将发布脚本附加到的对象的转换。 您应该可以使用以下内容查看仿真时间话题:

$ rostopic list
  • 1

  要查看消息内容,可以键入:

$ rostopic echo /simulationTime
  • 1

  现在,加载演示场景rosInterfaceTopicPublisherAndSubscriber.ttt,并运行模拟。 附加到Vision_sensor的子脚本中的代码将使发布者可以流式传输视觉传感器的图像,还可以使订阅者收听相同的流。 订阅者将读取的数据应用于被动视觉传感器,该被动视觉传感器仅用作数据容器。 因此,CoppeliaSim在监听相同数据的同时传输数据! 这是正在发生的事情:

在这里插入图片描述

  尝试以下代码。 您还可以使用以下命令可视化CoppeliaSim流式传输的图像:

$ rosrun image_view image_view image:=/visionSensorData
  • 1

  如果您正在流传输更简单的数据,那么您还可以通过以下方式将其可视化:

$ rostopic echo /visionSensorData
  • 1

  现在停止模拟并加载演示场景controlTypeExamples/controlledViaRos.ttt,然后运行模拟。 机器人是简单化的,并且出于简化目的也以简单的方式表现。 它是通过ROS接口控制的:

在这里插入图片描述

附加到机器人的子脚本以非线程方式运行,它负责以下工作:

  • 确定一些物体的手柄(例如,马达关节手柄和接近传感器手柄)
  • 验证是否已加载ROS接口
  • 启动电机速度订阅
  • 启动传感器发布者和模拟时间发布者
  • 最后启动客户端应用程序。使用某些主题名称作为参数调用该应用程序,以便它将知道要监听和订阅的主题。然后,客户端应用程序(rosBubbleRob)将通过ROS进行机器人的控制。

  运行模拟时,复制并粘贴几次机器人。请注意,每个副本都是直接可操作且独立的。这是CoppeliaSim的众多优势之一。

  现在停止模拟并打开一个新场景,然后将以下模型拖入其中:Models / tools / rosInterface_helper_tool.ttm。此模型由提供以下主题发布者和订阅者的单个定制脚本构成:

  • startSimulation主题:可通过在此主题上发布std_msgs::Bool消息来启动仿真。
  • pauseSimulation主题:可通过在此主题上发布std_msgs::Bool消息来暂停模拟。
  • stopSimulation主题:可通过在此主题上发布std_msgs::Bool消息来停止模拟。
  • enableSyncMode主题:可通过在该主题上发布std_msgs::Bool消息,可以启用/禁用同步仿真模式。
  • triggerNextStep主题:可通过在该主题上发布std_msgs::Bool消息,可以在同步仿真模式下触发下一个仿真步骤。
  • SimulationStepDone主题:在每个模拟遍的末尾将发布std_msgs::Bool类型的消息。
  • SimulationState主题:std_msgs::Int32类型的消息将定期发布。 0表示仿真已停止,1表示正在运行,2表示已暂停。
  • SimulationTime主题:std_msgs::Float32类型的消息将定期发布,指示当前的模拟时间。

查看定制脚本的内容,该脚本可以针对各种目的进行完全定制。尝试从命令行生成主题消息,例如:

$ rostopic pub /startSimulation std_msgs/Bool true --once
$ rostopic pub /pauseSimulation std_msgs/Bool true --once
$ rostopic pub /stopSimulation  std_msgs/Bool true --once
$ rostopic pub /enableSyncMode  std_msgs/Bool true --once
$ rostopic pub /startSimulation std_msgs/Bool true --once
$ rostopic pub /triggerNextStep std_msgs/Bool true --once
$ rostopic pub /triggerNextStep std_msgs/Bool true --once
$ rostopic pub /triggerNextStep std_msgs/Bool true --once
$ rostopic pub /stopSimulation  std_msgs/Bool true --once
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

为了显示当前的模拟时间,您可以输入:

$ rostopic echo /simulationTime
  • 1

  最后,确保查看一下CoppeliaSim中的远程API功能和BlueZero框架:与ROS相似,它允许远程执行功能,来回快速流数据,易于使用,重量轻且跨平台。 远程API功能可用于7种不同的语言。 在某些情况下,远程APi和BlueZero框架都是ROS的有趣替代品。

9.10.2 ROS2教程

  本教程将尝试以一种简单的方式来说明如何基于ROS 2 Dashing来启用CoppeliaSim ROS 2。

  首先,您应确保至少已经学习过了ROS官方教程,至少是初学者部分。然后,我们假设您正在运行最新的Ubuntu,已安装ROS,并且已设置工作空间文件夹。在此还请参阅有关ROS 2安装的官方文档。

  通过ROS接口(libsimExtROS2Interface.so)支持CoppeliaSim中的常规ROS功能。 Linux发行版应包括已经在CoppeliaSim / compiledROSPlugins中编译过的文件,但是首先需要将其复制到CoppeliaSim /中,否则将不会被加载。但是,您可能会遇到插件加载问题,具体取决于您的系统特性:请确保始终检查CoppeliaSim的终端窗口以获取有关插件加载操作的详细信息。启动CoppeliaSim时将加载插件。另外,在运行CoppeliaSim之前,请确保获取ROS环境。

  如果无法加载该插件,则应自己重新编译。它是开源的,可以根据需要进行任意修改,以支持特定功能或扩展其功能。如果需要支持特定的消息、服务等,请确保在重新编译之前编辑simExtROSInterface / meta /中的文件。有2个软件包:

  • simExtROS2Interface:此软件包是ROS 2接口,将被编译成一个“ .so”文件,并且由CoppeliaSim使用。
  • ros2_bubble_rob:这是一个非常简单的机器人控制器的软件包,该机器人控制器通过ROS接口连接到CoppeliaSim。该节点将负责控制演示场景中的红色机器人controlTypeExamples/controlledViaRos.ttt

  以上软件包应复制到ros2_ws / src文件夹中。

  为了生成软件包,请打开ros2_ws文件夹并键入:

$ export COPPELIASIM_ROOT_DIR=~/path/to/coppeliaSim/folder
$ ulimit -s unlimited #otherwise compilation might freeze/crash
$ colcon build --symlink-install --cmake-args -DCMAKE_BUILD_TYPE=Release -DLIBPLUGIN_DIR=$COPPELIASIM_ROOT_DIR/programming/libPlugin
  • 1
  • 2
  • 3

  就这样,软件包应该已经生成并编译为可执行文件或库。 将创建的文件复制并粘贴到CoppeliaSim安装文件夹。 插件现在可以使用了!

  现在打开一个终端,移至CoppeliaSim安装文件夹并启动CoppeliaSim。 这是您应该拥有的(或类似的):

$ ./coppeliaSim.sh
...
Plugin 'ROS2Interface': loading...
Plugin 'ROS2Interface': load succeeded.
...
  • 1
  • 2
  • 3
  • 4
  • 5

  成功加载ROS2接口后,检查可用节点将得到以下信息:

$ ros2 node list
/sim_ros2_interface
  • 1
  • 2

  在CoppeliaSim的空场景中,选择一个对象,然后使用[菜单栏->添加->关联的子脚本->非线程]将非线程的子脚本附加到该对象。 打开该脚本的脚本编辑器,并将内容替换为以下内容:

function subscriber_callback(msg)
    -- This is the subscriber callback function
    sim.addStatusbarMessage('subscriber receiver following Float32: '..msg.data)
end

function getTransformStamped(objHandle,name,relTo,relToName)
    -- This function retrieves the stamped transform for a specific object
    t=sim.getSystemTime()
    p=sim.getObjectPosition(objHandle,relTo)
    o=sim.getObjectQuaternion(objHandle,relTo)
    return {
        header={
            stamp=t,
            frame_id=relToName
        },
        child_frame_id=name,
        transform={
            translation={x=p[1],y=p[2],z=p[3]},
            rotation={x=o[1],y=o[2],z=o[3],w=o[4]}
        }
    }
end

function sysCall_init()
    -- The child script initialization
    objectHandle=sim.getObjectAssociatedWithScript(sim.handle_self)
    objectName=sim.getObjectName(objectHandle)
    ros2InterfacePresent=simROS2

    -- Prepare the float32 publisher and subscriber (we subscribe to the topic we advertise):
    if ros2InterfacePresent then
        publisher=simROS2.advertise('/simulationTime','std_msgs/Float32')
        subscriber=simROS2.subscribe('/simulationTime','std_msgs/Float32','subscriber_callback')
    end
end

function sysCall_actuation()
    -- Send an updated simulation time message, and send the transform of the object attached to this script:
    if ros2InterfacePresent then
        simROS2.publish(publisher,{data=sim.getSimulationTime()})
        simROS2.sendTransform(getTransformStamped(objectHandle,objectName,-1,'world'))
        -- To send several transforms at once, use simROS2.sendTransforms instead
    end
end

function sysCall_cleanup()
    -- Following not really needed in a simulation script (i.e. automatically shut down at simulation end):
    if rosInterfacePresent then
        simROS.shutdownPublisher(publisher)
        simROS.shutdownSubscriber(subscriber)
    end
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

  上面的脚本将发布仿真时间,并同时订阅它。 它还将发布脚本附加到的对象的转换。 您应该可以使用以下内容查看仿真时间话题:

$ ros2 topic list
  • 1

  要查看消息内容,可键入:

$ ros2 topic echo /simulationTime
  • 1

  现在,加载演示场景ros2InterfaceTopicPublisherAndSubscriber.ttt,并运行仿真。 附加到Vision_sensor的子脚本中的代码将使发布者可以流式传输视觉传感器的图像,还可以使订阅者收听相同的流。 订阅者将读取的数据应用于被动视觉传感器,该被动视觉传感器仅用作数据容器。 因此,CoppeliaSim在监听相同数据的同时传输数据! 这是正在发生的事情:

在这里插入图片描述

  尝试以下代码。 您还可以使用以下命令可视化CoppeliaSim流式传输的图像:

$ ros2 run image_view image_view image:=/visionSensorData
  • 1

  如果您正在流传输更简单的数据,那么您还可以通过以下方式将其可视化:

$ ros2 topic echo /visionSensorData
  • 1

  现在停止模拟并加载演示场景controlTypeExamples/controlledViaRos2.ttt,然后运行模拟。 机器人是简单化的,并且出于简化目的也以简单的方式表现。 它是通过ROS2接口控制的:

在这里插入图片描述

  附加到机器人的子脚本以非线程方式运行,它负责以下工作:

  • 确定一些物体的手柄(例如,马达关节手柄和接近传感器手柄)
  • 验证是否已加载ROS2接口
  • 启动电机速度订阅
  • 启动传感器发布者和模拟时间发布者
  • 最后启动客户端应用程序。使用某些主题名称作为参数调用该应用程序,以便它将知道要监听和订阅的主题。然后,客户端应用程序(ros2BubbleRob)将通过ROS2进行机器人的控制。

  运行模拟时,复制并粘贴几次机器人。请注意,每个副本都是直接可操作且独立的。这是CoppeliaSim的众多优势之一。

  现在停止模拟并打开一个新场景,然后将以下模型拖入其中:Models / tools / ros2Interface_helper_tool.ttm。此模型由提供以下主题发布者和订阅者的单个定制脚本构成:

  • startSimulation主题:可通过在此主题上发布std_msgs::Bool消息来启动仿真。
  • pauseSimulation主题:可通过在此主题上发布std_msgs::Bool消息来暂停模拟。
  • stopSimulation主题:可通过在此主题上发布std_msgs::Bool消息来停止模拟。
  • enableSyncMode主题:可通过在该主题上发布std_msgs::Bool消息,可以启用/禁用同步仿真模式。
  • triggerNextStep主题:可通过在该主题上发布std_msgs::Bool消息,可以在同步仿真模式下触发下一个仿真步骤。
  • SimulationStepDone主题:在每个模拟遍的末尾将发布std_msgs::Bool类型的消息。
  • SimulationState主题:std_msgs::Int32类型的消息将定期发布。 0表示仿真已停止,1表示正在运行,2表示已暂停。
  • SimulationTime主题:std_msgs::Float32类型的消息将定期发布,指示当前的模拟时间。

  查看定制脚本的内容,该脚本可以针对各种目的进行完全定制。尝试从命令行生成主题消息,例如:

$ ros2 topic pub /startSimulation std_msgs/Bool '{data: true}' --once
$ ros2 topic pub /pauseSimulation std_msgs/Bool '{data: true}' --once
$ ros2 topic pub /stopSimulation  std_msgs/Bool '{data: true}' --once
$ ros2 topic pub /enableSyncMode  std_msgs/Bool '{data: true}' --once
$ ros2 topic pub /startSimulation std_msgs/Bool '{data: true}' --once
$ ros2 topic pub /triggerNextStep std_msgs/Bool '{data: true}' --once
$ ros2 topic pub /triggerNextStep std_msgs/Bool '{data: true}' --once
$ ros2 topic pub /triggerNextStep std_msgs/Bool '{data: true}' --once
$ ros2 topic pub /stopSimulation  std_msgs/Bool '{data: true}' --once
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

  为了显示当前的仿真时间,你可以键入:

$ ros2 topic echo /simulationTime
  • 1

  最后,确保查看一下CoppeliaSim中的远程API功能和BlueZero框架:与ROS相似,它允许远程执行功能,来回快速流数据,易于使用,重量轻且跨平台。 远程API功能可用于7种不同的语言。 在某些情况下,远程APi和BlueZero框架都是ROS的有趣替代品。

编译CoppeliaSim

  可以在此处找到CoppeliaSim的完整源代码(包括大多数插件源代码和其他)。虽然CoppeliaSim库(coppeliaSimLib)是GNU GPL许可的,但是几何插件(simExtGeometric)附带了特定的许可证。简而言之,未经明确许可,只有教育实体(学生、教师、教授、学校或大学)可以下载和使用该插件。在进行此操作之前,请确保您了解并遵守许可条件。

  其他源代码项(例如,各种项目、插件、接口等)不在此处讨论。

  CoppeliaSimLib和几何插件是Qt项目,需要在计算机上安装Qt。尝试遵守以下要求列表,以便在编译过程中最少遇到问题:

  • 如果可能,请尝试使用与用于编译二进制CoppeliaSim Edu的版本相同的Qt版本(单击[帮助->关于CoppeliaSim Edu …]以显示Qt版本)
  • 对于Qt项目,请确保使用与用于编译二进制CoppeliaSim Edu的编译器相同的编译器。
  • Ubuntu是首选的Linux发行版。
  • 下载并编译QScintilla2(C ++编辑器控件)。
  • 下载并安装Boost C ++库。
  • 下载Lua5.1二进制文件和头文件。

  下载并安装CoppeliaSim Edu。然后将CoppeliaSimLib库源代码(以及可选的插件源代码)下载到CoppeliaSim Edu安装文件夹中。您应该具有以下文件夹结构:

在这里插入图片描述

  与插件一起使用CoppeliaSim时,请确保使用同一发行版中的源代码/二进制文件,以避免不兼容。最后,在能够编译CoppeliaSim项目之前,您将必须在config.pri文件中调整各种路径。

  CoppeliaSimLib编译为共享库。加载和运行该库的默认客户端应用程序是coppeliaSim或coppeliaSim.exe。您可以使用预编译的版本,也可以自己重新编译(请参阅以下项目文件:coppeliaSimClientApplication)。仅在以下情况下,库的加载操作才能成功:

  • 客户端应用程序可以在共享库中找到所有需要的功能
  • 共享库可以找到其自身依赖的所有共享库(例如Lua共享库等)

  简单的操作是将已编译的库复制到CoppeliaSim文件夹中,然后将确保CoppeliaSim应该启动并且不会抱怨缺少依赖项。

  不要混用各种Qt版本或来自各种编译器的二进制文件,这一点非常重要。如果您的主要CoppeliaSimLib库是使用Qt X和编译器Y编译的,则所有与CoppeliaSim相关的插件也应该已经使用Qt X和编译器Y编译,否则,您将遇到奇怪的行为(库无法加载,突然崩溃等)。如果您的插件未使用任何Qt函数,则可以放宽此要求。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/96707
推荐阅读
相关标签
  

闽ICP备14008679号