simulo 0.16.0__tar.gz → 0.17.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.
- {simulo-0.16.0/src/simulo.egg-info → simulo-0.17.0}/PKG-INFO +7 -6
- {simulo-0.16.0 → simulo-0.17.0}/pyproject.toml +34 -3
- simulo-0.17.0/src/simulo/__init__.py +409 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/asset.py +6 -5
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/asset_api.py +2 -2
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/asset_package.py +20 -4
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/asset_pins.py +3 -3
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/cli.py +449 -50
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/facades.py +144 -18
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/jobs_api.py +14 -4
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/learning.py +110 -12
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/templates/scenario/app.py.tmpl +7 -3
- {simulo-0.16.0 → simulo-0.17.0/src/simulo.egg-info}/PKG-INFO +7 -6
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo.egg-info/requires.txt +6 -5
- simulo-0.16.0/src/simulo/__init__.py +0 -188
- {simulo-0.16.0 → simulo-0.17.0}/MANIFEST.in +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/PYPI.md +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/setup.cfg +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/__init__.py +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/_entrypoint.py +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/_mounts.py +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/_runner.py +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/app.py +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/artifacts.py +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/builtin_aliases.py +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/bundle.py +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/cancel_api.py +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/config.py +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/credentials.py +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/discovery.py +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/http.py +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/login.py +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/mode.py +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/packaging.py +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/preflight_api.py +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/preflight_render.py +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/registry.py +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/runtime.py +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/runtime_display.py +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/seed_ref.py +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/stub.py +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/submit_api.py +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/templates/__init__.py +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/templates/inference/app.py.tmpl +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/templates/inference/simuloignore.tmpl +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/templates/scenario/simuloignore.tmpl +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/templates/training/app.py.tmpl +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/templates/training/simuloignore.tmpl +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/view_session_api.py +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/_client/volume.py +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/callbacks.py +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo/py.typed +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo.egg-info/SOURCES.txt +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo.egg-info/dependency_links.txt +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo.egg-info/entry_points.txt +0 -0
- {simulo-0.16.0 → simulo-0.17.0}/src/simulo.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: simulo
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.17.0
|
|
4
4
|
Summary: Simulo SDK and CLI — define robotics simulation and training apps in Python and run them on the Simulo cloud.
|
|
5
5
|
Author-email: Simulo Team <team@simulo.ai>
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -15,17 +15,18 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
15
15
|
Classifier: Typing :: Typed
|
|
16
16
|
Requires-Python: >=3.11
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
|
-
Requires-Dist: simulo-interfaces<0.
|
|
18
|
+
Requires-Dist: simulo-interfaces<0.12,>=0.11
|
|
19
19
|
Requires-Dist: mcap<2,>=1.3
|
|
20
20
|
Requires-Dist: defusedxml>=0.7.1
|
|
21
21
|
Requires-Dist: usd-core<27,>=25.5
|
|
22
22
|
Provides-Extra: assets
|
|
23
23
|
Requires-Dist: usd-core<27,>=25.5; extra == "assets"
|
|
24
24
|
Provides-Extra: dev
|
|
25
|
-
Requires-Dist: black
|
|
26
|
-
Requires-Dist: isort
|
|
27
|
-
Requires-Dist: ruff
|
|
28
|
-
Requires-Dist: mypy
|
|
25
|
+
Requires-Dist: black==26.5.1; extra == "dev"
|
|
26
|
+
Requires-Dist: isort==8.0.1; extra == "dev"
|
|
27
|
+
Requires-Dist: ruff==0.15.21; extra == "dev"
|
|
28
|
+
Requires-Dist: mypy==2.3.0; extra == "dev"
|
|
29
|
+
Requires-Dist: types-defusedxml==0.7.0.20260504; extra == "dev"
|
|
29
30
|
Requires-Dist: pytest>=8.2; extra == "dev"
|
|
30
31
|
Requires-Dist: build>=1.2; extra == "dev"
|
|
31
32
|
Requires-Dist: twine>=5.1; extra == "dev"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "simulo"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.17.0"
|
|
8
8
|
description = "Simulo SDK and CLI — define robotics simulation and training apps in Python and run them on the Simulo cloud."
|
|
9
9
|
readme = "PYPI.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -26,7 +26,7 @@ classifiers = [
|
|
|
26
26
|
# mcap: used by `simulo recordings` to read back a downloaded recording and
|
|
27
27
|
# verify it (message count) — MIT-licensed, deps only lz4+zstandard.
|
|
28
28
|
dependencies = [
|
|
29
|
-
"simulo-interfaces>=0.
|
|
29
|
+
"simulo-interfaces>=0.11,<0.12",
|
|
30
30
|
"mcap>=1.3,<2",
|
|
31
31
|
# Hardened XML parsing for URDF ingestion (USD Asset Catalogs, PR-12 fix
|
|
32
32
|
# loop 2, security NIT): stdlib xml.etree.ElementTree relies on
|
|
@@ -54,7 +54,27 @@ dependencies = [
|
|
|
54
54
|
# already installed unconditionally) but harmless — resolvers don't warn on
|
|
55
55
|
# an extra whose contents are already satisfied by the base requirement.
|
|
56
56
|
assets = ["usd-core>=25.5,<27"]
|
|
57
|
-
|
|
57
|
+
# black/isort/ruff/mypy pinned EXACT, not >=: see simulo-backend/pyproject.toml's
|
|
58
|
+
# `dev` extra for the full rationale (2026-08-01 ruff 0.15.21->0.16.1 CI incident).
|
|
59
|
+
# Keep in lockstep with `.github/workflows/simulo-ci.yml`'s `ruff`/`black`/`isort`/`mypy`
|
|
60
|
+
# steps, which resolve their versions from THIS extra (`pip install -e ".[dev]"`).
|
|
61
|
+
#
|
|
62
|
+
# `types-defusedxml` is pinned EXACT for the same reason as the four tools above:
|
|
63
|
+
# a stub package is a rule set, and a new typeshed release can tighten a
|
|
64
|
+
# signature and turn a green tree red with no code change. `defusedxml` is a
|
|
65
|
+
# declared RUNTIME dependency of this package (asset_package.py parses URDF with
|
|
66
|
+
# it), so without the stubs mypy reported `Library stubs not installed` and —
|
|
67
|
+
# worse — silently skipped type-checking every call into it.
|
|
68
|
+
dev = [
|
|
69
|
+
"black==26.5.1",
|
|
70
|
+
"isort==8.0.1",
|
|
71
|
+
"ruff==0.15.21",
|
|
72
|
+
"mypy==2.3.0",
|
|
73
|
+
"types-defusedxml==0.7.0.20260504",
|
|
74
|
+
"pytest>=8.2",
|
|
75
|
+
"build>=1.2",
|
|
76
|
+
"twine>=5.1",
|
|
77
|
+
]
|
|
58
78
|
# `dev` carries build/twine but deliberately not commitizen; `release` below
|
|
59
79
|
# adds commitizen for cutting version bumps without a second build/twine copy.
|
|
60
80
|
release = ["commitizen>=3.27", "build>=1.2", "twine>=5.1"]
|
|
@@ -91,6 +111,17 @@ namespace_packages = true
|
|
|
91
111
|
explicit_package_bases = true
|
|
92
112
|
strict = true
|
|
93
113
|
|
|
114
|
+
# `pxr` (the USD Python bindings, shipped by `usd-core`) is a C++-backed
|
|
115
|
+
# extension module that publishes neither type stubs nor a `py.typed` marker,
|
|
116
|
+
# so there is no more precise type available for it than the module object
|
|
117
|
+
# itself. Suppressed at the module level — the same mechanism, and for the same
|
|
118
|
+
# reason, as simulo-backend/pyproject.toml's skrl / onnxruntime overrides.
|
|
119
|
+
# Scoped to `pxr` alone, so it cannot hide a real type error anywhere else:
|
|
120
|
+
# `_load_pxr_usdutils` is the ONLY place this package imports it.
|
|
121
|
+
[[tool.mypy.overrides]]
|
|
122
|
+
module = ["pxr", "pxr.*"]
|
|
123
|
+
ignore_missing_imports = true
|
|
124
|
+
|
|
94
125
|
[tool.pytest.ini_options]
|
|
95
126
|
testpaths = ["tests"]
|
|
96
127
|
addopts = "--import-mode=importlib"
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
"""Simulo thin client — the user-facing, torch-free SDK surface.
|
|
2
|
+
|
|
3
|
+
This is the ONE Simulo distribution that ships ``simulo/__init__.py``. The
|
|
4
|
+
``extend_path`` call below unions every ``simulo/`` directory on ``sys.path`` into
|
|
5
|
+
this package's ``__path__`` so the PEP 420 namespace portions shipped by sibling
|
|
6
|
+
distributions (``simulo.core`` / ``simulo.backend`` / ``simulo.recording`` from
|
|
7
|
+
``simulo-backend``; ``simulo.interfaces`` from ``simulo-interfaces``) resolve
|
|
8
|
+
alongside the clean top-level ``simulo.App`` re-exported here.
|
|
9
|
+
|
|
10
|
+
It MUST run before the re-exports below, because importing the thin client's
|
|
11
|
+
internal ``App`` module pulls in ``simulo.interfaces`` (a sibling distribution).
|
|
12
|
+
|
|
13
|
+
**The one-import rule.** Everything a user can legitimately need is reachable
|
|
14
|
+
from ``import simulo``. ``simulo.interfaces`` is a packaging detail two
|
|
15
|
+
distributions share so the thin client and the backend hold ONE definition
|
|
16
|
+
instead of drifting copies — it is never a namespace a user types. A user who
|
|
17
|
+
can write ``simulo.Physics.rigid(...)`` must not have to discover a second
|
|
18
|
+
import to write ``except simulo.JobFailedError`` or
|
|
19
|
+
``collision=simulo.Collision.enabled()``.
|
|
20
|
+
|
|
21
|
+
The rule is enforced as a rule, not as a hand-kept list: the thin client's
|
|
22
|
+
one-import-rule test derives the expected surface from
|
|
23
|
+
``simulo.interfaces.__all__`` and ``simulo.interfaces.authoring.__all__`` and
|
|
24
|
+
fails on any name that is not reachable here or explicitly declared in that
|
|
25
|
+
test's exclusions table with a reason. A hand-maintained whitelist is only ever
|
|
26
|
+
as current as the last person who remembered to edit it, and two names were
|
|
27
|
+
missed that way before the rule replaced it.
|
|
28
|
+
|
|
29
|
+
Three kinds of name live on ``simulo``:
|
|
30
|
+
|
|
31
|
+
* **Platform** names — ``App / Runtime / Volume / Asset / Artifact /
|
|
32
|
+
ArtifactKind / callbacks`` — are **real, eager** attributes bound below.
|
|
33
|
+
They are always torch-free (``Artifact`` is the ``simulo-interfaces``
|
|
34
|
+
contract dataclass, ``ArtifactKind`` its wire-stable ``StrEnum``, both
|
|
35
|
+
re-exported).
|
|
36
|
+
* **Contract vocabulary** — the shared error hierarchy (``SimuloError`` and its
|
|
37
|
+
subclasses), the ``NewType`` identifier aliases (``JobId``, ``ProjectId``, …),
|
|
38
|
+
and the authoring value types that are not already carried by the lazy
|
|
39
|
+
resolver (``Collision``, ``Transform``, the primitive shapes, the factory
|
|
40
|
+
return types, the markers, …) — are also **real, eager** attributes. They cost
|
|
41
|
+
nothing to bind: ``simulo.interfaces.authoring`` is a zero-dependency package
|
|
42
|
+
that the thin client's own learning resolver already imports at module scope,
|
|
43
|
+
so this adds no import and no dependency. Errors and IDs are likewise
|
|
44
|
+
stdlib-only (``NewType`` is the underlying ``str`` at runtime).
|
|
45
|
+
* **Lazy, mode-aware** names — the learning names ``Task / Scene / Robot /
|
|
46
|
+
LearningEnv / RLTrainer / …`` plus the execution-time ``save_artifact`` and
|
|
47
|
+
``run`` (the scenario execution loop) — are
|
|
48
|
+
resolved through the PEP 562 ``__getattr__`` below, which delegates to the
|
|
49
|
+
thin client's internal learning-name resolver. At submit (discovery mode) they resolve to
|
|
50
|
+
lean stand-ins so the user app imports without the heavy runtime; only the
|
|
51
|
+
backend runner (execution mode) resolves them to the real implementations.
|
|
52
|
+
Because the platform names are real attributes, ``__getattr__`` fires *only*
|
|
53
|
+
for the lazy names. The RELOCATED authoring value types (``Pose`` /
|
|
54
|
+
``Material`` / ``Physics`` / ``Light`` / ``Terrain`` / ``World`` /
|
|
55
|
+
``Entity`` / ``Visual`` / ``RecordConfig``) ride the same resolver but
|
|
56
|
+
resolve to their real, dependency-free ``simulo.interfaces.authoring``
|
|
57
|
+
class in BOTH modes — they need no heavy runtime, and exactly one
|
|
58
|
+
definition exists. (``Terrain`` is ground you generate; ``World`` is a
|
|
59
|
+
prebuilt place you bring, ``World.usd(...)``.)
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
import pkgutil as _pkgutil
|
|
63
|
+
from typing import TYPE_CHECKING
|
|
64
|
+
|
|
65
|
+
__path__ = _pkgutil.extend_path(__path__, __name__)
|
|
66
|
+
|
|
67
|
+
from simulo import callbacks
|
|
68
|
+
from simulo._client.app import App
|
|
69
|
+
from simulo._client.asset import Asset
|
|
70
|
+
from simulo._client.learning import LEARNING_NAMES as _LEARNING_NAMES
|
|
71
|
+
from simulo._client.learning import resolve as _resolve_learning
|
|
72
|
+
from simulo._client.runtime import Runtime
|
|
73
|
+
from simulo._client.volume import Volume
|
|
74
|
+
|
|
75
|
+
# --- Contract vocabulary, re-exported under the one-import rule ---------------
|
|
76
|
+
#
|
|
77
|
+
# The shared error hierarchy and the identifier aliases. Both are stdlib-only:
|
|
78
|
+
# the exceptions derive from ``Exception``, and every identifier is a
|
|
79
|
+
# ``typing.NewType`` over ``str`` (zero runtime cost, distinct to a type
|
|
80
|
+
# checker). A user catching a failed job writes ``except simulo.JobFailedError``,
|
|
81
|
+
# never a second import.
|
|
82
|
+
from simulo.interfaces import (
|
|
83
|
+
CheckpointId,
|
|
84
|
+
ContractViolationError,
|
|
85
|
+
Digest,
|
|
86
|
+
ImportResolutionError,
|
|
87
|
+
JobFailedError,
|
|
88
|
+
JobId,
|
|
89
|
+
OrganizationId,
|
|
90
|
+
PackageId,
|
|
91
|
+
ProjectId,
|
|
92
|
+
ResourceId,
|
|
93
|
+
ResourceNotFoundError,
|
|
94
|
+
ResourceUri,
|
|
95
|
+
ResumeIncompatibleError,
|
|
96
|
+
SimuloError,
|
|
97
|
+
TagId,
|
|
98
|
+
TrainedModelId,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# The authoring value types that the lazy resolver does NOT already carry.
|
|
102
|
+
# Every one is a real, dependency-free class with exactly one definition, so —
|
|
103
|
+
# unlike the learning names — there is nothing mode-aware about them and no
|
|
104
|
+
# reason to defer: binding them eagerly is what makes ``simulo.Collision`` and
|
|
105
|
+
# ``simulo.Transform`` exist at all.
|
|
106
|
+
#
|
|
107
|
+
# ``simulo.Asset`` (the thin client's registry HANDLE, imported above — the
|
|
108
|
+
# name every shipped example and doc page uses) and
|
|
109
|
+
# ``simulo.interfaces.authoring.Asset`` (the model-source spec) used to share
|
|
110
|
+
# one name — two different user-facing
|
|
111
|
+
# classes, both exposing ``.usd()`` / ``.urdf()``, so re-exporting the
|
|
112
|
+
# authoring one here would have silently shadowed the handle users actually
|
|
113
|
+
# want. #693 (owner decision) resolved the collision by renaming the
|
|
114
|
+
# contract-side class to ``AssetSource``; with the names distinct, it is
|
|
115
|
+
# re-exported here like every other concrete authoring type — no exclusion
|
|
116
|
+
# needed any more.
|
|
117
|
+
from simulo.interfaces.authoring import (
|
|
118
|
+
ArrowMarker,
|
|
119
|
+
AssetEntity,
|
|
120
|
+
AssetSource,
|
|
121
|
+
AxesMarker,
|
|
122
|
+
Collision,
|
|
123
|
+
Cone,
|
|
124
|
+
Cuboid,
|
|
125
|
+
Cylinder,
|
|
126
|
+
DeformableMaterial,
|
|
127
|
+
DeformablePhysics,
|
|
128
|
+
DistantLight,
|
|
129
|
+
DomeLight,
|
|
130
|
+
GroundPlane,
|
|
131
|
+
LightConfig,
|
|
132
|
+
PointMarker,
|
|
133
|
+
PrimitiveShape,
|
|
134
|
+
RigidPhysics,
|
|
135
|
+
RoughTerrain,
|
|
136
|
+
Sphere,
|
|
137
|
+
SphereMarker,
|
|
138
|
+
SubTerrainConfig,
|
|
139
|
+
SurfaceMaterial,
|
|
140
|
+
TerrainConfig,
|
|
141
|
+
Transform,
|
|
142
|
+
URDFAsset,
|
|
143
|
+
USDAsset,
|
|
144
|
+
USDWorld,
|
|
145
|
+
VisualMarkerBase,
|
|
146
|
+
)
|
|
147
|
+
from simulo.interfaces.platform import Artifact, ArtifactKind
|
|
148
|
+
|
|
149
|
+
# Public surface = eager platform exports (real attributes; ``__getattr__`` never
|
|
150
|
+
# fires for these) + the lazily-resolved learning names. Kept a static literal so
|
|
151
|
+
# tooling recognises the re-exports; a test guards it against ``LEARNING_NAMES`` drift.
|
|
152
|
+
__all__ = [
|
|
153
|
+
# platform (eager, real)
|
|
154
|
+
"App",
|
|
155
|
+
"Artifact",
|
|
156
|
+
"ArtifactKind",
|
|
157
|
+
"Runtime",
|
|
158
|
+
"Volume",
|
|
159
|
+
"Asset",
|
|
160
|
+
"callbacks",
|
|
161
|
+
# contract errors (eager, real) — the shared exception hierarchy
|
|
162
|
+
"SimuloError",
|
|
163
|
+
"ContractViolationError",
|
|
164
|
+
"ImportResolutionError",
|
|
165
|
+
"ResourceNotFoundError",
|
|
166
|
+
"ResumeIncompatibleError",
|
|
167
|
+
"JobFailedError",
|
|
168
|
+
# contract identifiers (eager, real) — NewType aliases over ``str``
|
|
169
|
+
"OrganizationId",
|
|
170
|
+
"ProjectId",
|
|
171
|
+
"PackageId",
|
|
172
|
+
"JobId",
|
|
173
|
+
"ResourceId",
|
|
174
|
+
"CheckpointId",
|
|
175
|
+
"TrainedModelId",
|
|
176
|
+
"TagId",
|
|
177
|
+
"ResourceUri",
|
|
178
|
+
"Digest",
|
|
179
|
+
# authoring value types (eager, real) — the rest of the vocabulary the
|
|
180
|
+
# lazy resolver below does not carry
|
|
181
|
+
"Collision",
|
|
182
|
+
"RigidPhysics",
|
|
183
|
+
"DeformableMaterial",
|
|
184
|
+
"DeformablePhysics",
|
|
185
|
+
"SurfaceMaterial",
|
|
186
|
+
"AssetSource",
|
|
187
|
+
"USDAsset",
|
|
188
|
+
"URDFAsset",
|
|
189
|
+
"LightConfig",
|
|
190
|
+
"DistantLight",
|
|
191
|
+
"DomeLight",
|
|
192
|
+
"TerrainConfig",
|
|
193
|
+
"GroundPlane",
|
|
194
|
+
"SubTerrainConfig",
|
|
195
|
+
"RoughTerrain",
|
|
196
|
+
"USDWorld",
|
|
197
|
+
"PrimitiveShape",
|
|
198
|
+
"Cone",
|
|
199
|
+
"Cuboid",
|
|
200
|
+
"Sphere",
|
|
201
|
+
"Cylinder",
|
|
202
|
+
"Transform",
|
|
203
|
+
"AssetEntity",
|
|
204
|
+
"VisualMarkerBase",
|
|
205
|
+
"AxesMarker",
|
|
206
|
+
"SphereMarker",
|
|
207
|
+
"ArrowMarker",
|
|
208
|
+
"PointMarker",
|
|
209
|
+
# lazy, mode-aware (the learning names + save_artifact)
|
|
210
|
+
"ActuatorGainsConfig",
|
|
211
|
+
"Camera",
|
|
212
|
+
"CameraSpawnConfig",
|
|
213
|
+
"ContactSensor",
|
|
214
|
+
"DifferentialIKController",
|
|
215
|
+
"Entity",
|
|
216
|
+
"ForceTorqueSensor",
|
|
217
|
+
"FrameTransformer",
|
|
218
|
+
"FrameTransformerTarget",
|
|
219
|
+
"GripperState",
|
|
220
|
+
"IMU",
|
|
221
|
+
"LearningEnv",
|
|
222
|
+
"Light",
|
|
223
|
+
"Material",
|
|
224
|
+
"ObservationComponent",
|
|
225
|
+
"ObservationSet",
|
|
226
|
+
"OperationalSpaceController",
|
|
227
|
+
"Physics",
|
|
228
|
+
"Player",
|
|
229
|
+
"Policy",
|
|
230
|
+
"Pose",
|
|
231
|
+
"RLPlayer",
|
|
232
|
+
"RLTrainer",
|
|
233
|
+
"RayCaster",
|
|
234
|
+
"RayPattern",
|
|
235
|
+
"RecordConfig",
|
|
236
|
+
"RewardComponent",
|
|
237
|
+
"RewardSet",
|
|
238
|
+
"Robot",
|
|
239
|
+
"Scenario",
|
|
240
|
+
"Scene",
|
|
241
|
+
"SensorOffset",
|
|
242
|
+
"Simulation",
|
|
243
|
+
"SurfaceGripperActuator",
|
|
244
|
+
"Task",
|
|
245
|
+
"Terrain",
|
|
246
|
+
"TerminationCondition",
|
|
247
|
+
"TerminationSet",
|
|
248
|
+
"Trainer",
|
|
249
|
+
"Visual",
|
|
250
|
+
"World",
|
|
251
|
+
"run",
|
|
252
|
+
"save_artifact",
|
|
253
|
+
]
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def __getattr__(name: str) -> object:
|
|
257
|
+
"""PEP 562 hook: lazily resolve the mode-aware learning names.
|
|
258
|
+
|
|
259
|
+
Fires only for attributes missing from the module dict — i.e. never for the
|
|
260
|
+
real platform exports above. Delegates the learning names to the thin
|
|
261
|
+
client's internal resolver (discovery stand-in vs. real
|
|
262
|
+
``simulo.core`` class, by mode); everything else is a genuine ``AttributeError``.
|
|
263
|
+
"""
|
|
264
|
+
if name in _LEARNING_NAMES:
|
|
265
|
+
return _resolve_learning(name)
|
|
266
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def __dir__() -> list[str]:
|
|
270
|
+
"""Expose only the public surface to ``dir(simulo)``.
|
|
271
|
+
|
|
272
|
+
That is ``__all__`` (eager platform exports + learning names) unioned with
|
|
273
|
+
:data:`LEARNING_NAMES`. Private module globals (``_pkgutil``,
|
|
274
|
+
``_resolve_learning``, ``_LEARNING_NAMES``, the ``TYPE_CHECKING`` import, …)
|
|
275
|
+
are deliberately filtered out so tab-completion shows the user-facing API only.
|
|
276
|
+
"""
|
|
277
|
+
return sorted(set(__all__) | set(_LEARNING_NAMES))
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
if TYPE_CHECKING:
|
|
281
|
+
# Static aliases so a type checker sees the learning names with precise types
|
|
282
|
+
# (PEP 562 ``__getattr__`` would otherwise type them as ``Any``). The rule that
|
|
283
|
+
# this block MUST follow — and that the mypy positive test in
|
|
284
|
+
# ``tests/test_learning_typing.py`` locks — is:
|
|
285
|
+
#
|
|
286
|
+
# * **Subclassable** names (used as ``class X(simulo.Task)``) alias their
|
|
287
|
+
# ``interfaces.runtime`` Protocol, so the subclass is checked against the
|
|
288
|
+
# contract (e.g. ``TaskProtocol``).
|
|
289
|
+
# * **Instantiated mirror** names (used as ``simulo.LearningEnv(task=...)``,
|
|
290
|
+
# ``simulo.RLTrainer(env=...)``, ``simulo.Robot(...)``,
|
|
291
|
+
# ``simulo.RLPlayer(env=...)``) alias their typing FACADE in the thin
|
|
292
|
+
# client's internal facades module — a Protocol alias would make mypy
|
|
293
|
+
# reject instantiation ("Cannot instantiate protocol class"), and
|
|
294
|
+
# ``Any`` (the pre-Wave-C4 rule) type-checked nothing. The facade
|
|
295
|
+
# declares the contract surface plus the real constructor, so both the
|
|
296
|
+
# call and every member access are checked against the real member set
|
|
297
|
+
# and signature shape (name, kind, arity, presence of a default —
|
|
298
|
+
# NOT the declared type; see the facades module docstring for the
|
|
299
|
+
# known gap); facade fidelity to the contract AND to the concrete
|
|
300
|
+
# worker class is pinned by the backend suite's conformance engine.
|
|
301
|
+
# * **Called mirror** name (``simulo.run(scenario_class, ...)`` — the one
|
|
302
|
+
# non-learning, non-instantiated member of this kind): same problem as
|
|
303
|
+
# an instantiated mirror one level down — with no override, ``run``
|
|
304
|
+
# types as ``object`` (``__getattr__``'s declared return), and
|
|
305
|
+
# ``object`` is not *callable* at all. It aliases a plain FUNCTION
|
|
306
|
+
# facade (there is no Protocol for a bare function to alias against),
|
|
307
|
+
# never registered in the backend's class-shaped conformance engine —
|
|
308
|
+
# see the facades module docstring for where its drift guard lives
|
|
309
|
+
# instead.
|
|
310
|
+
# * **Annotation-position mirror** names alias their contract Protocol:
|
|
311
|
+
# ``Scene`` is ``SceneProtocol`` — measured across every shipped user
|
|
312
|
+
# surface it appears only as ``def build(self, scene: simulo.Scene)``,
|
|
313
|
+
# never instantiated, and the Protocol alias is what keeps that override
|
|
314
|
+
# Liskov-compatible with the contract's ``scene: SceneProtocol``.
|
|
315
|
+
# * **Relocated value types** alias their real, dependency-free
|
|
316
|
+
# ``simulo.interfaces.authoring`` class — precise field checking in both
|
|
317
|
+
# modes, because only one definition exists.
|
|
318
|
+
#
|
|
319
|
+
# Engine authoring vocabulary (the one-import rule's second half —
|
|
320
|
+
# ``Camera`` and its sensor siblings, ``Simulation``, the controllers, the
|
|
321
|
+
# actuators, the ``*Set`` containers): only ``Camera`` gets an alias here. ``CameraProtocol`` exists
|
|
322
|
+
# in ``simulo.interfaces.authoring`` and ``Camera`` is instantiated
|
|
323
|
+
# (``simulo.Camera(width=640, ...)``, never subclassed), so it follows the
|
|
324
|
+
# "instantiated mirror -> facade" rule above exactly. The other 17 names
|
|
325
|
+
# have no TYPE_CHECKING alias, DELIBERATELY, per the rule's own "where none
|
|
326
|
+
# exists, do not invent one" corollary:
|
|
327
|
+
# - ``CameraSpawnConfig`` / ``SensorOffset`` / ``RayPattern`` /
|
|
328
|
+
# ``ActuatorGainsConfig`` / ``GripperState`` / ``Simulation`` have no
|
|
329
|
+
# contract Protocol at all — nothing to alias against.
|
|
330
|
+
# - ``ContactSensor`` / ``RayCaster`` / ``IMU`` / ``ForceTorqueSensor`` /
|
|
331
|
+
# ``FrameTransformer`` conform only to the BASE ``SensorProtocol``
|
|
332
|
+
# (path/update_period/enabled/debug_vis/name/update/reset) — it does not
|
|
333
|
+
# declare their type-specific readers (``read_net_forces``,
|
|
334
|
+
# ``read_hits``, ``read_linear_acceleration``, …), so aliasing them to
|
|
335
|
+
# ``SensorProtocol`` would be a LOSSY, misleading type (mypy would flag
|
|
336
|
+
# real shipped calls to those readers as errors) and aliasing to a
|
|
337
|
+
# Protocol at all breaks instantiation ("Cannot instantiate protocol
|
|
338
|
+
# class") — the same reason facades exist. Their own per-type contracts
|
|
339
|
+
# are a later decision (``simulo.interfaces.authoring.sensor``'s own
|
|
340
|
+
# docstring), not implied here.
|
|
341
|
+
# - ``ObservationSet`` / ``RewardSet`` / ``TerminationSet`` /
|
|
342
|
+
# ``DifferentialIKController`` / ``OperationalSpaceController`` /
|
|
343
|
+
# ``SurfaceGripperActuator`` have no dedicated Protocol either.
|
|
344
|
+
# Runtime resolution (the lazy resolver) is unaffected either way — these
|
|
345
|
+
# fall back to ``__getattr__``'s ``-> object`` return type for a type
|
|
346
|
+
# checker, same as any name with no static override.
|
|
347
|
+
from simulo._client.facades import Camera as Camera
|
|
348
|
+
from simulo._client.facades import LearningEnv as LearningEnv
|
|
349
|
+
from simulo._client.facades import RLPlayer as RLPlayer
|
|
350
|
+
from simulo._client.facades import RLTrainer as RLTrainer
|
|
351
|
+
from simulo._client.facades import Robot as Robot
|
|
352
|
+
from simulo.interfaces.authoring import SceneProtocol
|
|
353
|
+
from simulo.interfaces.runtime import (
|
|
354
|
+
ObservationComponentProtocol,
|
|
355
|
+
PlayerProtocol,
|
|
356
|
+
PolicyProtocol,
|
|
357
|
+
RewardComponentProtocol,
|
|
358
|
+
ScenarioProtocol,
|
|
359
|
+
TaskProtocol,
|
|
360
|
+
TerminationConditionProtocol,
|
|
361
|
+
TrainerProtocol,
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
# Subclassable names alias their Protocol so ``class X(simulo.Task)`` is checked
|
|
365
|
+
# against ``TaskProtocol``.
|
|
366
|
+
Task = TaskProtocol
|
|
367
|
+
Scenario = ScenarioProtocol
|
|
368
|
+
Policy = PolicyProtocol
|
|
369
|
+
Player = PlayerProtocol
|
|
370
|
+
Trainer = TrainerProtocol
|
|
371
|
+
ObservationComponent = ObservationComponentProtocol
|
|
372
|
+
RewardComponent = RewardComponentProtocol
|
|
373
|
+
TerminationCondition = TerminationConditionProtocol
|
|
374
|
+
# Annotation-position mirror name (see the rule above).
|
|
375
|
+
Scene = SceneProtocol
|
|
376
|
+
# ``save_artifact`` aliases its real execution-mode implementation (a
|
|
377
|
+
# torch-free thin-client function), so ``simulo.save_artifact("summary",
|
|
378
|
+
# path)`` type-checks with the precise signature instead of ``Any``.
|
|
379
|
+
from simulo._client.artifacts import save_artifact as save_artifact
|
|
380
|
+
|
|
381
|
+
# ``run`` (the scenario execution loop) aliases its typing FACADE — a
|
|
382
|
+
# plain FUNCTION facade, not a class one (there is no ``RunProtocol`` to
|
|
383
|
+
# alias against; ``run`` is not a contract method). Without this, ``run``
|
|
384
|
+
# falls back to ``__getattr__``'s ``-> object`` return type, and
|
|
385
|
+
# ``object`` is not callable: ``simulo.run(MyScenario, ...)`` in a freshly
|
|
386
|
+
# scaffolded app is a hard mypy error no ``--ignore-missing-imports`` can
|
|
387
|
+
# suppress. The facade's real target (``simulo.scenario.run``) lives in
|
|
388
|
+
# the ``simulo-backend`` distribution — outside this package's own
|
|
389
|
+
# ``MYPYPATH`` — but the facade itself is thin-client-resident code
|
|
390
|
+
# (``simulo/_client/facades.py``), exactly like ``Camera``'s facade for
|
|
391
|
+
# the same reason; see that module's docstring for the full rationale and
|
|
392
|
+
# where drift against the real function is caught (a live, unmocked
|
|
393
|
+
# signature comparison in ``simulo-backend``'s test suite, not mypy).
|
|
394
|
+
from simulo._client.facades import run as run
|
|
395
|
+
|
|
396
|
+
# RELOCATED authoring value types (Plan C — Waves C1, C2, then C4's
|
|
397
|
+
# ``RecordConfig``): the runtime object IS the real, dependency-free
|
|
398
|
+
# ``simulo.interfaces.authoring`` class in both
|
|
399
|
+
# modes, so these alias the real class — precise field/signature checking
|
|
400
|
+
# (e.g. mypy rejects ``simulo.Pose(xyz="nope")``) instead of ``Any``.
|
|
401
|
+
from simulo.interfaces.authoring import Entity as Entity
|
|
402
|
+
from simulo.interfaces.authoring import Light as Light
|
|
403
|
+
from simulo.interfaces.authoring import Material as Material
|
|
404
|
+
from simulo.interfaces.authoring import Physics as Physics
|
|
405
|
+
from simulo.interfaces.authoring import Pose as Pose
|
|
406
|
+
from simulo.interfaces.authoring import RecordConfig as RecordConfig
|
|
407
|
+
from simulo.interfaces.authoring import Terrain as Terrain
|
|
408
|
+
from simulo.interfaces.authoring import Visual as Visual
|
|
409
|
+
from simulo.interfaces.authoring import World as World
|
|
@@ -23,8 +23,9 @@ The on-disk :attr:`path` only exists in **execution mode**:
|
|
|
23
23
|
name a robot/object source to load into a scene. In discovery (submit) these return
|
|
24
24
|
an inert, torch-free :class:`_AssetSpec` so authoring a task is lean; only the
|
|
25
25
|
backend runner (execution mode) resolves them to the real model-source
|
|
26
|
-
``simulo.interfaces.authoring.asset.
|
|
27
|
-
definition the backend consumes — relocated out of the backend in Plan C
|
|
26
|
+
``simulo.interfaces.authoring.asset.AssetSource`` (the single, dependency-free
|
|
27
|
+
definition the backend consumes — relocated out of the backend in Plan C, renamed
|
|
28
|
+
from ``Asset`` by owner decision to resolve the name collision with this very class).
|
|
28
29
|
|
|
29
30
|
**Layer split.** This is the user-facing *handle* (``simulo.Asset``); the
|
|
30
31
|
asset-catalog **wire** contract (ref grammar, routes, caps, wire dataclasses) lives
|
|
@@ -40,7 +41,7 @@ from typing import Any, Iterator, List, Optional, Tuple
|
|
|
40
41
|
|
|
41
42
|
from simulo._client._mounts import asset_env_key
|
|
42
43
|
from simulo._client.mode import EXECUTION, current_mode
|
|
43
|
-
from simulo.interfaces.authoring import
|
|
44
|
+
from simulo.interfaces.authoring import AssetSource as _ModelSourceAsset
|
|
44
45
|
from simulo.interfaces.exceptions import ContractViolationError, ResourceNotFoundError
|
|
45
46
|
from simulo.interfaces.platform.asset_catalog import (
|
|
46
47
|
GLOBAL_ASSET_PUBLISHER,
|
|
@@ -181,8 +182,8 @@ class Asset:
|
|
|
181
182
|
@staticmethod
|
|
182
183
|
def _model_asset(kind: str, path: str) -> Any:
|
|
183
184
|
if current_mode() == EXECUTION:
|
|
184
|
-
# The model-source
|
|
185
|
-
# ``simulo.interfaces.authoring.asset.
|
|
185
|
+
# The model-source AssetSource is the dependency-free
|
|
186
|
+
# ``simulo.interfaces.authoring.asset.AssetSource`` (single definition,
|
|
186
187
|
# relocated out of the backend in Plan C) — importable statically
|
|
187
188
|
# and torch-free, so no lazy import is needed any more.
|
|
188
189
|
factory = getattr(_ModelSourceAsset, kind)
|
|
@@ -574,9 +574,9 @@ def upload_archive(
|
|
|
574
574
|
uploaded = 0
|
|
575
575
|
for start in range(0, len(needed), _MAX_PART_NUMBERS_PER_BATCH):
|
|
576
576
|
batch = needed[start : start + _MAX_PART_NUMBERS_PER_BATCH]
|
|
577
|
-
|
|
577
|
+
specs_by_number = {int(spec["part_number"]): spec for spec in client.presign_parts(version_id, batch)}
|
|
578
578
|
for part_number in batch:
|
|
579
|
-
spec =
|
|
579
|
+
spec = specs_by_number.get(part_number)
|
|
580
580
|
if spec is None:
|
|
581
581
|
raise AssetApiError(f"The server did not presign part {part_number}.")
|
|
582
582
|
chunk = _read_chunk(tar_path, part_number, part_size)
|
|
@@ -66,6 +66,7 @@ import xml.etree.ElementTree as ET
|
|
|
66
66
|
from dataclasses import dataclass, field
|
|
67
67
|
from hashlib import sha256
|
|
68
68
|
from pathlib import Path, PurePosixPath
|
|
69
|
+
from types import ModuleType
|
|
69
70
|
from typing import Callable, Iterable, Optional
|
|
70
71
|
|
|
71
72
|
import defusedxml.ElementTree as DefusedET
|
|
@@ -132,7 +133,7 @@ class AssetsExtraMissingError(AssetPackageError):
|
|
|
132
133
|
ASSETS_EXTRA_INSTALL_HINT = 'pip install "simulo[assets]"'
|
|
133
134
|
|
|
134
135
|
|
|
135
|
-
def _load_pxr_usdutils(): # pragma: no cover - exercised via _extract_usd_references seam
|
|
136
|
+
def _load_pxr_usdutils() -> ModuleType: # pragma: no cover - exercised via _extract_usd_references seam
|
|
136
137
|
"""Import ``pxr.UsdUtils`` lazily — the ONLY place ``pxr`` is ever imported.
|
|
137
138
|
|
|
138
139
|
Raises :class:`AssetsExtraMissingError` with the copyable install command
|
|
@@ -145,7 +146,11 @@ def _load_pxr_usdutils(): # pragma: no cover - exercised via _extract_usd_refer
|
|
|
145
146
|
"Publishing or validating a USD package needs the USD inspection extra. "
|
|
146
147
|
f"Install it with: {ASSETS_EXTRA_INSTALL_HINT}"
|
|
147
148
|
) from exc
|
|
148
|
-
|
|
149
|
+
# `pxr` publishes no stubs (see the mypy override in pyproject.toml), so
|
|
150
|
+
# `UsdUtils` arrives as Any. Bind through a typed local so this function
|
|
151
|
+
# hands its caller a real ModuleType instead of leaking Any across the seam.
|
|
152
|
+
usd_utils: ModuleType = UsdUtils
|
|
153
|
+
return usd_utils
|
|
149
154
|
|
|
150
155
|
|
|
151
156
|
def _extract_usd_references(layer_path: Path) -> list[str]:
|
|
@@ -905,10 +910,21 @@ def _inspect_urdf(
|
|
|
905
910
|
"declarations and re-run."
|
|
906
911
|
) from exc
|
|
907
912
|
|
|
913
|
+
# ElementTree types getroot() as Optional because a tree can be constructed
|
|
914
|
+
# empty; a tree returned by a SUCCESSFUL parse always has a root, so this
|
|
915
|
+
# branch is unreachable in practice. It is a real check rather than a cast
|
|
916
|
+
# so that if it ever were reached, it surfaces as the same clean
|
|
917
|
+
# publish-time error as every other URDF problem — never an AttributeError.
|
|
918
|
+
root_element = tree.getroot()
|
|
919
|
+
if root_element is None: # pragma: no cover - unreachable after a successful parse
|
|
920
|
+
raise AssetPackageError(
|
|
921
|
+
f"{entry_rel} is not parseable as URDF XML: it has no root element. Fix the file and re-run."
|
|
922
|
+
)
|
|
923
|
+
|
|
908
924
|
closure: dict[str, Path] = {entry_rel: entry_abs}
|
|
909
925
|
reference_count = 0
|
|
910
926
|
rewrites: list[tuple[ET.Element, str]] = [] # (element, new entry-relative filename)
|
|
911
|
-
for element in
|
|
927
|
+
for element in root_element.iter():
|
|
912
928
|
if element.tag not in ("mesh", "texture"):
|
|
913
929
|
continue
|
|
914
930
|
ref = element.get("filename")
|
|
@@ -935,7 +951,7 @@ def _inspect_urdf(
|
|
|
935
951
|
if reference_count > 0:
|
|
936
952
|
for element, new_filename in rewrites:
|
|
937
953
|
element.set("filename", new_filename)
|
|
938
|
-
entry_override_bytes = ET.tostring(
|
|
954
|
+
entry_override_bytes = ET.tostring(root_element, encoding="utf-8", xml_declaration=True)
|
|
939
955
|
|
|
940
956
|
files = sorted(closure.items(), key=lambda item: item[0])
|
|
941
957
|
return entry_rel, auto, files, reference_count, warnings, entry_override_bytes
|
|
@@ -20,7 +20,7 @@ The heavy logic (resolution, gating, rendering) lives here rather than in
|
|
|
20
20
|
from __future__ import annotations
|
|
21
21
|
|
|
22
22
|
import sys
|
|
23
|
-
from typing import Dict, List, Optional, Sequence, TextIO
|
|
23
|
+
from typing import Any, Dict, List, Mapping, Optional, Sequence, TextIO
|
|
24
24
|
|
|
25
25
|
from simulo._client.runtime_display import display_runtime
|
|
26
26
|
from simulo._client.submit_api import AssetPinError, SubmitApiClient
|
|
@@ -34,7 +34,7 @@ from simulo.interfaces.platform.asset_catalog import DEFAULT_ASSET_RUNTIME, Reso
|
|
|
34
34
|
__all__ = ["DEFAULT_ASSET_RUNTIME", "collect_asset_refs", "resolve_and_pin_assets"]
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
def collect_asset_refs(manifest:
|
|
37
|
+
def collect_asset_refs(manifest: Mapping[str, Any]) -> List[str]:
|
|
38
38
|
"""Return the de-duplicated registry-asset refs an app declared, in first-seen
|
|
39
39
|
order.
|
|
40
40
|
|
|
@@ -85,7 +85,7 @@ def _resolved_publisher(canonical_ref: str) -> Optional[str]:
|
|
|
85
85
|
return None
|
|
86
86
|
|
|
87
87
|
|
|
88
|
-
def _render_resolved_block(refs: Sequence[str], resolved:
|
|
88
|
+
def _render_resolved_block(refs: Sequence[str], resolved: Mapping[str, ResolvedAssetPin]) -> str:
|
|
89
89
|
"""The ``Resolved assets:`` block — the authored (short) ref →
|
|
90
90
|
resolved version + short digest, columns aligned, in the order the refs were
|
|
91
91
|
declared. When the ref OMITTED its publisher (the caller's own org), the
|