贡献指南#

我们衷心欢迎对项目的贡献,以使框架更加成熟并对大家更加有用。这些贡献可能以以下形式出现:

  • Bug 报告: 如果您发现任何 bug,请在 issue tracker 中报告。

  • 功能请求: 请提出您希望在 discussions 中看到的新功能。

  • 代码贡献: 请提交一个 pull request

    • 错误修复

    • 新特性

    • 文档改进

    • 教程和教程改进

我们更倾向于使用 GitHub discussions 来讨论想法、提问、交流和新功能的请求。

请仅使用 issue tracker 来跟踪具有明确范围和清晰交付物的可执行任务。这些任务可以是修复 bug、新功能或一般更新。

贡献代码#

注意

请参阅 Google Style Guide 以了解编码风格,然后再为代码库做贡献。在编码风格部分,我们概述了代码库中遵循的与风格指南的具体偏差。

我们使用 GitHub 进行代码托管。请按照以下步骤贡献代码:

  1. issue tracker 中创建一个问题,讨论您希望进行的更改或添加内容。这有助于我们避免重复工作,并确保这些更改与项目的路线图保持一致。

  2. Fork 仓库。

  3. 创建一个新分支用于您的更改。

  4. 进行更改并提交它们。

  5. 将您的更改推送到您的fork。

  6. 提交一个拉取请求到 main分支

  7. 确保对拉取请求模板执行所有检查。

在发送拉取请求之后,维护者将审查你的代码并提供反馈。

请确保您的代码格式良好、文档齐全,并且通过所有测试。

小技巧

保持拉取请求尽可能小是很重要的。这使得维护者更容易审查你的代码。如果你正在进行多个更改,请发送多个拉取请求。大型拉取请求很难审查,并且可能需要很长时间才能合并。

贡献文档#

为文档贡献的方式与为代码库贡献一样简单。所有文档的源文件都位于``IsaacLab/docs``目录中。文档使用`reStructuredText <https://docutils.sourceforge.io/rst.html>`__格式编写。

我们使用 SphinxBook Theme 来维护文档。

发送文档的拉取请求与发送代码库的拉取请求相同。请按照 Contributing Code 部分中提到的步骤进行操作。

小心

为了构建文档,我们推荐创建一个 virtual environment 来安装依赖项。这也可以是一个 conda environment

要构建文档,请在终端中运行以下命令,该命令将安装所需的 python 包并使用 docs/Makefile 构建文档:

./isaaclab.sh --docs  # or "./isaaclab.sh -d"

文档生成在 docs/_build 目录中。要查看文档,请打开 html 目录中的 index.html 文件。这可以通过在终端中运行以下命令来完成:

xdg-open docs/_build/current/index.html

提示

xdg-open 命令用于在默认浏览器中打开 index.html 文件。如果您使用的是不同的操作系统,可以使用适当的命令在浏览器中打开该文件。

要进行干净的构建,请在终端中运行以下命令:

rm -rf docs/_build && ./isaaclab.sh --docs

贡献资产#

目前,我们将扩展的资源托管在 NVIDIA Nucleus Server 上。Nucleus 是一种基于云的存储服务,允许用户存储和共享大型文件。它与 NVIDIA Omniverse Platform 集成。

由于所有资产都托管在 Nucleus 上,我们不需要将它们包含在代码库中。然而,我们需要在文档中包含指向资产的链接。

包含的资产是 Isaac Sim Content 的一部分。要使用这些内容,您可以使用 Isaac Sim 中提供的资产浏览器。

请查看 Isaac Sim documentation 以获取有关如何下载资产的更多信息。

注意

我们目前正在研究一种更好的方式来贡献资产。一旦我们有了解决方案,我们将更新此部分。在此期间,请按照下面提到的步骤进行操作。

要托管您自己的资源,当前的解决方案是:

  1. 创建一个单独的存储库用于资产,并将其添加到那里

  2. 确保资产已获得使用和分发的许可

  3. 在仓库的 README 文件中包含资产的图片

  4. 发送一个包含仓库链接的拉取请求

我们将验证资产及其许可,并将资产包含到 Nucleus 服务器中进行托管。如果您有任何问题,请随时通过电子邮件或在存储库中开启问题来联系我们。

维护changelog和extension.toml#

