pow-cli 0.3.0__tar.gz → 0.4.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.3.0 → pow_cli-0.4.0}/CHANGELOG.md +72 -1
- {pow_cli-0.3.0 → pow_cli-0.4.0}/PKG-INFO +2 -2
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/cli/init.py +51 -99
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/cli/ros.py +3 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/cli/sim.py +50 -20
- pow_cli-0.4.0/pow_cli/common/installation.py +97 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/core/asset_manager.py +2 -1
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/core/initializer.py +117 -52
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/core/linter.py +5 -11
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/core/models/pow_config.py +65 -20
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/core/ros_manager.py +135 -9
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/core/runner.py +21 -7
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/core/vscode_settings.py +2 -2
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/data/pow.template.toml +1 -1
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/docker/Dockerfile.simros_jazzy +9 -4
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pyproject.toml +2 -2
- pow_cli-0.4.0/tests/test_bump.py +30 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/tests/test_config.py +65 -13
- {pow_cli-0.3.0 → pow_cli-0.4.0}/tests/test_init_cmd.py +83 -11
- {pow_cli-0.3.0 → pow_cli-0.4.0}/tests/test_initializer.py +56 -99
- pow_cli-0.4.0/tests/test_isaacsim_6_1_0.py +188 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/tests/test_linter.py +26 -12
- {pow_cli-0.3.0 → pow_cli-0.4.0}/tests/test_prompt.py +14 -14
- {pow_cli-0.3.0 → pow_cli-0.4.0}/tests/test_ros_cmd.py +8 -0
- pow_cli-0.4.0/tests/test_ros_image_recovery.py +135 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/tests/test_ros_manager.py +5 -5
- {pow_cli-0.3.0 → pow_cli-0.4.0}/tests/test_runner.py +37 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/tests/test_sim_cmd.py +24 -17
- pow_cli-0.4.0/tests/test_sim_setup.py +238 -0
- pow_cli-0.4.0/tests/test_verify_isaacsim.py +156 -0
- pow_cli-0.3.0/tests/test_verify_isaacsim.py +0 -109
- {pow_cli-0.3.0 → pow_cli-0.4.0}/.gitignore +0 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/LICENSE +0 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/NOTICE +0 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/README.md +0 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/__init__.py +0 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/cli/__init__.py +0 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/cli/asset.py +0 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/cli/lint.py +0 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/cli/main.py +0 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/cli/python.py +0 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/cli/run.py +0 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/common/__init__.py +0 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/common/jsonc.py +0 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/common/prompt.py +0 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/common/utils.py +0 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/core/__init__.py +0 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/core/models/asset_profile.py +0 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/core/models/system_config.py +0 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/data/asset-registry/isaacsim_assets_5_1_0.toml +0 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/data/asset-registry/omniverse_assets.toml +0 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/data/example/asset-profile.example.toml +0 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/data/gitignore.template +0 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/data/system.template.toml +0 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/pow_cli/main.py +0 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/scripts/check_assets_urls.py +0 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/tests/conftest.py +0 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/tests/test_version.py +0 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/tests/test_vscode_settings.py +0 -0
- {pow_cli-0.3.0 → pow_cli-0.4.0}/tests/test_xauthority.py +0 -0
|
@@ -2,7 +2,78 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the `pow-cli` package will be documented in this file.
|
|
4
4
|
|
|
5
|
-
## [
|
|
5
|
+
## [0.4.0] - 2026-09-14
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Isaac Sim 6.1.0 support.** `pow init` now offers 6.1.0, downloads its standalone
|
|
10
|
+
archive and selects the matching ROS workspace tag. Asset references use
|
|
11
|
+
`Assets/Isaac/6.1`. Isaac Sim 5.1.0 remains supported.
|
|
12
|
+
- **Linux aarch64 support.** `pow init` and `pow sim` download the
|
|
13
|
+
aarch64 build of 6.1.0 or 5.1.0 on ARM hosts such as DGX Spark, and the simulator launch and
|
|
14
|
+
compatibility-check commands accept that architecture. The bundled ROS Docker image remains
|
|
15
|
+
amd64-only: `pow init` skips its build on aarch64, and `pow ros` / `pow ros build`
|
|
16
|
+
report that limitation.
|
|
17
|
+
- **`pow sim` / `pow sim check` install missing prerequisites.** Both commands
|
|
18
|
+
now create missing global folders and `system.toml`, install the selected
|
|
19
|
+
supported release when absent, and prepare the asset-browser cache before
|
|
20
|
+
launching. No project or confirmation prompt is required. Incomplete installs
|
|
21
|
+
are backed up before repair; a failed repair restores the original contents.
|
|
22
|
+
- **ROS image rebuild prompt in `pow init`.** An existing `pow_simros_jazzy`
|
|
23
|
+
image without a matching simulator-version label used to stop initialization
|
|
24
|
+
with no rebuild option. Init now asks whether to rebuild it (default: No).
|
|
25
|
+
Accepting updates the existing tag using Docker's cache and preserves existing
|
|
26
|
+
containers and workspace files. Recreate containers explicitly to use the new image.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- `pow init` defaults new projects to `6.1.0` unless a global preference is set.
|
|
31
|
+
Explicit version selection takes precedence; keeping an existing project
|
|
32
|
+
configuration preserves its selected version.
|
|
33
|
+
- A missing version pinned in global `system.toml` is now installed by `pow sim`
|
|
34
|
+
and `pow sim check`, rather than falling back to another installed version.
|
|
35
|
+
- **ROS workspace and container compatibility checks.** Setup and launching now
|
|
36
|
+
check the selected 6.1.0 workspace, image label and running-container mount.
|
|
37
|
+
Unknown or incompatible state is reported with recovery guidance. Stale
|
|
38
|
+
container-mounted build directories are no longer deleted automatically.
|
|
39
|
+
- `pow lint` asset-version fixes use verified release mappings. Unknown versions are
|
|
40
|
+
not rewritten; changes to scenes still require `pow lint fix` and do not migrate
|
|
41
|
+
renamed assets or application APIs.
|
|
42
|
+
- The optional simulator lookup reads matching distribution metadata without
|
|
43
|
+
importing `isaacsim`, avoiding simulator startup during path discovery.
|
|
44
|
+
- Python 3.10 installs `tomli` for the existing `tomllib` fallback.
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- **`pow init` could not upgrade an existing project's simulator version.**
|
|
49
|
+
Choosing to update the project now opens the version picker instead of keeping
|
|
50
|
+
the old version. Explicit `--sim-version` changes are saved with the
|
|
51
|
+
corresponding simulator link and editor paths while preserving unrelated TOML
|
|
52
|
+
settings, profiles, and comments.
|
|
53
|
+
- **`pow sim check` accepted unsuccessful verdicts.** A `System checking result:`
|
|
54
|
+
line now succeeds only when its verdict is `PASSED`; failed or unrecognized
|
|
55
|
+
verdicts are errors, alongside missing results and nonzero process exits.
|
|
56
|
+
- **Docker inspection errors were treated as missing or incompatible images.**
|
|
57
|
+
Daemon and permission failures now report the underlying error without offering
|
|
58
|
+
a rebuild. Failed ROS builds stop initialization before custom-image building
|
|
59
|
+
and finalization.
|
|
60
|
+
- **`setup.py install is deprecated` warnings during colcon builds** — suppressed
|
|
61
|
+
in both automatic and manual builds inside the bundled ROS container
|
|
62
|
+
(requires rebuilding the image).
|
|
63
|
+
- `pow asset set` now suggests `pow asset unset` when the assets symlink already
|
|
64
|
+
exists, instead of telling the user to remove it manually.
|
|
65
|
+
|
|
66
|
+
### Removed
|
|
67
|
+
|
|
68
|
+
- **Isaac Sim 6.0.1 installation support.** Projects pinned to it must select
|
|
69
|
+
6.1.0 or 5.1.0 for initialization; an existing complete 6.0.1 installation can
|
|
70
|
+
still launch through `pow sim -v 6.0.1`.
|
|
71
|
+
- The mandatory `isaacsim` runtime dependency — installing and starting pow-cli
|
|
72
|
+
no longer requires a simulator package.
|
|
73
|
+
|
|
74
|
+
Automated coverage uses mocked simulator, GPU, network, and Docker operations.
|
|
75
|
+
Actual 6.1.0 checker output, GUI/headless launches, standalone Python, ROS topic
|
|
76
|
+
delivery, and DGX Spark workflows still require hardware validation.
|
|
6
77
|
|
|
7
78
|
---
|
|
8
79
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pow-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.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,8 +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>=5.1.0.0
|
|
14
13
|
Requires-Dist: rich
|
|
14
|
+
Requires-Dist: tomli>=2; python_version < '3.11'
|
|
15
15
|
Requires-Dist: tomlkit>=0.14.0
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
|
|
@@ -6,9 +6,10 @@ from pathlib import Path
|
|
|
6
6
|
|
|
7
7
|
import click
|
|
8
8
|
from rich.panel import Panel
|
|
9
|
-
from rich.progress import
|
|
9
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
10
10
|
from rich.prompt import Confirm
|
|
11
11
|
|
|
12
|
+
from ..common.installation import download_with_progress
|
|
12
13
|
from ..common.prompt import ask_choice, ask_path
|
|
13
14
|
from ..common.utils import console
|
|
14
15
|
from ..core.initializer import Initializer
|
|
@@ -132,12 +133,13 @@ def _step3_global_folder(initializer: Initializer, global_path):
|
|
|
132
133
|
|
|
133
134
|
|
|
134
135
|
def _version_choices() -> list[tuple[str, str]]:
|
|
135
|
-
"""
|
|
136
|
+
"""Versions installable on this host's architecture, latest first, annotated for the picker."""
|
|
136
137
|
installed = set(PowConfig.installed_versions())
|
|
137
|
-
|
|
138
|
+
versions = PowConfig.versions_for_arch()
|
|
139
|
+
latest = versions[0] if versions else None
|
|
138
140
|
|
|
139
141
|
choices = []
|
|
140
|
-
for version in
|
|
142
|
+
for version in versions:
|
|
141
143
|
notes = []
|
|
142
144
|
if version == latest:
|
|
143
145
|
notes.append("latest")
|
|
@@ -170,10 +172,15 @@ def _resolve_sim_version(flag_version: str | None, config_version: str | None) -
|
|
|
170
172
|
)
|
|
171
173
|
return config_version
|
|
172
174
|
|
|
175
|
+
available = PowConfig.versions_for_arch()
|
|
176
|
+
default = PowConfig.configured_default_version() or (
|
|
177
|
+
available[0] if available else PowConfig.ISAACSIM_VERSION
|
|
178
|
+
)
|
|
179
|
+
PowConfig.release(default)
|
|
173
180
|
return ask_choice(
|
|
174
181
|
"Select Isaac Sim version",
|
|
175
182
|
_version_choices(),
|
|
176
|
-
default=
|
|
183
|
+
default=default,
|
|
177
184
|
)
|
|
178
185
|
|
|
179
186
|
|
|
@@ -182,97 +189,14 @@ def _step4_download_isaacsim(
|
|
|
182
189
|
flag_version: str | None = None,
|
|
183
190
|
config_version: str | None = None,
|
|
184
191
|
) -> dict | None:
|
|
185
|
-
"""
|
|
192
|
+
"""Select and install Isaac Sim. Return None on installation failure."""
|
|
186
193
|
console.print("[bold blue][4/10] 📦 Isaac Sim App:[/bold blue] Select a version to install")
|
|
187
194
|
version = _resolve_sim_version(flag_version, config_version)
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
error = None
|
|
192
|
-
|
|
193
|
-
last_completed = 0
|
|
194
|
-
last_time = time.time()
|
|
195
|
-
current_phase = "download"
|
|
196
|
-
|
|
197
|
-
with Progress(
|
|
198
|
-
TextColumn("[bold blue]{task.fields[filename]}", justify="right"),
|
|
199
|
-
BarColumn(bar_width=40),
|
|
200
|
-
"[progress.percentage]{task.percentage:>3.1f}%",
|
|
201
|
-
"•",
|
|
202
|
-
TextColumn("[bold cyan]{task.fields[speed]}"),
|
|
203
|
-
console=console,
|
|
204
|
-
refresh_per_second=10,
|
|
205
|
-
) as progress:
|
|
206
|
-
download_task = progress.add_task(
|
|
207
|
-
"download", filename=f"isaac-sim-{version}.zip", total=None, speed="0 MB/s"
|
|
208
|
-
)
|
|
209
|
-
extract_task = progress.add_task(
|
|
210
|
-
"extract", filename="", total=None, speed=" ", visible=False
|
|
211
|
-
)
|
|
212
|
-
|
|
213
|
-
def progress_callback(completed, total):
|
|
214
|
-
nonlocal last_completed, last_time, current_phase
|
|
215
|
-
if not total:
|
|
216
|
-
return
|
|
217
|
-
now = time.time()
|
|
218
|
-
if current_phase == "download":
|
|
219
|
-
progress.update(download_task, total=total, completed=completed)
|
|
220
|
-
elapsed = now - last_time
|
|
221
|
-
if elapsed >= 0.5:
|
|
222
|
-
speed_mb = ((completed - last_completed) / (1024 * 1024)) / elapsed
|
|
223
|
-
progress.update(download_task, speed=f"{speed_mb:.1f} MB/s")
|
|
224
|
-
last_completed = completed
|
|
225
|
-
last_time = now
|
|
226
|
-
elif current_phase == "extract":
|
|
227
|
-
progress.update(extract_task, total=total, completed=completed)
|
|
228
|
-
|
|
229
|
-
def status_callback(status):
|
|
230
|
-
nonlocal current_phase, last_completed, last_time
|
|
231
|
-
if status == "Downloading":
|
|
232
|
-
current_phase = "download"
|
|
233
|
-
elif status == "Extracting":
|
|
234
|
-
current_phase = "extract"
|
|
235
|
-
progress.update(download_task, visible=False)
|
|
236
|
-
progress.update(
|
|
237
|
-
extract_task,
|
|
238
|
-
filename=f"isaac-sim-{version} [yellow](Extracting...)[/yellow]",
|
|
239
|
-
speed=" ",
|
|
240
|
-
completed=0,
|
|
241
|
-
total=None,
|
|
242
|
-
visible=True,
|
|
243
|
-
)
|
|
244
|
-
last_completed = 0
|
|
245
|
-
last_time = time.time()
|
|
246
|
-
elif status == "Extracted":
|
|
247
|
-
progress.update(
|
|
248
|
-
extract_task,
|
|
249
|
-
filename=f"isaac-sim-{version} [green](Extracted → {version})[/green]",
|
|
250
|
-
)
|
|
251
|
-
time.sleep(1)
|
|
252
|
-
|
|
253
|
-
try:
|
|
254
|
-
result = initializer.download_isaacsim(
|
|
255
|
-
version=version,
|
|
256
|
-
progress_callback=progress_callback,
|
|
257
|
-
status_callback=status_callback,
|
|
258
|
-
)
|
|
259
|
-
except Exception as e:
|
|
260
|
-
error = e
|
|
261
|
-
|
|
262
|
-
# Print outside the progress context so lines don't get consumed
|
|
263
|
-
if error:
|
|
195
|
+
try:
|
|
196
|
+
return download_with_progress(initializer, version)
|
|
197
|
+
except Exception as error:
|
|
264
198
|
console.print(f" [bold red]❌ Error:[/bold red] {error}")
|
|
265
199
|
return None
|
|
266
|
-
result.setdefault("version", version)
|
|
267
|
-
if result["status"] == "Already installed":
|
|
268
|
-
console.print(
|
|
269
|
-
f" [yellow]✔[/yellow] Isaac Sim {version} is already installed at [dim]{result['path']}[/dim]"
|
|
270
|
-
)
|
|
271
|
-
else:
|
|
272
|
-
console.print(
|
|
273
|
-
f" [green]✔[/green] Downloaded and extracted to [dim]{result['path']}[/dim]"
|
|
274
|
-
)
|
|
275
|
-
return result
|
|
276
200
|
|
|
277
201
|
|
|
278
202
|
def _step5_optimization(initializer: Initializer, isaacsim_path: str):
|
|
@@ -380,6 +304,13 @@ def _step6_ros_integration(
|
|
|
380
304
|
f"via Isaac Sim internal libs."
|
|
381
305
|
)
|
|
382
306
|
|
|
307
|
+
if not RosManager.docker_image_supported():
|
|
308
|
+
console.print(
|
|
309
|
+
f" [yellow]⊖[/yellow] Skipping ROS Docker image build: "
|
|
310
|
+
f"{RosManager.docker_image_unsupported_message()}"
|
|
311
|
+
)
|
|
312
|
+
return True, display_path
|
|
313
|
+
|
|
383
314
|
# Build pow_simros Docker image
|
|
384
315
|
simros_already_built = False
|
|
385
316
|
|
|
@@ -394,11 +325,26 @@ def _step6_ros_integration(
|
|
|
394
325
|
line = state[len("simros_building:"):]
|
|
395
326
|
simros_status.update(f"[bold green]pow_simros build:[/bold green] [dim]{line[:80]}[/dim]")
|
|
396
327
|
|
|
328
|
+
def confirm_simros_rebuild(image, version):
|
|
329
|
+
simros_status.stop()
|
|
330
|
+
try:
|
|
331
|
+
console.print(
|
|
332
|
+
f" ROS image '{image}' is unlabelled or targets another simulator. "
|
|
333
|
+
"Rebuilding updates its tag using Docker's cache. Existing containers "
|
|
334
|
+
"and workspace files are preserved; recreate containers explicitly "
|
|
335
|
+
"to use the rebuilt image."
|
|
336
|
+
)
|
|
337
|
+
return Confirm.ask(f" Rebuild '{image}' for Isaac Sim {version}?", default=False)
|
|
338
|
+
finally:
|
|
339
|
+
simros_status.start()
|
|
340
|
+
|
|
397
341
|
with console.status("Building pow_simros image...") as simros_status:
|
|
398
342
|
try:
|
|
399
343
|
ros_mgr.build_simros_image(
|
|
400
344
|
status_callback=simros_status_callback,
|
|
401
345
|
ws_path=resolved_ws,
|
|
346
|
+
sim_version=sim_version,
|
|
347
|
+
confirm_rebuild=confirm_simros_rebuild,
|
|
402
348
|
)
|
|
403
349
|
except Exception as e:
|
|
404
350
|
console.print(f" [bold red]❌ pow_simros Build Error:[/bold red] {e}")
|
|
@@ -425,7 +371,10 @@ def _step6_ros_integration(
|
|
|
425
371
|
|
|
426
372
|
with console.status(f"Building custom image '{custom_image}'...") as custom_status:
|
|
427
373
|
try:
|
|
428
|
-
ros_mgr.build_custom_ros_image(
|
|
374
|
+
ros_mgr.build_custom_ros_image(
|
|
375
|
+
status_callback=custom_status_callback,
|
|
376
|
+
sim_version=sim_version, ws_path=resolved_ws,
|
|
377
|
+
)
|
|
429
378
|
except Exception as e:
|
|
430
379
|
console.print(f" [bold red]❌ Custom ROS Build Error:[/bold red] {e}")
|
|
431
380
|
console.print(" [yellow]⊖[/yellow] Skipping remaining steps due to build error.")
|
|
@@ -634,7 +583,8 @@ def init_cmd(sim_version: str | None):
|
|
|
634
583
|
|
|
635
584
|
\b
|
|
636
585
|
The Isaac Sim version comes from --sim-version, then from `[sim] version`
|
|
637
|
-
in an existing pow.toml you
|
|
586
|
+
in an existing pow.toml you choose to keep, then from an interactive prompt
|
|
587
|
+
(global default or latest).
|
|
638
588
|
"""
|
|
639
589
|
initializer = Initializer()
|
|
640
590
|
config = initializer.get_config_path()
|
|
@@ -658,11 +608,13 @@ def init_cmd(sim_version: str | None):
|
|
|
658
608
|
ros_forced = None
|
|
659
609
|
ros_ws_forced = None
|
|
660
610
|
version_forced = None
|
|
661
|
-
if
|
|
611
|
+
if Path("pow.toml").exists():
|
|
662
612
|
try:
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
613
|
+
if not override_pow_toml or sim_version is not None:
|
|
614
|
+
ros_forced = initializer.config.get("enable_ros", False)
|
|
615
|
+
ros_ws_forced = initializer.config.get("isaacsim_ros_ws", None)
|
|
616
|
+
if not override_pow_toml:
|
|
617
|
+
version_forced = initializer.config.get("version", None)
|
|
666
618
|
except Exception:
|
|
667
619
|
pass
|
|
668
620
|
|
|
@@ -703,7 +655,7 @@ def init_cmd(sim_version: str | None):
|
|
|
703
655
|
)
|
|
704
656
|
|
|
705
657
|
finalized = _step10_finalize(
|
|
706
|
-
initializer, override_pow_toml, ros_enabled, isaacsim_ros_ws,
|
|
658
|
+
initializer, override_pow_toml or sim_version is not None, ros_enabled, isaacsim_ros_ws,
|
|
707
659
|
sim_version=resolved_version,
|
|
708
660
|
)
|
|
709
661
|
|
|
@@ -83,6 +83,9 @@ def build_cmd(no_cache: bool):
|
|
|
83
83
|
if config.project_root is None:
|
|
84
84
|
raise click.ClickException("Not initialized. Run `pow init` first.")
|
|
85
85
|
|
|
86
|
+
if not RosManager.docker_image_supported():
|
|
87
|
+
raise click.ClickException(RosManager.docker_image_unsupported_message())
|
|
88
|
+
|
|
86
89
|
if not config.ros_dockerfile:
|
|
87
90
|
console.print(
|
|
88
91
|
"[yellow]No `ros_dockerfile` set in pow.toml; nothing to build.[/yellow]\n"
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import click
|
|
4
4
|
from rich.panel import Panel
|
|
5
|
+
from ..common.installation import download_with_progress
|
|
5
6
|
from ..common.utils import console
|
|
7
|
+
from ..core.initializer import Initializer
|
|
6
8
|
from ..core.models.pow_config import PowConfig
|
|
7
9
|
from ..core.runner import Runner
|
|
8
10
|
|
|
@@ -15,26 +17,49 @@ def _default_sim_version() -> str:
|
|
|
15
17
|
evaluates a callable default at parse time, so both the file read and the
|
|
16
18
|
scan of ``<global>/isaacsim`` happen per invocation rather than at import.
|
|
17
19
|
"""
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
return PowConfig.configured_default_version() or PowConfig.resolve_installed_version()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _ensure_sim_ready(version: str, *, check: bool = False) -> None:
|
|
24
|
+
"""Complete init's global setup steps without reading project settings."""
|
|
25
|
+
global_path = PowConfig.resolve_global_path()
|
|
26
|
+
isaacsim_path = PowConfig.version_dir(version, global_path)
|
|
27
|
+
initializer = Initializer(global_path=global_path)
|
|
28
|
+
installed = initializer.isaacsim_ready(isaacsim_path, check=check)
|
|
29
|
+
if not installed:
|
|
30
|
+
# Reject unavailable versions before making any setup changes. Existing
|
|
31
|
+
# manually installed versions need not be in the download allowlist.
|
|
32
|
+
PowConfig.release(version)
|
|
33
|
+
|
|
34
|
+
global_ready = all(
|
|
35
|
+
(global_path / sub).is_dir() for sub in Initializer.GLOBAL_SUBFOLDERS
|
|
36
|
+
) and (global_path / "system.toml").is_file()
|
|
37
|
+
if not global_ready:
|
|
27
38
|
console.print(
|
|
28
|
-
f"[
|
|
29
|
-
f"not installed; using {fallback}."
|
|
39
|
+
f"[blue]Preparing global configuration at [dim]{global_path}[/dim]...[/blue]"
|
|
30
40
|
)
|
|
31
|
-
|
|
32
|
-
|
|
41
|
+
initializer.create_global_folder()
|
|
42
|
+
initializer.create_system_toml()
|
|
43
|
+
|
|
44
|
+
if not installed:
|
|
45
|
+
download_with_progress(initializer, version, check=check)
|
|
46
|
+
if not initializer.isaacsim_ready(isaacsim_path, check=check):
|
|
47
|
+
raise click.ClickException(
|
|
48
|
+
f"Isaac Sim installation at {isaacsim_path} is missing required scripts."
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
cache_path = initializer.asset_browser_cache_path(isaacsim_path)
|
|
52
|
+
if not cache_path.is_file():
|
|
53
|
+
console.print("[blue]Creating the missing Isaac Sim asset browser cache...[/blue]")
|
|
54
|
+
initializer.fix_asset_browser_cache(isaacsim_path)
|
|
55
|
+
if not cache_path.is_file():
|
|
56
|
+
raise click.ClickException(f"Isaac Sim asset browser cache is missing at {cache_path}.")
|
|
33
57
|
|
|
34
58
|
|
|
35
|
-
def _guarded(action, **kwargs):
|
|
36
|
-
"""
|
|
59
|
+
def _guarded(action, *, check: bool = False, **kwargs):
|
|
60
|
+
"""Ensure prerequisites, then run the action with consistent error output."""
|
|
37
61
|
try:
|
|
62
|
+
_ensure_sim_ready(kwargs["version"], check=check)
|
|
38
63
|
action(**kwargs)
|
|
39
64
|
except click.ClickException:
|
|
40
65
|
raise
|
|
@@ -102,6 +127,11 @@ def sim_group(ctx: click.Context):
|
|
|
102
127
|
1. the -v/--version option
|
|
103
128
|
2. [sim] default_version in .pow/system.toml
|
|
104
129
|
3. the newest version installed under .pow/isaacsim/
|
|
130
|
+
4. the latest release supported by this pow CLI
|
|
131
|
+
|
|
132
|
+
Missing global folders, configuration, supported installations and the
|
|
133
|
+
asset browser cache are set up automatically before launch or check.
|
|
134
|
+
A missing pinned version is installed. Setup failures stop the command.
|
|
105
135
|
|
|
106
136
|
\b
|
|
107
137
|
Subcommands:
|
|
@@ -134,7 +164,7 @@ def sim_group(ctx: click.Context):
|
|
|
134
164
|
"--version",
|
|
135
165
|
"sim_version",
|
|
136
166
|
default=_default_sim_version,
|
|
137
|
-
show_default="system.toml, else
|
|
167
|
+
show_default="system.toml, else installed, else latest supported",
|
|
138
168
|
help="Isaac Sim version to run.",
|
|
139
169
|
)
|
|
140
170
|
@click.option(
|
|
@@ -153,7 +183,7 @@ def sim_group(ctx: click.Context):
|
|
|
153
183
|
)
|
|
154
184
|
@click.pass_context
|
|
155
185
|
def launch_cmd(ctx: click.Context, sim_version: str, ros_bridge: str, no_ros: bool):
|
|
156
|
-
"""
|
|
186
|
+
"""Set up missing prerequisites, then launch Isaac Sim with extra args."""
|
|
157
187
|
_guarded(
|
|
158
188
|
Runner.run_sim,
|
|
159
189
|
version=sim_version,
|
|
@@ -171,12 +201,12 @@ def launch_cmd(ctx: click.Context, sim_version: str, ros_bridge: str, no_ros: bo
|
|
|
171
201
|
"--version",
|
|
172
202
|
"sim_version",
|
|
173
203
|
default=_default_sim_version,
|
|
174
|
-
show_default="system.toml, else
|
|
204
|
+
show_default="system.toml, else installed, else latest supported",
|
|
175
205
|
help="Isaac Sim version to check.",
|
|
176
206
|
)
|
|
177
207
|
@click.pass_context
|
|
178
208
|
def check_cmd(ctx: click.Context, sim_version: str):
|
|
179
|
-
"""
|
|
209
|
+
"""Set up missing prerequisites, then run the Isaac Sim compatibility check.
|
|
180
210
|
|
|
181
211
|
\b
|
|
182
212
|
Runs .pow/isaacsim/<version>/isaac-sim.compatibility_check.sh, which
|
|
@@ -184,4 +214,4 @@ def check_cmd(ctx: click.Context, sim_version: str):
|
|
|
184
214
|
project, and the script builds its own ROS environment - forward
|
|
185
215
|
`-- --no-ros-env` to skip that step.
|
|
186
216
|
"""
|
|
187
|
-
_guarded(Runner.run_sim_check, version=sim_version, extra_args=ctx.args)
|
|
217
|
+
_guarded(Runner.run_sim_check, check=True, version=sim_version, extra_args=ctx.args)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Shared Isaac Sim installation progress for init and sim commands."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
from rich.progress import BarColumn, Progress, TextColumn
|
|
6
|
+
|
|
7
|
+
from .utils import console
|
|
8
|
+
from ..core.initializer import Initializer
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def download_with_progress(
|
|
12
|
+
initializer: Initializer, version: str, *, check: bool = False
|
|
13
|
+
) -> dict:
|
|
14
|
+
"""Install an already-selected version, propagating setup failures."""
|
|
15
|
+
console.print(f" Installing Isaac Sim [bold]{version}[/bold]...")
|
|
16
|
+
|
|
17
|
+
last_completed = 0
|
|
18
|
+
last_time = time.time()
|
|
19
|
+
current_phase = "download"
|
|
20
|
+
|
|
21
|
+
with Progress(
|
|
22
|
+
TextColumn("[bold blue]{task.fields[filename]}", justify="right"),
|
|
23
|
+
BarColumn(bar_width=40),
|
|
24
|
+
"[progress.percentage]{task.percentage:>3.1f}%",
|
|
25
|
+
"•",
|
|
26
|
+
TextColumn("[bold cyan]{task.fields[speed]}"),
|
|
27
|
+
console=console,
|
|
28
|
+
refresh_per_second=10,
|
|
29
|
+
) as progress:
|
|
30
|
+
download_task = progress.add_task(
|
|
31
|
+
"download", filename=f"isaac-sim-{version}.zip", total=None, speed="0 MB/s"
|
|
32
|
+
)
|
|
33
|
+
extract_task = progress.add_task(
|
|
34
|
+
"extract", filename="", total=None, speed=" ", visible=False
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
def progress_callback(completed, total):
|
|
38
|
+
nonlocal last_completed, last_time, current_phase
|
|
39
|
+
if not total:
|
|
40
|
+
return
|
|
41
|
+
now = time.time()
|
|
42
|
+
if current_phase == "download":
|
|
43
|
+
progress.update(download_task, total=total, completed=completed)
|
|
44
|
+
elapsed = now - last_time
|
|
45
|
+
if elapsed >= 0.5:
|
|
46
|
+
speed_mb = ((completed - last_completed) / (1024 * 1024)) / elapsed
|
|
47
|
+
progress.update(download_task, speed=f"{speed_mb:.1f} MB/s")
|
|
48
|
+
last_completed = completed
|
|
49
|
+
last_time = now
|
|
50
|
+
elif current_phase == "extract":
|
|
51
|
+
progress.update(extract_task, total=total, completed=completed)
|
|
52
|
+
|
|
53
|
+
def status_callback(status):
|
|
54
|
+
nonlocal current_phase, last_completed, last_time
|
|
55
|
+
if status == "Downloading":
|
|
56
|
+
current_phase = "download"
|
|
57
|
+
elif status == "Extracting":
|
|
58
|
+
current_phase = "extract"
|
|
59
|
+
progress.update(download_task, visible=False)
|
|
60
|
+
progress.update(
|
|
61
|
+
extract_task,
|
|
62
|
+
filename=f"isaac-sim-{version} [yellow](Extracting...)[/yellow]",
|
|
63
|
+
speed=" ",
|
|
64
|
+
completed=0,
|
|
65
|
+
total=None,
|
|
66
|
+
visible=True,
|
|
67
|
+
)
|
|
68
|
+
last_completed = 0
|
|
69
|
+
last_time = time.time()
|
|
70
|
+
elif status == "Extracted":
|
|
71
|
+
progress.update(
|
|
72
|
+
extract_task,
|
|
73
|
+
filename=f"isaac-sim-{version} [green](Extracted → {version})[/green]",
|
|
74
|
+
)
|
|
75
|
+
time.sleep(1)
|
|
76
|
+
|
|
77
|
+
result = initializer.download_isaacsim(
|
|
78
|
+
version=version,
|
|
79
|
+
progress_callback=progress_callback,
|
|
80
|
+
status_callback=status_callback,
|
|
81
|
+
check=check,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Print outside the progress context so lines don't get consumed.
|
|
85
|
+
result.setdefault("version", version)
|
|
86
|
+
if result["status"] == "Already installed":
|
|
87
|
+
console.print(
|
|
88
|
+
f" [yellow]✔[/yellow] Isaac Sim {version} is already installed at [dim]{result['path']}[/dim]"
|
|
89
|
+
)
|
|
90
|
+
else:
|
|
91
|
+
console.print(
|
|
92
|
+
f" [green]✔[/green] Downloaded and extracted to [dim]{result['path']}[/dim]"
|
|
93
|
+
)
|
|
94
|
+
if result.get("backup"):
|
|
95
|
+
console.print(f" Previous installation preserved at [dim]{result['backup']}[/dim]")
|
|
96
|
+
return result
|
|
97
|
+
|
|
@@ -537,7 +537,8 @@ class AssetManager:
|
|
|
537
537
|
if assets_symlink.is_symlink():
|
|
538
538
|
raise AssetError(
|
|
539
539
|
f"'{assets_symlink}' is already a symlink.\n"
|
|
540
|
-
"
|
|
540
|
+
"Run 'pow asset unset' to remove the current asset configuration "
|
|
541
|
+
"before running this command again."
|
|
541
542
|
)
|
|
542
543
|
|
|
543
544
|
@staticmethod
|