pow-cli 0.2.0rc2__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/CHANGELOG.md +141 -0
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/PKG-INFO +3 -5
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/pow_cli/cli/init.py +253 -47
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/pow_cli/cli/lint.py +3 -9
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/pow_cli/cli/main.py +2 -4
- pow_cli-0.3.0/pow_cli/cli/sim.py +187 -0
- pow_cli-0.3.0/pow_cli/common/jsonc.py +332 -0
- pow_cli-0.3.0/pow_cli/common/prompt.py +282 -0
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/pow_cli/core/asset_manager.py +7 -1
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/pow_cli/core/initializer.py +237 -103
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/pow_cli/core/linter.py +155 -27
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/pow_cli/core/models/pow_config.py +135 -6
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/pow_cli/core/models/system_config.py +26 -4
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/pow_cli/core/ros_manager.py +95 -32
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/pow_cli/core/runner.py +93 -51
- pow_cli-0.3.0/pow_cli/core/vscode_settings.py +294 -0
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/pow_cli/data/pow.template.toml +1 -1
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/pow_cli/data/system.template.toml +5 -0
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/pyproject.toml +2 -4
- pow_cli-0.3.0/tests/conftest.py +11 -0
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/tests/test_config.py +119 -7
- pow_cli-0.3.0/tests/test_init_cmd.py +410 -0
- pow_cli-0.3.0/tests/test_initializer.py +466 -0
- pow_cli-0.3.0/tests/test_linter.py +147 -0
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/tests/test_prompt.py +149 -1
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/tests/test_ros_manager.py +77 -3
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/tests/test_runner.py +143 -4
- pow_cli-0.3.0/tests/test_sim_cmd.py +233 -0
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/tests/test_verify_isaacsim.py +45 -18
- pow_cli-0.3.0/tests/test_vscode_settings.py +370 -0
- pow_cli-0.3.0/tests/test_xauthority.py +105 -0
- pow_cli-0.2.0rc2/pow_cli/cli/check.py +0 -25
- pow_cli-0.2.0rc2/pow_cli/cli/sim.py +0 -67
- pow_cli-0.2.0rc2/pow_cli/common/prompt.py +0 -136
- pow_cli-0.2.0rc2/tests/test_check_cmd.py +0 -47
- pow_cli-0.2.0rc2/tests/test_init_cmd.py +0 -117
- pow_cli-0.2.0rc2/tests/test_initializer.py +0 -83
- pow_cli-0.2.0rc2/tests/test_sim_cmd.py +0 -71
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/.gitignore +0 -0
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/LICENSE +0 -0
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/NOTICE +0 -0
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/README.md +0 -0
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/pow_cli/__init__.py +0 -0
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/pow_cli/cli/__init__.py +0 -0
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/pow_cli/cli/asset.py +0 -0
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/pow_cli/cli/python.py +0 -0
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/pow_cli/cli/ros.py +0 -0
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/pow_cli/cli/run.py +0 -0
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/pow_cli/common/__init__.py +0 -0
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/pow_cli/common/utils.py +0 -0
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/pow_cli/core/__init__.py +0 -0
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/pow_cli/core/models/asset_profile.py +0 -0
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/pow_cli/data/asset-registry/isaacsim_assets_5_1_0.toml +0 -0
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/pow_cli/data/asset-registry/omniverse_assets.toml +0 -0
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/pow_cli/data/example/asset-profile.example.toml +0 -0
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/pow_cli/data/gitignore.template +0 -0
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/pow_cli/docker/Dockerfile.simros_jazzy +0 -0
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/pow_cli/main.py +0 -0
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/scripts/check_assets_urls.py +0 -0
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/tests/test_ros_cmd.py +0 -0
- {pow_cli-0.2.0rc2 → pow_cli-0.3.0}/tests/test_version.py +0 -0
|
@@ -2,6 +2,147 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the `pow-cli` package will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## [0.3.0] - 2026-09-07
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **`pow lint` rule 4 — Isaac asset version mismatch.** Asset references pointing
|
|
14
|
+
at an `Assets/Isaac/<major>.<minor>` tree other than the one `[sim] version`
|
|
15
|
+
reads from are reported, and rewritten by `pow lint fix` (`version = "6.0.1"`
|
|
16
|
+
→ `Assets/Isaac/6.0`). NVIDIA keeps every release's asset tree online, so a
|
|
17
|
+
stage authored against 5.1 kept loading 5.1 assets after the project moved to
|
|
18
|
+
6.0.1 with nothing to show for it. The target version is derived from the
|
|
19
|
+
configured version rather than tabulated, applies to every reference form (S3
|
|
20
|
+
URLs, `pow-assets`/`user-home` aliases, relative paths), and is skipped when
|
|
21
|
+
`pow.toml` sets no version
|
|
22
|
+
|
|
23
|
+
- **Isaac Sim 6.0.1 support.** `pow init` step 4 now offers the installable
|
|
24
|
+
versions as an arrow-key picker — latest first, marking the latest release and
|
|
25
|
+
any already installed — or takes the version from the new `--sim-version`
|
|
26
|
+
flag, or from `[sim] version` in an existing `pow.toml` you chose to keep. The
|
|
27
|
+
picker falls back to a typed prompt when there is no terminal (piped stdin,
|
|
28
|
+
CI), so the command stays scriptable. Releases live in
|
|
29
|
+
`PowConfig.ISAACSIM_RELEASES`, which pins each version's download URL — 6.0.1
|
|
30
|
+
is served from `downloads.isaacsim.nvidia.com`, not the host used for 5.1.0 —
|
|
31
|
+
and the matching `IsaacSim-<version>` ROS workspace ref cloned during setup.
|
|
32
|
+
- `pow sim` / `pow sim check` default `-v` to the Isaac Sim version actually
|
|
33
|
+
installed under `.pow/isaacsim/` instead of a fixed constant.
|
|
34
|
+
- **`[sim] default_version` in `~/.pow/system.toml`** — pin the version `pow sim`
|
|
35
|
+
and `pow sim check` use when `-v` is omitted. Resolution order is `-v`, then
|
|
36
|
+
this key, then the newest version installed under `.pow/isaacsim/`; an empty
|
|
37
|
+
value means auto-detect. A pinned version that is not installed prints a
|
|
38
|
+
warning and falls back to the newest installed one. New `system.toml` files
|
|
39
|
+
are created with the key present and empty; existing files are left untouched
|
|
40
|
+
and read as unset.
|
|
41
|
+
- `pow sim --help` now lists the options bare `pow sim` accepts (`-v/--version`,
|
|
42
|
+
`--ros`, `--no-ros`), which previously showed up only under
|
|
43
|
+
`pow sim launch --help`.
|
|
44
|
+
- **`pow sim check`** — run the Isaac Sim compatibility check from the managed
|
|
45
|
+
installation (`.pow/isaacsim/<version>/isaac-sim.compatibility_check.sh`), with
|
|
46
|
+
`-v/--version` to pick the version. Needs no project and no extra pip
|
|
47
|
+
dependency; the script sets up its own ROS environment (forward
|
|
48
|
+
`-- --no-ros-env` to skip it). `pow sim` is now a command group whose default
|
|
49
|
+
subcommand is `launch` — bare `pow sim [args...]` still launches Isaac Sim and
|
|
50
|
+
forwards raw arguments exactly as before, and `pow sim launch` is the explicit
|
|
51
|
+
form
|
|
52
|
+
|
|
53
|
+
### Changed
|
|
54
|
+
|
|
55
|
+
- **`pow init` no longer replaces an existing `pow.toml`.** Choosing to update
|
|
56
|
+
the configuration used to overwrite the file with the bundled template, losing
|
|
57
|
+
custom `exts`, `raw_args`, `ros_bridge`, every `[[profiles]]` block and all
|
|
58
|
+
comments. It now patches only the three settings init asks about — `version`,
|
|
59
|
+
`enable_ros`, `isaacsim_ros_ws` — and leaves the rest of the document, key
|
|
60
|
+
order and comments included, exactly as written. Step 10 lists what changed
|
|
61
|
+
(`version: 5.1.0 → 6.0.1`), and says the file is already up to date when
|
|
62
|
+
nothing moved, in which case it is not rewritten at all. Keys the template has
|
|
63
|
+
and your file lacks are deliberately not back-filled. The step-2 prompt is
|
|
64
|
+
reworded from "override" to "Update settings in existing pow.toml?" to match.
|
|
65
|
+
- The `_isaacsim` symlink is re-pointed when `pow init` installs a different
|
|
66
|
+
version, instead of being left on the previous installation. A real directory
|
|
67
|
+
at `_isaacsim` is never deleted; it is reported as an error.
|
|
68
|
+
- `pow init` writes the selected `version` into the generated `pow.toml`.
|
|
69
|
+
- Isaac Sim's bundled ROS 2 libraries are located by probing
|
|
70
|
+
`exts/isaacsim.ros2.core/<distro>/lib` and then
|
|
71
|
+
`exts/isaacsim.ros2.bridge/<distro>/lib`, so ROS works on 6.0.1, which renamed
|
|
72
|
+
the extension.
|
|
73
|
+
|
|
74
|
+
### Security
|
|
75
|
+
|
|
76
|
+
- **ROS containers no longer disable X11 access control with `xhost +`.** When
|
|
77
|
+
`DISPLAY` is set, `pow ros` now copies only the current display's Xauthority
|
|
78
|
+
record into a private temporary file, mounts it read-only in the container,
|
|
79
|
+
and sets the container's `XAUTHORITY` to that file. The host's authority file
|
|
80
|
+
and X server access-control settings are left unchanged, and the temporary
|
|
81
|
+
credentials are removed when the Docker client exits. Headless launches skip
|
|
82
|
+
X11 setup; GUI launches fail with an actionable message when `xauth` or valid
|
|
83
|
+
display credentials are unavailable. Existing running containers must be
|
|
84
|
+
recreated to receive the new authentication mount.
|
|
85
|
+
- An Isaac Sim version can no longer escape `.pow/isaacsim/`: versions used as a
|
|
86
|
+
path component are validated, and download URLs are only ever read from the
|
|
87
|
+
release registry, never built from a supplied version string.
|
|
88
|
+
- Archive members are chmod-ed via the path `zipfile` actually wrote rather than
|
|
89
|
+
a recomputed one, so a crafted zip cannot change permissions outside the
|
|
90
|
+
install directory; setuid/setgid bits in the archive are dropped.
|
|
91
|
+
|
|
92
|
+
### Fixed
|
|
93
|
+
|
|
94
|
+
- A `pow.toml` that is not valid TOML is reported as
|
|
95
|
+
`pow.toml is not valid TOML: <parse error>` with a pointer to fix or delete it,
|
|
96
|
+
instead of a raw `tomllib` traceback. No command rewrites the file to make it
|
|
97
|
+
parse.
|
|
98
|
+
- An interrupted Isaac Sim download no longer leaves a truncated zip that the
|
|
99
|
+
next `pow init` mistakes for a complete archive: downloads land on a `.part`
|
|
100
|
+
file and are renamed only once finished.
|
|
101
|
+
- Execute permissions are restored before `post_install.sh` runs, so an archive
|
|
102
|
+
without Unix mode bits no longer fails the install.
|
|
103
|
+
- `pow asset` resolves the `.kit` file from the project's configured Isaac Sim
|
|
104
|
+
version instead of a hardcoded `5.1.0` path.
|
|
105
|
+
- **`pow sim check` could report nothing at all** — inside Isaac Sim's bundled
|
|
106
|
+
Python the check extension failed with `ModuleNotFoundError: No module named
|
|
107
|
+
'packaging'` (then `'setuptools'`) and never produced a verdict. The vendor
|
|
108
|
+
launcher `isaac-sim.compatibility_check.sh` never sources
|
|
109
|
+
`setup_python_env.sh`, so whether `packaging` was importable depended on kit's
|
|
110
|
+
extension load order. pow now puts the installation's own bundled module
|
|
111
|
+
directories on `PYTHONPATH` and no longer forwards the host `PYTHONPATH` (which
|
|
112
|
+
typically targets another Python version, the same reason the ROS bridge
|
|
113
|
+
environment strips it)
|
|
114
|
+
- **`pow sim check` exited 0 when the check produced no result** — Isaac Sim exits
|
|
115
|
+
0 even when the check app fails to start. The command now streams the output and
|
|
116
|
+
fails with a clear message if no `System checking result:` line appears; a
|
|
117
|
+
non-zero exit code from the check still takes priority
|
|
118
|
+
|
|
119
|
+
### Removed
|
|
120
|
+
|
|
121
|
+
- **`pow check`** — the standalone Isaac Sim compatibility check command has been
|
|
122
|
+
removed, replaced by `pow sim check` above. It ran the check through the pip
|
|
123
|
+
`isaacsim` entry point, so pow-cli no longer depends on the
|
|
124
|
+
`isaacsim[compatibility-check]` extra and `isaacsim-app` is no longer installed
|
|
125
|
+
as a transitive dependency
|
|
126
|
+
- The `packaging` and `setuptools` runtime dependencies, which existed only for
|
|
127
|
+
that pip-based check. `pow sim check` reads those modules from the Isaac Sim
|
|
128
|
+
installation instead
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## [0.2.0] - 2026-07-28
|
|
133
|
+
|
|
134
|
+
Stable release of the 0.2.0 line, promoted from `0.2.0-rc.2` with no functional
|
|
135
|
+
changes.
|
|
136
|
+
|
|
137
|
+
Everything new since `0.1.1` is listed in the two release candidate entries
|
|
138
|
+
below — headline items: the new `pow sim` and `pow ros build` commands, the
|
|
139
|
+
`ros_bridge` and `ros_docker_image` config keys, Jazzy-only ROS 2 support
|
|
140
|
+
(Humble removed), a faster `pow init` that no longer builds the ROS workspace,
|
|
141
|
+
and fixes for `rosdep install`, `cpu_performance_mode`, and `ros2` tab
|
|
142
|
+
completion inside the container.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
5
146
|
## [0.2.0-rc.2] - 2026-07-28
|
|
6
147
|
|
|
7
148
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: pow-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Command line interface for Isaac Powerpack
|
|
5
5
|
Author: Titiwat Munintravong, Isaac Powerpack Contributors
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -10,10 +10,8 @@ Requires-Python: >=3.10
|
|
|
10
10
|
Requires-Dist: aiohttp>=3.11.11
|
|
11
11
|
Requires-Dist: click>=8.1.7
|
|
12
12
|
Requires-Dist: distro>=1.9.0
|
|
13
|
-
Requires-Dist: isaacsim
|
|
14
|
-
Requires-Dist: packaging>=26.0
|
|
13
|
+
Requires-Dist: isaacsim>=5.1.0.0
|
|
15
14
|
Requires-Dist: rich
|
|
16
|
-
Requires-Dist: setuptools>=82.0.1
|
|
17
15
|
Requires-Dist: tomlkit>=0.14.0
|
|
18
16
|
Description-Content-Type: text/markdown
|
|
19
17
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Init command implementation."""
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
import time
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
|
|
@@ -8,9 +9,10 @@ from rich.panel import Panel
|
|
|
8
9
|
from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn
|
|
9
10
|
from rich.prompt import Confirm
|
|
10
11
|
|
|
11
|
-
from ..common.prompt import ask_path
|
|
12
|
+
from ..common.prompt import ask_choice, ask_path
|
|
12
13
|
from ..common.utils import console
|
|
13
14
|
from ..core.initializer import Initializer
|
|
15
|
+
from ..core.models.pow_config import PowConfig
|
|
14
16
|
from ..core.ros_manager import RosManager
|
|
15
17
|
|
|
16
18
|
|
|
@@ -87,11 +89,14 @@ def _step2_check_existing_config(initializer: Initializer) -> bool:
|
|
|
87
89
|
"[yellow]Found existing pow.toml[/yellow]"
|
|
88
90
|
)
|
|
89
91
|
override = Confirm.ask(
|
|
90
|
-
"
|
|
92
|
+
" Update settings in existing pow.toml?",
|
|
91
93
|
default=False,
|
|
92
94
|
)
|
|
93
95
|
if override:
|
|
94
|
-
console.print(
|
|
96
|
+
console.print(
|
|
97
|
+
" [green]Will update only version, enable_ros and isaacsim_ros_ws.[/green]"
|
|
98
|
+
)
|
|
99
|
+
console.print(" [dim]Your other settings and comments are kept.[/dim]")
|
|
95
100
|
else:
|
|
96
101
|
console.print(" [yellow]Proceeding with existing pow.toml.[/yellow]")
|
|
97
102
|
initializer.read_config()
|
|
@@ -126,9 +131,61 @@ def _step3_global_folder(initializer: Initializer, global_path):
|
|
|
126
131
|
)
|
|
127
132
|
|
|
128
133
|
|
|
129
|
-
def
|
|
134
|
+
def _version_choices() -> list[tuple[str, str]]:
|
|
135
|
+
"""Installable versions, latest first, annotated for the picker."""
|
|
136
|
+
installed = set(PowConfig.installed_versions())
|
|
137
|
+
latest = PowConfig.SUPPORTED_ISAACSIM_VERSIONS[0]
|
|
138
|
+
|
|
139
|
+
choices = []
|
|
140
|
+
for version in PowConfig.SUPPORTED_ISAACSIM_VERSIONS:
|
|
141
|
+
notes = []
|
|
142
|
+
if version == latest:
|
|
143
|
+
notes.append("latest")
|
|
144
|
+
if version in installed:
|
|
145
|
+
notes.append("installed")
|
|
146
|
+
choices.append((version, ", ".join(notes)))
|
|
147
|
+
return choices
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _resolve_sim_version(flag_version: str | None, config_version: str | None) -> str:
|
|
151
|
+
"""Decide which Isaac Sim version to install.
|
|
152
|
+
|
|
153
|
+
Precedence: ``--sim-version`` flag, then ``[sim] version`` from an existing
|
|
154
|
+
pow.toml the user chose to keep, then an interactive prompt.
|
|
155
|
+
"""
|
|
156
|
+
if flag_version:
|
|
157
|
+
console.print(
|
|
158
|
+
f" Using version from --sim-version: [bold green]{flag_version}[/bold green]"
|
|
159
|
+
)
|
|
160
|
+
return flag_version
|
|
161
|
+
|
|
162
|
+
if config_version:
|
|
163
|
+
if config_version not in PowConfig.SUPPORTED_ISAACSIM_VERSIONS:
|
|
164
|
+
raise click.ClickException(
|
|
165
|
+
f"Unsupported Isaac Sim version '{config_version}' in pow.toml. "
|
|
166
|
+
f"Supported versions: {', '.join(PowConfig.SUPPORTED_ISAACSIM_VERSIONS)}."
|
|
167
|
+
)
|
|
168
|
+
console.print(
|
|
169
|
+
f" Using version from pow.toml: [bold green]{config_version}[/bold green]"
|
|
170
|
+
)
|
|
171
|
+
return config_version
|
|
172
|
+
|
|
173
|
+
return ask_choice(
|
|
174
|
+
"Select Isaac Sim version",
|
|
175
|
+
_version_choices(),
|
|
176
|
+
default=PowConfig.ISAACSIM_VERSION,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _step4_download_isaacsim(
|
|
181
|
+
initializer: Initializer,
|
|
182
|
+
flag_version: str | None = None,
|
|
183
|
+
config_version: str | None = None,
|
|
184
|
+
) -> dict | None:
|
|
130
185
|
"""Download Isaac Sim with a Rich progress bar. Returns result dict or None on error."""
|
|
131
|
-
console.print("[bold blue][4/10] 📦 Isaac Sim App:[/bold blue]
|
|
186
|
+
console.print("[bold blue][4/10] 📦 Isaac Sim App:[/bold blue] Select a version to install")
|
|
187
|
+
version = _resolve_sim_version(flag_version, config_version)
|
|
188
|
+
console.print(f" Installing Isaac Sim [bold]{version}[/bold]...")
|
|
132
189
|
|
|
133
190
|
result = None
|
|
134
191
|
error = None
|
|
@@ -147,7 +204,7 @@ def _step4_download_isaacsim(initializer: Initializer) -> dict | None:
|
|
|
147
204
|
refresh_per_second=10,
|
|
148
205
|
) as progress:
|
|
149
206
|
download_task = progress.add_task(
|
|
150
|
-
"download", filename="isaac-sim-
|
|
207
|
+
"download", filename=f"isaac-sim-{version}.zip", total=None, speed="0 MB/s"
|
|
151
208
|
)
|
|
152
209
|
extract_task = progress.add_task(
|
|
153
210
|
"extract", filename="", total=None, speed=" ", visible=False
|
|
@@ -178,7 +235,7 @@ def _step4_download_isaacsim(initializer: Initializer) -> dict | None:
|
|
|
178
235
|
progress.update(download_task, visible=False)
|
|
179
236
|
progress.update(
|
|
180
237
|
extract_task,
|
|
181
|
-
filename="isaac-sim-
|
|
238
|
+
filename=f"isaac-sim-{version} [yellow](Extracting...)[/yellow]",
|
|
182
239
|
speed=" ",
|
|
183
240
|
completed=0,
|
|
184
241
|
total=None,
|
|
@@ -189,12 +246,13 @@ def _step4_download_isaacsim(initializer: Initializer) -> dict | None:
|
|
|
189
246
|
elif status == "Extracted":
|
|
190
247
|
progress.update(
|
|
191
248
|
extract_task,
|
|
192
|
-
filename="isaac-sim-
|
|
249
|
+
filename=f"isaac-sim-{version} [green](Extracted → {version})[/green]",
|
|
193
250
|
)
|
|
194
251
|
time.sleep(1)
|
|
195
252
|
|
|
196
253
|
try:
|
|
197
254
|
result = initializer.download_isaacsim(
|
|
255
|
+
version=version,
|
|
198
256
|
progress_callback=progress_callback,
|
|
199
257
|
status_callback=status_callback,
|
|
200
258
|
)
|
|
@@ -205,9 +263,10 @@ def _step4_download_isaacsim(initializer: Initializer) -> dict | None:
|
|
|
205
263
|
if error:
|
|
206
264
|
console.print(f" [bold red]❌ Error:[/bold red] {error}")
|
|
207
265
|
return None
|
|
266
|
+
result.setdefault("version", version)
|
|
208
267
|
if result["status"] == "Already installed":
|
|
209
268
|
console.print(
|
|
210
|
-
f" [yellow]✔[/yellow] Isaac Sim
|
|
269
|
+
f" [yellow]✔[/yellow] Isaac Sim {version} is already installed at [dim]{result['path']}[/dim]"
|
|
211
270
|
)
|
|
212
271
|
else:
|
|
213
272
|
console.print(
|
|
@@ -232,6 +291,7 @@ def _step6_ros_integration(
|
|
|
232
291
|
global_dir_name: str,
|
|
233
292
|
forced_value: bool | None = None,
|
|
234
293
|
forced_ws: str | None = None,
|
|
294
|
+
sim_version: str | None = None,
|
|
235
295
|
) -> tuple[bool, str]:
|
|
236
296
|
"""Prompt for ROS integration and set it up.
|
|
237
297
|
|
|
@@ -295,6 +355,7 @@ def _step6_ros_integration(
|
|
|
295
355
|
ros_res = ros_mgr.setup_ros_workspace(
|
|
296
356
|
status_callback=ros_status_callback,
|
|
297
357
|
ws_path=resolved_ws,
|
|
358
|
+
sim_version=sim_version,
|
|
298
359
|
)
|
|
299
360
|
except Exception as e:
|
|
300
361
|
ros_setup_failed = True
|
|
@@ -305,7 +366,10 @@ def _step6_ros_integration(
|
|
|
305
366
|
raise SystemExit(1)
|
|
306
367
|
|
|
307
368
|
if ros_cloned:
|
|
308
|
-
console.print(
|
|
369
|
+
console.print(
|
|
370
|
+
f" [green]✔[/green] Cloned IsaacSim-ros_workspaces "
|
|
371
|
+
f"([bold]{ros_res['ws_ref']}[/bold]) to [dim]{display_path}[/dim]"
|
|
372
|
+
)
|
|
309
373
|
else:
|
|
310
374
|
console.print(f" [yellow]✔[/yellow] IsaacSim-ros_workspaces already available in [dim]{display_path}[/dim]")
|
|
311
375
|
|
|
@@ -404,28 +468,95 @@ def _step7_project_structure(initializer: Initializer):
|
|
|
404
468
|
console.print(" [yellow]✔[/yellow] .gitignore already exists. [dim]Kept existing.[/dim]")
|
|
405
469
|
|
|
406
470
|
|
|
407
|
-
def _step8_project_link(initializer: Initializer):
|
|
408
|
-
"""Symlink managed Isaac Sim to local project.
|
|
471
|
+
def _step8_project_link(initializer: Initializer, sim_version: str | None = None) -> dict | None:
|
|
472
|
+
"""Symlink managed Isaac Sim to local project.
|
|
473
|
+
|
|
474
|
+
Returns the link result, or None when it could not be linked - everything
|
|
475
|
+
after this step describes the linked install, so the caller must stop.
|
|
476
|
+
"""
|
|
409
477
|
console.print("[bold blue][8/10] 🔗 Project Link:[/bold blue] Linking Isaac Sim to project...")
|
|
410
|
-
result = initializer.link_managed_isaacsim()
|
|
478
|
+
result = initializer.link_managed_isaacsim(version=sim_version)
|
|
411
479
|
if result["status"] == "Created":
|
|
412
480
|
console.print(f" [green]✔[/green] Created symlink: [dim]{result['path']}[/dim]")
|
|
481
|
+
elif result["status"] == "Repointed":
|
|
482
|
+
console.print(
|
|
483
|
+
f" [green]✔[/green] Re-pointed symlink [dim]{result['path']}[/dim] "
|
|
484
|
+
f"to Isaac Sim {sim_version} [dim](was {result['previous']})[/dim]"
|
|
485
|
+
)
|
|
413
486
|
elif result["status"] == "Existed":
|
|
414
487
|
console.print(f" [yellow]✔[/yellow] Symlink already exists: [dim]{result['path']}[/dim]")
|
|
415
488
|
elif result["status"] == "Error":
|
|
416
489
|
console.print(f" [bold red]❌ Error:[/bold red] {result['message']}")
|
|
490
|
+
return None
|
|
491
|
+
return result
|
|
417
492
|
|
|
418
493
|
|
|
419
|
-
|
|
420
|
-
|
|
494
|
+
#: VSCode config outcomes that are not a problem, mapped to how they are marked.
|
|
495
|
+
_VSCODE_OK_STATUSES = {
|
|
496
|
+
"Copied": "green",
|
|
497
|
+
"Copied and patched": "green",
|
|
498
|
+
"Created": "green",
|
|
499
|
+
"Updated": "green",
|
|
500
|
+
"Already up to date": "yellow",
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def _step9_vscode_setup(
|
|
505
|
+
initializer: Initializer,
|
|
506
|
+
version: str | None = None,
|
|
507
|
+
version_changed: bool = True,
|
|
508
|
+
):
|
|
509
|
+
"""Setup VSCode configuration for the project.
|
|
510
|
+
|
|
511
|
+
*version_changed* comes from step 8: the Isaac Sim extension paths are
|
|
512
|
+
rewritten only when the project moved to a different version.
|
|
513
|
+
"""
|
|
421
514
|
console.print("[bold blue][9/10] 💻 VSCode Config:[/bold blue] Setting up VSCode configs...")
|
|
422
|
-
result = initializer.setup_vscode_configs()
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
status_symbol = "[green]✔[/green]" if "patched" in res["status"] or res["status"] == "Copied" else "[yellow]⚠[/yellow]"
|
|
426
|
-
console.print(f" {status_symbol} {res['file']}: [dim]{res['status']}[/dim]")
|
|
427
|
-
else:
|
|
515
|
+
result = initializer.setup_vscode_configs(version=version, version_changed=version_changed)
|
|
516
|
+
|
|
517
|
+
if result["status"] != "Success":
|
|
428
518
|
console.print(f" [bold red]❌ Error:[/bold red] {result['message']}")
|
|
519
|
+
return
|
|
520
|
+
|
|
521
|
+
for res in result["results"]:
|
|
522
|
+
status = res["status"]
|
|
523
|
+
colour = _VSCODE_OK_STATUSES.get(status)
|
|
524
|
+
if status == "Error":
|
|
525
|
+
console.print(f" [bold red]❌[/bold red] {res['file']}: [dim]{res.get('message', '')}[/dim]")
|
|
526
|
+
console.print(" Fix it, or delete it and re-run [bold]pow init[/bold].")
|
|
527
|
+
continue
|
|
528
|
+
|
|
529
|
+
symbol = f"[{colour}]✔[/{colour}]" if colour else "[yellow]⚠[/yellow]"
|
|
530
|
+
note = " [dim](kept your other settings)[/dim]" if status == "Updated" else ""
|
|
531
|
+
console.print(f" {symbol} {res['file']}: [dim]{status}[/dim]{note}")
|
|
532
|
+
|
|
533
|
+
for key, (old, new) in (res.get("changed") or {}).items():
|
|
534
|
+
old_text = "[dim]unset[/dim]" if old is None else _json_value(old)
|
|
535
|
+
new_text = "[dim]removed[/dim]" if new is None else f"[bold]{_json_value(new)}[/bold]"
|
|
536
|
+
console.print(f" [dim]{key}:[/dim] {old_text} → {new_text}", highlight=False)
|
|
537
|
+
|
|
538
|
+
if res["file"] == "settings.json" and not version_changed:
|
|
539
|
+
console.print(
|
|
540
|
+
f" [dim]python.analysis.extraPaths: kept "
|
|
541
|
+
f"(Isaac Sim {result['version']} unchanged)[/dim]"
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
if res.get("warning"):
|
|
545
|
+
console.print(f" [yellow]⚠[/yellow] [dim]{res['warning']}[/dim]")
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
def _json_value(value) -> str:
|
|
549
|
+
"""Render a settings value the way settings.json spells it, shortening containers."""
|
|
550
|
+
if isinstance(value, (list, dict)) and len(value) > 3:
|
|
551
|
+
return f"[dim]{len(value)} entries[/dim]"
|
|
552
|
+
return json.dumps(value)
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
def _toml_value(value) -> str:
|
|
556
|
+
"""Render a setting the way pow.toml spells it, so diffs read like the file."""
|
|
557
|
+
if isinstance(value, bool):
|
|
558
|
+
return "true" if value else "false"
|
|
559
|
+
return str(value)
|
|
429
560
|
|
|
430
561
|
|
|
431
562
|
def _step10_finalize(
|
|
@@ -433,14 +564,25 @@ def _step10_finalize(
|
|
|
433
564
|
override_pow_toml: bool,
|
|
434
565
|
ros_enabled: bool,
|
|
435
566
|
isaacsim_ros_ws: str = "~/IsaacSim-ros_workspaces",
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
# Ensure user-home alias in Omniverse config
|
|
567
|
+
sim_version: str | None = None,
|
|
568
|
+
) -> bool:
|
|
569
|
+
"""Generate pow.toml configuration. Returns False when it could not be written."""
|
|
570
|
+
# Both calls return their outcome instead of printing, so the step header can
|
|
571
|
+
# be rendered afterwards and carry the right icon.
|
|
443
572
|
alias_result = initializer.setup_omniverse_user_home_alias()
|
|
573
|
+
result = initializer.create_pow_toml(
|
|
574
|
+
override=override_pow_toml,
|
|
575
|
+
enable_ros=ros_enabled,
|
|
576
|
+
isaacsim_ros_ws=isaacsim_ros_ws,
|
|
577
|
+
sim_version=sim_version,
|
|
578
|
+
)
|
|
579
|
+
failed = result["status"] == "Error"
|
|
580
|
+
|
|
581
|
+
icon = "❌" if failed else "✅"
|
|
582
|
+
console.print(
|
|
583
|
+
f"[bold blue][10/10] {icon} Finalizing:[/bold blue] Generating configuration..."
|
|
584
|
+
)
|
|
585
|
+
|
|
444
586
|
if alias_result["status"] == "created":
|
|
445
587
|
console.print(f" [green]✔[/green] Added user-home alias in [dim]{alias_result['path']}[/dim]")
|
|
446
588
|
elif alias_result["status"] == "updated":
|
|
@@ -448,25 +590,52 @@ def _step10_finalize(
|
|
|
448
590
|
else:
|
|
449
591
|
console.print(f" [yellow]✔[/yellow] user-home alias already set in [dim]{alias_result['path']}[/dim]")
|
|
450
592
|
|
|
451
|
-
# Create pow.toml
|
|
452
|
-
result = initializer.create_pow_toml(
|
|
453
|
-
override=override_pow_toml,
|
|
454
|
-
enable_ros=ros_enabled,
|
|
455
|
-
isaacsim_ros_ws=isaacsim_ros_ws,
|
|
456
|
-
)
|
|
457
593
|
if result["status"] == "Created":
|
|
458
594
|
console.print(" [green]✔[/green] Created pow.toml (from template)")
|
|
595
|
+
elif result["status"] == "Updated":
|
|
596
|
+
changed = result.get("changed") or {}
|
|
597
|
+
if not changed:
|
|
598
|
+
console.print(" [yellow]✔[/yellow] pow.toml already up to date")
|
|
599
|
+
else:
|
|
600
|
+
console.print(
|
|
601
|
+
" [green]✔[/green] Updated pow.toml "
|
|
602
|
+
"[dim](kept your other settings)[/dim]"
|
|
603
|
+
)
|
|
604
|
+
for key, (old, new) in changed.items():
|
|
605
|
+
old_text = "[dim]unset[/dim]" if old is None else _toml_value(old)
|
|
606
|
+
console.print(
|
|
607
|
+
f" [dim]{key}:[/dim] {old_text} → [bold]{_toml_value(new)}[/bold]",
|
|
608
|
+
highlight=False,
|
|
609
|
+
)
|
|
459
610
|
elif result["status"] == "Existed":
|
|
460
611
|
console.print(" [yellow]✔[/yellow] Kept existing pow.toml")
|
|
612
|
+
elif failed:
|
|
613
|
+
console.print(" [bold red]❌ Error:[/bold red] pow.toml could not be parsed")
|
|
614
|
+
console.print(f" [dim]{result['message']}[/dim]")
|
|
615
|
+
console.print(" Fix it, or delete it and re-run [bold]pow init[/bold].")
|
|
461
616
|
else:
|
|
462
617
|
console.print(" [yellow]⚠[/yellow] pow.toml template not found. [dim]Skipped.[/dim]")
|
|
463
618
|
|
|
619
|
+
return not failed
|
|
620
|
+
|
|
464
621
|
|
|
465
622
|
# ── Command entry point ───────────────────────────────────────────────────────
|
|
466
623
|
|
|
467
624
|
@click.command(name="init")
|
|
468
|
-
|
|
469
|
-
""
|
|
625
|
+
@click.option(
|
|
626
|
+
"--sim-version",
|
|
627
|
+
"sim_version",
|
|
628
|
+
type=click.Choice(PowConfig.SUPPORTED_ISAACSIM_VERSIONS),
|
|
629
|
+
default=None,
|
|
630
|
+
help="Isaac Sim version to install. Skips the interactive prompt.",
|
|
631
|
+
)
|
|
632
|
+
def init_cmd(sim_version: str | None):
|
|
633
|
+
"""Initialize Isaac Sim project.
|
|
634
|
+
|
|
635
|
+
\b
|
|
636
|
+
The Isaac Sim version comes from --sim-version, then from `[sim] version`
|
|
637
|
+
in an existing pow.toml you chose to keep, then from an interactive prompt.
|
|
638
|
+
"""
|
|
470
639
|
initializer = Initializer()
|
|
471
640
|
config = initializer.get_config_path()
|
|
472
641
|
global_dir_name = config["global_dir_name"]
|
|
@@ -485,30 +654,67 @@ def init_cmd():
|
|
|
485
654
|
override_pow_toml = _step2_check_existing_config(initializer)
|
|
486
655
|
_step3_global_folder(initializer, global_path)
|
|
487
656
|
|
|
488
|
-
|
|
489
|
-
if download_result is None:
|
|
490
|
-
return
|
|
491
|
-
|
|
492
|
-
_step5_optimization(initializer, download_result["path"])
|
|
493
|
-
|
|
494
|
-
# Use existing ROS setting if not overriding pow.toml
|
|
657
|
+
# Reuse settings from an existing pow.toml the user chose to keep.
|
|
495
658
|
ros_forced = None
|
|
496
659
|
ros_ws_forced = None
|
|
660
|
+
version_forced = None
|
|
497
661
|
if not override_pow_toml:
|
|
498
662
|
try:
|
|
499
663
|
ros_forced = initializer.config.get("enable_ros", False)
|
|
500
664
|
ros_ws_forced = initializer.config.get("isaacsim_ros_ws", None)
|
|
665
|
+
version_forced = initializer.config.get("version", None)
|
|
501
666
|
except Exception:
|
|
502
667
|
pass
|
|
503
668
|
|
|
669
|
+
download_result = _step4_download_isaacsim(
|
|
670
|
+
initializer, flag_version=sim_version, config_version=version_forced,
|
|
671
|
+
)
|
|
672
|
+
if download_result is None:
|
|
673
|
+
return
|
|
674
|
+
|
|
675
|
+
resolved_version = download_result["version"]
|
|
676
|
+
|
|
677
|
+
_step5_optimization(initializer, download_result["path"])
|
|
678
|
+
|
|
504
679
|
ros_enabled, isaacsim_ros_ws = _step6_ros_integration(
|
|
505
|
-
initializer, global_dir_name,
|
|
680
|
+
initializer, global_dir_name,
|
|
681
|
+
forced_value=ros_forced, forced_ws=ros_ws_forced,
|
|
682
|
+
sim_version=resolved_version,
|
|
506
683
|
)
|
|
507
684
|
_step7_project_structure(initializer)
|
|
508
|
-
_step8_project_link(initializer)
|
|
509
|
-
|
|
685
|
+
linked = _step8_project_link(initializer, sim_version=resolved_version)
|
|
686
|
+
if linked is None:
|
|
687
|
+
# Everything below describes the linked install: the .vscode configs are
|
|
688
|
+
# read from it and pow.toml records its version. Writing either against
|
|
689
|
+
# a link that is not there would leave the project inconsistent.
|
|
690
|
+
console.print(
|
|
691
|
+
Panel(
|
|
692
|
+
"[bold red]✘ Initialization incomplete: Isaac Sim could not be linked."
|
|
693
|
+
"[/bold red]",
|
|
694
|
+
border_style="red",
|
|
695
|
+
)
|
|
696
|
+
)
|
|
697
|
+
raise SystemExit(1)
|
|
510
698
|
|
|
511
|
-
|
|
699
|
+
_step9_vscode_setup(
|
|
700
|
+
initializer,
|
|
701
|
+
version=resolved_version,
|
|
702
|
+
version_changed=linked["status"] in ("Created", "Repointed"),
|
|
703
|
+
)
|
|
704
|
+
|
|
705
|
+
finalized = _step10_finalize(
|
|
706
|
+
initializer, override_pow_toml, ros_enabled, isaacsim_ros_ws,
|
|
707
|
+
sim_version=resolved_version,
|
|
708
|
+
)
|
|
709
|
+
|
|
710
|
+
if not finalized:
|
|
711
|
+
console.print(
|
|
712
|
+
Panel(
|
|
713
|
+
"[bold red]✘ Initialization incomplete: pow.toml was not updated.[/bold red]",
|
|
714
|
+
border_style="red",
|
|
715
|
+
)
|
|
716
|
+
)
|
|
717
|
+
raise SystemExit(1)
|
|
512
718
|
|
|
513
719
|
console.print(
|
|
514
720
|
Panel(
|
|
@@ -144,7 +144,7 @@ def fix_cmd(path, short):
|
|
|
144
144
|
if not issues:
|
|
145
145
|
continue
|
|
146
146
|
|
|
147
|
-
fix_file(usda_file, issues)
|
|
147
|
+
fix_file(usda_file, issues, alias_config)
|
|
148
148
|
files_fixed += 1
|
|
149
149
|
total_fixed += len(issues)
|
|
150
150
|
|
|
@@ -190,15 +190,9 @@ def _print_issue(issue, *, short: bool = False) -> None:
|
|
|
190
190
|
"""Print a single lint issue."""
|
|
191
191
|
rel_path = _short_path(issue.file)
|
|
192
192
|
if short:
|
|
193
|
-
if "simready_content" in issue.original:
|
|
194
|
-
problem = "relative path → use sim-ready staging S3 URL"
|
|
195
|
-
elif "Pow" in issue.original:
|
|
196
|
-
problem = "relative path → use pow-assets alias"
|
|
197
|
-
else:
|
|
198
|
-
problem = "relative path → use NVIDIA production S3 URL"
|
|
199
193
|
console.print(
|
|
200
194
|
f" [dim]{rel_path}[/dim]:[green]{issue.line}[/green]"
|
|
201
|
-
f" [red]●[/red] {
|
|
195
|
+
f" [red]●[/red] {escape(issue.label)}",
|
|
202
196
|
soft_wrap=True,
|
|
203
197
|
)
|
|
204
198
|
else:
|
|
@@ -223,7 +217,7 @@ def _print_fix(issue, *, short: bool = False) -> None:
|
|
|
223
217
|
if short:
|
|
224
218
|
console.print(
|
|
225
219
|
f" [dim]{rel_path}[/dim]:[green]{issue.line}[/green]"
|
|
226
|
-
f" [green]✔[/green] fixed
|
|
220
|
+
f" [green]✔[/green] fixed: {escape(issue.label)}",
|
|
227
221
|
soft_wrap=True,
|
|
228
222
|
)
|
|
229
223
|
else:
|
|
@@ -4,10 +4,9 @@ import click
|
|
|
4
4
|
|
|
5
5
|
from .init import init_cmd
|
|
6
6
|
# from .add import add_group
|
|
7
|
-
from .check import check_cmd
|
|
8
7
|
from .lint import lint_group
|
|
9
8
|
from .run import run_cmd
|
|
10
|
-
from .sim import
|
|
9
|
+
from .sim import sim_group
|
|
11
10
|
from .ros import ros_group
|
|
12
11
|
from .python import python_cmd
|
|
13
12
|
from .asset import asset_group
|
|
@@ -30,10 +29,9 @@ def pow_group():
|
|
|
30
29
|
|
|
31
30
|
# Register commands
|
|
32
31
|
pow_group.add_command(init_cmd)
|
|
33
|
-
pow_group.add_command(check_cmd)
|
|
34
32
|
pow_group.add_command(lint_group)
|
|
35
33
|
pow_group.add_command(run_cmd)
|
|
36
|
-
pow_group.add_command(
|
|
34
|
+
pow_group.add_command(sim_group)
|
|
37
35
|
pow_group.add_command(ros_group)
|
|
38
36
|
pow_group.add_command(python_cmd)
|
|
39
37
|
pow_group.add_command(asset_group)
|