每个扩展都在 docs 目录中的 CHANGELOG.rst 文件中维护变更日志,并在 config 目录中有一个 extension.toml 文件。

extension.toml 文件包含扩展的元数据。它用于描述扩展的名称、版本、描述以及其他元数据。

CHANGELOG.rst 是一个文件,包含了每个版本扩展的经过整理、按时间顺序排列的显著更改列表。

备注

extension.toml 文件中的版本号应根据 Semantic Versioning 更新,并应与 CHANGELOG.rst 文件中的版本号匹配。

变更日志文件是以 reStructuredText 格式编写的。此变更日志的目标是帮助用户和贡献者精确地看到每个版本(或版本之间)的显著变化。这对于每个扩展来说都是*必须*的。

为了更新变更日志,请遵循以下指南:

  • 每个版本应有一个包含版本号和发布日期的章节。

  • 版本号根据 Semantic Versioning 进行更新。发布日期是版本发布的日期。

  • 每个版本根据所做更改的类型划分为子部分。

    • Added: 新功能。

    • Changed: 现有功能的更改。

    • Deprecated: 即将删除的功能。

    • Removed: 当前移除的功能。

    • Fixed: 用于任何错误修复。

  • 每个变化都在其对应的小节中以项目符号形式描述。

  • 项目要点采用 过去时 编写。

    • 这意味着这个变化被描述为已经发生过。

    • 项目要简明扼要,直奔主题。不应冗长。

    • 项目符号应当包括变更的原因,如果适用。

小技巧

有疑问时,请检查现有的 changelog 文件中的样式,并遵循相同的样式。

例如,以下是一个示例的更新日志:

Changelog
---------

0.1.0 (2021-02-01)
~~~~~~~~~~~~~~~~~~

Added
^^^^^

* Added a new feature that helps in a 10x speedup.

Changed
^^^^^^^

* Changed an existing feature. Earlier, we were using :meth:`torch.bmm` to perform the matrix multiplication.
  However, this was slow for large matrices. We have now switched to using :meth:`torch.einsum` which is
  significantly faster.

Deprecated
^^^^^^^^^^

* Deprecated an existing feature in favor of a new feature.

Removed
^^^^^^^

* Removed an existing feature. This was done to simplify the codebase and reduce the complexity.

Fixed
^^^^^

* Fixed crashing of the :meth:`my_function` when the input was too large.
  We now use :meth:`torch.einsum` that is able to handle larger inputs.

编码风格#

我们遵循 Google Style Guides 作为代码库的规范。对于 Python 代码,遵循 PEP 指南。最重要的包括 PEP-8 用于代码注释和布局, PEP-484PEP-585 用于类型提示。

对于文档,我们采用 Google Style Guide 来编写 docstring。我们使用 Sphinx 来生成文档。请确保您的代码有良好的文档说明并遵循指南。

循环导入#

循环导入发生在两个模块相互导入时,这是 Python 中常见的问题。您可以通过遵循在此 StackOverflow post 中概述的最佳实践来防止循环导入。

一般来说,避免循环导入是至关重要的,因为它们可能导致不可预测的行为。

然而,在我们的代码库中,我们在子包级别遇到了循环导入。这种情况是由于我们特定的代码结构所导致的。我们将类或函数及其相应的配置对象组织到不同的文件中。这种分离增强了代码的可读性和可维护性。然而,它可能导致循环导入,因为在许多配置对象中,我们使用属性 class_typefunc 分别指定类或函数作为默认值。

为了处理循环导入问题,我们利用 typing.TYPE_CHECKING 变量。该特殊变量仅在类型检查时评估,从而使我们能够在配置对象中导入类或函数,而不会触发循环导入。

需要注意的是,这是我们代码库中唯一使用并且可以接受循环导入的实例。在所有其他情况下,我们遵循最佳实践,并建议您也这样做。

类型提示#

为了提高代码的可读性,我们对所有的函数和类使用了 type hints 。这有助于理解代码,并使得维护更加容易。遵循这一做法还可以帮助我们通过像 mypy 这样的静态类型检查工具尽早发现 bug。

仅在函数签名中使用类型提示

为了避免重复工作,我们在文档字符串中不为参数和返回值指定类型提示。

例如,以下是由于各种原因导致的不良示例:

def my_function(a, b):
   """Adds two numbers.

   This function is a bad example. Reason: No type hints anywhere.

   Args:
      a: The first argument.
      b: The second argument.

   Returns:
      The sum of the two arguments.
   """
   return a + b
def my_function(a, b):
   """Adds two numbers.

   This function is a bad example. Reason: Type hints in the docstring and not in the
   function signature.

   Args:
      a (int): The first argument.
      b (int): The second argument.

   Returns:
      int: The sum of the two arguments.
   """
   return a + b
def my_function(a: int, b: int) -> int:
   """Adds two numbers.

   This function is a bad example. Reason: Type hints in the docstring and in the function
   signature. Redundancy.

   Args:
      a (int): The first argument.
      b (int): The second argument.

   Returns:
      int: The sum of the two arguments.
   """
   return a + b

以下是我们期望您编写文档字符串和类型提示的方式:

def my_function(a: int, b: int) -> int:
   """Adds two numbers.

   This function is a good example. Reason: Type hints in the function signature and not in the
   docstring.

   Args:
      a: The first argument.
      b: The second argument.

   Returns:
      The sum of the two arguments.
   """
   return a + b

不对 None 进行类型提示

我们在文档字符串中不指定 None 的返回类型。这是因为它不是必需的,并且可以从函数签名中推断出来。

例如,以下是一个不好的例子:

def my_function(x: int | None) -> None:
   pass

相反,我们推荐以下内容:

def my_function(x: int | None):
   pass

编写代码文档#

代码文档与代码本身同样重要。它有助于理解代码,并使维护变得更加容易。然而,更常见的情况是,文档往往是事后的思考,或者为了跟上开发进度而匆忙完成。

什么被认为是糟糕的文档?

  • 如果其他人想使用这段代码,他们仅通过阅读文档是无法理解代码的。

    这意味着文档不完整,或者没有以易于理解的方式编写。下次有人想使用代码时,他们将不得不花时间理解代码(在最佳情况下),或者丢弃代码并从头开始(在最坏情况下)。

  • 某些设计细节没有文档化,只能从代码中看出。

    通常会做出某些设计决策,以解决特定的使用场景。这些使用场景对想要使用代码的人来说并不明显。他们可能以一种不直观的方式修改代码,并无意中破坏代码。

  • 文档在代码更新时没有更新。

    这意味着文档没有随着代码的更新而保持最新。重要的是,在代码更新时更新文档。这有助于保持文档的最新状态,并与代码保持同步。

什么是被认为是好的文档?

我们建议将代码文档视为一个活文档,帮助读者理解代码的 what, why 和 how。我们经常看到的文档只解释了 what,而没有解释 how 或 why。这在长远来看并没有帮助。

我们建议始终从新用户的角度思考文档。他们应该能够直接查看文档并且对代码有一个清晰的理解。

有关如何编写优秀文档的信息,请查看 Dart’s effective documentationtechnical writing 上的说明。我们在下面总结了关键要点:

  • 告知(教育读者)并说服(说服读者)。 * 心中要有明确的目标,并确保你写的每一件事都只是为了这个目标。 * 在引入抽象概念之前,先使用示例和类比。

  • 使用适合受众的语气。

  • 组成简单的主动语态句子。

  • 避免不必要的术语和重复。使用简单英语。

  • 避免使用模糊的词语,如 ‘kind of’, ‘sort of’, ‘a bit’ 等。

  • 在句子的开头陈述重要信息。

  • 说出你真正想表达的意思。不要回避写下令人不舒服的真相。

单元测试#

我们使用 unittest 进行单元测试。好的测试不仅覆盖代码的基本功能,还要涵盖边界情况。它们应该能够捕捉回归,并确保代码按预期工作。请确保为您的更改添加测试。

工具#

我们使用以下工具来维护代码质量:

  • pre-commit: 在代码库上运行格式化工具和代码检查工具的列表。

  • black: 不妥协的代码格式化工具。

  • flake8: 一个 PyFlakes、pycodestyle 和 McCabe 复杂度检查器的封装器。

请检查 这里 以获取设置这些的说明。要在整个仓库中运行,请在终端中执行以下命令:

./isaaclab.sh --format  # or "./isaaclab.sh -f"