devagent-physical-engine 0.10.0__py3-none-any.whl

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.
Files changed (84) hide show
  1. devagent_physical_engine/__init__.py +44 -0
  2. devagent_physical_engine/agent/__init__.py +40 -0
  3. devagent_physical_engine/agent/compiler.py +285 -0
  4. devagent_physical_engine/agent/contracts.py +129 -0
  5. devagent_physical_engine/agent/coordinator.py +108 -0
  6. devagent_physical_engine/agent/critic.py +72 -0
  7. devagent_physical_engine/agent/evidence.py +34 -0
  8. devagent_physical_engine/agent/interpreter.py +179 -0
  9. devagent_physical_engine/agent/planner.py +105 -0
  10. devagent_physical_engine/agent/recovery.py +54 -0
  11. devagent_physical_engine/agent/routing.py +76 -0
  12. devagent_physical_engine/agent/runtime.py +270 -0
  13. devagent_physical_engine/agent/semantic.py +304 -0
  14. devagent_physical_engine/agent/structured.py +423 -0
  15. devagent_physical_engine/ai_cli.py +226 -0
  16. devagent_physical_engine/cli.py +392 -0
  17. devagent_physical_engine/doctor.py +20 -0
  18. devagent_physical_engine/engineering_agent.py +243 -0
  19. devagent_physical_engine/engineering_request.py +630 -0
  20. devagent_physical_engine/execution.py +90 -0
  21. devagent_physical_engine/models.py +143 -0
  22. devagent_physical_engine/operating_envelope.py +120 -0
  23. devagent_physical_engine/optimization/__init__.py +50 -0
  24. devagent_physical_engine/optimization/benchmark.py +122 -0
  25. devagent_physical_engine/optimization/candidates.py +198 -0
  26. devagent_physical_engine/optimization/contracts.py +235 -0
  27. devagent_physical_engine/optimization/evaluator.py +107 -0
  28. devagent_physical_engine/optimization/evidence.py +53 -0
  29. devagent_physical_engine/optimization/experience.py +105 -0
  30. devagent_physical_engine/optimization/measured.py +125 -0
  31. devagent_physical_engine/optimization/optimizer.py +215 -0
  32. devagent_physical_engine/optimization/orchestrator.py +155 -0
  33. devagent_physical_engine/physical_campaign.py +413 -0
  34. devagent_physical_engine/physical_evidence.py +214 -0
  35. devagent_physical_engine/physical_motion.py +196 -0
  36. devagent_physical_engine/planning.py +80 -0
  37. devagent_physical_engine/preexecution_contract.py +65 -0
  38. devagent_physical_engine/provider_adapters/__init__.py +22 -0
  39. devagent_physical_engine/provider_adapters/anthropic.py +112 -0
  40. devagent_physical_engine/provider_adapters/common.py +187 -0
  41. devagent_physical_engine/provider_adapters/factory.py +20 -0
  42. devagent_physical_engine/provider_adapters/gemini.py +126 -0
  43. devagent_physical_engine/provider_adapters/openai.py +95 -0
  44. devagent_physical_engine/provider_qualification.py +268 -0
  45. devagent_physical_engine/providers.py +94 -0
  46. devagent_physical_engine/qualification.py +44 -0
  47. devagent_physical_engine/qualification_cli.py +195 -0
  48. devagent_physical_engine/qualification_harness.py +917 -0
  49. devagent_physical_engine/robot_platform.py +411 -0
  50. devagent_physical_engine/robots.py +76 -0
  51. devagent_physical_engine/ros2/__init__.py +35 -0
  52. devagent_physical_engine/ros2/acceptance.py +324 -0
  53. devagent_physical_engine/ros2/commands.py +175 -0
  54. devagent_physical_engine/ros2/doctor.py +116 -0
  55. devagent_physical_engine/ros2/fk_probe.py +83 -0
  56. devagent_physical_engine/ros2/frame_alignment.py +61 -0
  57. devagent_physical_engine/ros2/gazebo_world.py +125 -0
  58. devagent_physical_engine/ros2/joint_state_recorder.py +64 -0
  59. devagent_physical_engine/ros2/measured_motion.py +233 -0
  60. devagent_physical_engine/ros2/moveit_scene.py +121 -0
  61. devagent_physical_engine/ros2/preexecution.py +113 -0
  62. devagent_physical_engine/ros2/qualification.py +81 -0
  63. devagent_physical_engine/ros2/qualification_v10.py +252 -0
  64. devagent_physical_engine/ros2/scene_probe.py +219 -0
  65. devagent_physical_engine/ros2/state_validity_probe.py +125 -0
  66. devagent_physical_engine/ros2/tf_probe.py +51 -0
  67. devagent_physical_engine/ros2/trajectory.py +188 -0
  68. devagent_physical_engine/ros2/ur5e.py +59 -0
  69. devagent_physical_engine/ros2/ur5e_adapter.py +349 -0
  70. devagent_physical_engine/ros2/ur5e_v10_adapter.py +292 -0
  71. devagent_physical_engine/setup_profile.py +356 -0
  72. devagent_physical_engine/simulation.py +32 -0
  73. devagent_physical_engine/simulation_platform.py +269 -0
  74. devagent_physical_engine/trajectory_qualification.py +201 -0
  75. devagent_physical_engine/twin.py +939 -0
  76. devagent_physical_engine/twin_builder.py +309 -0
  77. devagent_physical_engine/twin_materialization.py +404 -0
  78. devagent_physical_engine/verification.py +46 -0
  79. devagent_physical_engine-0.10.0.dist-info/METADATA +315 -0
  80. devagent_physical_engine-0.10.0.dist-info/RECORD +84 -0
  81. devagent_physical_engine-0.10.0.dist-info/WHEEL +5 -0
  82. devagent_physical_engine-0.10.0.dist-info/entry_points.txt +3 -0
  83. devagent_physical_engine-0.10.0.dist-info/licenses/NOTICE +2 -0
  84. devagent_physical_engine-0.10.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,411 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from enum import Enum, IntEnum
5
+ from types import MappingProxyType
6
+ from typing import Any, Iterable, Mapping
7
+
8
+ from .models import Capability, Resource
9
+
10
+
11
+ class RobotPlatformError(ValueError):
12
+ pass
13
+
14
+
15
+ class QualificationState(str, Enum):
16
+ QUALIFIED = "qualified"
17
+ EXPERIMENTAL = "experimental"
18
+ NOT_QUALIFIED = "not_qualified"
19
+ NOT_SUPPORTED = "not_supported"
20
+
21
+
22
+ class SimulationTier(str, Enum):
23
+ DETERMINISTIC = "deterministic"
24
+ KINEMATIC = "kinematic"
25
+ PHYSICS = "physics"
26
+ VISUAL = "visual"
27
+
28
+
29
+ class RobotLifecycleLevel(IntEnum):
30
+ DISCOVERED = 0
31
+ SIM_READY = 1
32
+ SIM_QUALIFIED = 2
33
+ HARDWARE_VERIFIED = 3
34
+ SIM_REAL_CORRELATED = 4
35
+ SITE_QUALIFIED = 5
36
+
37
+
38
+ @dataclass(frozen=True, slots=True)
39
+ class RosIntegration:
40
+ """Robot-facing integration contract, not a claim that a package is installed."""
41
+
42
+ ros2_compatible: bool
43
+ joint_state_interface: str | None = None
44
+ trajectory_interface: str | None = None
45
+ robot_description: str | None = None
46
+ moveit_config: str | None = None
47
+ hardware_driver: str | None = None
48
+
49
+ def __post_init__(self) -> None:
50
+ for name in (
51
+ "joint_state_interface",
52
+ "trajectory_interface",
53
+ "robot_description",
54
+ "moveit_config",
55
+ "hardware_driver",
56
+ ):
57
+ value = getattr(self, name)
58
+ if value is not None and not value.strip():
59
+ raise RobotPlatformError(f"empty_ros_integration_field:{name}")
60
+ if not self.ros2_compatible and any(
61
+ getattr(self, name) is not None
62
+ for name in (
63
+ "joint_state_interface",
64
+ "trajectory_interface",
65
+ "robot_description",
66
+ "moveit_config",
67
+ "hardware_driver",
68
+ )
69
+ ):
70
+ raise RobotPlatformError("non_ros_profile_cannot_declare_ros_packages")
71
+
72
+
73
+ @dataclass(frozen=True, slots=True)
74
+ class RobotQualification:
75
+ simulation: Mapping[SimulationTier, QualificationState]
76
+ hardware_shadow: QualificationState = QualificationState.NOT_QUALIFIED
77
+ sim_real_correlation: QualificationState = QualificationState.NOT_QUALIFIED
78
+ site_qualification: QualificationState = QualificationState.NOT_QUALIFIED
79
+
80
+ def __post_init__(self) -> None:
81
+ normalized = {
82
+ tier: self.simulation.get(tier, QualificationState.NOT_QUALIFIED)
83
+ for tier in SimulationTier
84
+ }
85
+ object.__setattr__(self, "simulation", MappingProxyType(normalized))
86
+
87
+ def state_for(self, tier: SimulationTier) -> QualificationState:
88
+ return self.simulation[tier]
89
+
90
+ @property
91
+ def highest_qualified_simulation_tier(self) -> SimulationTier | None:
92
+ order = (
93
+ SimulationTier.DETERMINISTIC,
94
+ SimulationTier.KINEMATIC,
95
+ SimulationTier.PHYSICS,
96
+ SimulationTier.VISUAL,
97
+ )
98
+ qualified = [
99
+ tier
100
+ for tier in order
101
+ if self.simulation[tier] is QualificationState.QUALIFIED
102
+ ]
103
+ return qualified[-1] if qualified else None
104
+
105
+
106
+ @dataclass(frozen=True, slots=True)
107
+ class RobotLimits:
108
+ payload_kg: float | None = None
109
+ reach_m: float | None = None
110
+
111
+ def __post_init__(self) -> None:
112
+ if self.payload_kg is not None and self.payload_kg <= 0:
113
+ raise RobotPlatformError("payload_limit_invalid")
114
+ if self.reach_m is not None and self.reach_m <= 0:
115
+ raise RobotPlatformError("reach_limit_invalid")
116
+
117
+
118
+ @dataclass(frozen=True, slots=True)
119
+ class RobotProfile:
120
+ key: str
121
+ vendor: str
122
+ model: str
123
+ capabilities: frozenset[Capability]
124
+ qualification: RobotQualification
125
+ ros: RosIntegration
126
+ limits: RobotLimits = field(default_factory=RobotLimits)
127
+ aliases: tuple[str, ...] = ()
128
+ resource_type: str = "industrial_arm"
129
+ controller_family: str | None = None
130
+ legacy_qualification: str | None = None
131
+ notes: tuple[str, ...] = ()
132
+
133
+ def __post_init__(self) -> None:
134
+ key = self.key.strip().lower()
135
+ vendor = self.vendor.strip().lower()
136
+ model = self.model.strip().lower()
137
+ if not key or not vendor or not model:
138
+ raise RobotPlatformError("robot_profile_identity_required")
139
+ if not self.resource_type.strip():
140
+ raise RobotPlatformError("robot_resource_type_required")
141
+ if not self.capabilities:
142
+ raise RobotPlatformError("robot_capabilities_required")
143
+ if any(not alias.strip() for alias in self.aliases):
144
+ raise RobotPlatformError("empty_robot_alias")
145
+ if len(set(alias.strip().lower() for alias in self.aliases)) != len(self.aliases):
146
+ raise RobotPlatformError("duplicate_robot_alias")
147
+ if self.legacy_qualification is not None and not self.legacy_qualification.strip():
148
+ raise RobotPlatformError("empty_legacy_qualification")
149
+ if any(not note.strip() for note in self.notes):
150
+ raise RobotPlatformError("empty_robot_note")
151
+ object.__setattr__(self, "key", key)
152
+ object.__setattr__(self, "vendor", vendor)
153
+ object.__setattr__(self, "model", model)
154
+ object.__setattr__(
155
+ self,
156
+ "aliases",
157
+ tuple(alias.strip().lower() for alias in self.aliases),
158
+ )
159
+ if self.legacy_qualification is not None:
160
+ object.__setattr__(
161
+ self,
162
+ "legacy_qualification",
163
+ self.legacy_qualification.strip().lower(),
164
+ )
165
+
166
+ @property
167
+ def lifecycle_level(self) -> RobotLifecycleLevel:
168
+ if self.qualification.site_qualification is QualificationState.QUALIFIED:
169
+ return RobotLifecycleLevel.SITE_QUALIFIED
170
+ if self.qualification.sim_real_correlation is QualificationState.QUALIFIED:
171
+ return RobotLifecycleLevel.SIM_REAL_CORRELATED
172
+ if self.qualification.hardware_shadow is QualificationState.QUALIFIED:
173
+ return RobotLifecycleLevel.HARDWARE_VERIFIED
174
+
175
+ physical_states = tuple(
176
+ self.qualification.state_for(tier)
177
+ for tier in (
178
+ SimulationTier.KINEMATIC,
179
+ SimulationTier.PHYSICS,
180
+ SimulationTier.VISUAL,
181
+ )
182
+ )
183
+ if any(state is QualificationState.QUALIFIED for state in physical_states):
184
+ return RobotLifecycleLevel.SIM_QUALIFIED
185
+ if any(state is QualificationState.EXPERIMENTAL for state in physical_states):
186
+ return RobotLifecycleLevel.SIM_READY
187
+ return RobotLifecycleLevel.DISCOVERED
188
+
189
+ def to_resource(self, resource_id: str = "robot_01") -> Resource:
190
+ if not resource_id.strip():
191
+ raise RobotPlatformError("resource_id_required")
192
+ highest = self.qualification.highest_qualified_simulation_tier
193
+ metadata: dict[str, Any] = {
194
+ "robot_profile": self.key,
195
+ "vendor": self.vendor,
196
+ "model": self.model,
197
+ "qualification": self.legacy_qualification
198
+ or (highest.value if highest is not None else "not_qualified"),
199
+ "simulation_qualification": {
200
+ tier.value: self.qualification.state_for(tier).value
201
+ for tier in SimulationTier
202
+ },
203
+ "hardware_shadow_qualification": self.qualification.hardware_shadow.value,
204
+ "sim_real_qualification": self.qualification.sim_real_correlation.value,
205
+ "site_qualification": self.qualification.site_qualification.value,
206
+ "lifecycle_level": int(self.lifecycle_level),
207
+ "lifecycle_name": self.lifecycle_level.name.lower(),
208
+ "real_execution_locked": True,
209
+ }
210
+ if self.controller_family:
211
+ metadata["controller_family"] = self.controller_family
212
+ return Resource(
213
+ resource_id,
214
+ self.resource_type,
215
+ self.capabilities,
216
+ metadata,
217
+ )
218
+
219
+
220
+ class RobotProfileRegistry:
221
+ """Vendor-neutral profile registry with deterministic alias resolution."""
222
+
223
+ def __init__(self, profiles: Iterable[RobotProfile] = ()) -> None:
224
+ self._profiles: dict[str, RobotProfile] = {}
225
+ self._aliases: dict[str, str] = {}
226
+ for profile in profiles:
227
+ self.register(profile)
228
+
229
+ @staticmethod
230
+ def _normalize(value: str) -> str:
231
+ return "_".join(value.strip().lower().replace("-", " ").split())
232
+
233
+ def register(self, profile: RobotProfile, *, replace: bool = False) -> None:
234
+ key = self._normalize(profile.key)
235
+ aliases = {
236
+ self._normalize(profile.key),
237
+ self._normalize(f"{profile.vendor} {profile.model}"),
238
+ *(self._normalize(alias) for alias in profile.aliases),
239
+ }
240
+ if key in self._profiles and not replace:
241
+ raise RobotPlatformError(f"duplicate_robot_profile:{key}")
242
+ for alias in aliases:
243
+ owner = self._aliases.get(alias)
244
+ # replace=True may update the same profile, but it must never steal
245
+ # an alias owned by a different robot profile.
246
+ if owner is not None and owner != key:
247
+ raise RobotPlatformError(f"duplicate_robot_alias:{alias}")
248
+
249
+ if replace and key in self._profiles:
250
+ stale = [alias for alias, owner in self._aliases.items() if owner == key]
251
+ for alias in stale:
252
+ del self._aliases[alias]
253
+
254
+ self._profiles[key] = profile
255
+ for alias in aliases:
256
+ self._aliases[alias] = key
257
+
258
+ def resolve(self, name: str) -> RobotProfile:
259
+ normalized = self._normalize(name)
260
+ try:
261
+ key = self._aliases[normalized]
262
+ return self._profiles[key]
263
+ except KeyError as exc:
264
+ raise RobotPlatformError(f"unknown_robot_profile:{normalized}") from exc
265
+
266
+ def get(self, key: str) -> RobotProfile:
267
+ normalized = self._normalize(key)
268
+ try:
269
+ return self._profiles[normalized]
270
+ except KeyError as exc:
271
+ raise RobotPlatformError(f"unknown_robot_profile:{normalized}") from exc
272
+
273
+ def keys(self) -> tuple[str, ...]:
274
+ return tuple(sorted(self._profiles))
275
+
276
+ def all(self) -> tuple[RobotProfile, ...]:
277
+ return tuple(self._profiles[key] for key in self.keys())
278
+
279
+ def capability_matrix(self) -> tuple[dict[str, Any], ...]:
280
+ rows: list[dict[str, Any]] = []
281
+ for profile in self.all():
282
+ rows.append(
283
+ {
284
+ "robot": profile.key,
285
+ "vendor": profile.vendor,
286
+ "model": profile.model,
287
+ "ros2": profile.ros.ros2_compatible,
288
+ "lifecycle_level": int(profile.lifecycle_level),
289
+ "lifecycle_name": profile.lifecycle_level.name.lower(),
290
+ "simulation": {
291
+ tier.value: profile.qualification.state_for(tier).value
292
+ for tier in SimulationTier
293
+ },
294
+ "hardware_shadow": profile.qualification.hardware_shadow.value,
295
+ "sim_real": profile.qualification.sim_real_correlation.value,
296
+ "site": profile.qualification.site_qualification.value,
297
+ }
298
+ )
299
+ return tuple(rows)
300
+
301
+
302
+ ARM_CAPABILITIES = frozenset(
303
+ {
304
+ Capability.MOVE,
305
+ Capability.PICK,
306
+ Capability.PLACE,
307
+ Capability.WAIT,
308
+ Capability.TRANSFER,
309
+ }
310
+ )
311
+
312
+
313
+ def _qualification(
314
+ *,
315
+ deterministic: QualificationState = QualificationState.QUALIFIED,
316
+ kinematic: QualificationState = QualificationState.NOT_QUALIFIED,
317
+ physics: QualificationState = QualificationState.NOT_QUALIFIED,
318
+ visual: QualificationState = QualificationState.NOT_QUALIFIED,
319
+ ) -> RobotQualification:
320
+ return RobotQualification(
321
+ {
322
+ SimulationTier.DETERMINISTIC: deterministic,
323
+ SimulationTier.KINEMATIC: kinematic,
324
+ SimulationTier.PHYSICS: physics,
325
+ SimulationTier.VISUAL: visual,
326
+ }
327
+ )
328
+
329
+
330
+ # These statuses describe only evidence already established inside DevAgent.
331
+ # They intentionally do not infer hardware or sim-real qualification from the
332
+ # existence of an external vendor/ROS package.
333
+ BUILTIN_ROBOT_PROFILES = RobotProfileRegistry(
334
+ (
335
+ RobotProfile(
336
+ key="ur5e",
337
+ vendor="universal_robots",
338
+ model="ur5e",
339
+ capabilities=ARM_CAPABILITIES,
340
+ qualification=_qualification(
341
+ kinematic=QualificationState.QUALIFIED,
342
+ physics=QualificationState.QUALIFIED,
343
+ visual=QualificationState.QUALIFIED,
344
+ ),
345
+ ros=RosIntegration(
346
+ ros2_compatible=True,
347
+ joint_state_interface="/joint_states",
348
+ trajectory_interface="joint_trajectory_controller/follow_joint_trajectory",
349
+ robot_description="ur_description",
350
+ moveit_config="ur_moveit_config",
351
+ hardware_driver="ur_robot_driver",
352
+ ),
353
+ aliases=("ur 5e", "universal robots ur5e", "universal robot ur5e"),
354
+ legacy_qualification="primary",
355
+ notes=(
356
+ "DevAgent visual Gazebo/MoveIt workstation qualification exists; real execution remains locked.",
357
+ ),
358
+ ),
359
+ RobotProfile(
360
+ key="fanuc_crx",
361
+ vendor="fanuc",
362
+ model="crx",
363
+ capabilities=ARM_CAPABILITIES,
364
+ qualification=_qualification(),
365
+ ros=RosIntegration(
366
+ ros2_compatible=True,
367
+ joint_state_interface="/joint_states",
368
+ trajectory_interface="follow_joint_trajectory",
369
+ ),
370
+ aliases=("fanuc crx", "crx"),
371
+ legacy_qualification="primary",
372
+ notes=(
373
+ "Deterministic DevAgent qualification only until an exact ROS/simulator/controller profile is qualified.",
374
+ ),
375
+ ),
376
+ RobotProfile(
377
+ key="kuka_kr",
378
+ vendor="kuka",
379
+ model="kr",
380
+ capabilities=ARM_CAPABILITIES,
381
+ qualification=_qualification(),
382
+ ros=RosIntegration(
383
+ ros2_compatible=True,
384
+ joint_state_interface="/joint_states",
385
+ trajectory_interface="follow_joint_trajectory",
386
+ ),
387
+ aliases=("kuka kr",),
388
+ legacy_qualification="primary",
389
+ notes=(
390
+ "Deterministic DevAgent qualification only until an exact KR/controller profile is qualified.",
391
+ ),
392
+ ),
393
+ RobotProfile(
394
+ key="abb_irb",
395
+ vendor="abb",
396
+ model="irb",
397
+ capabilities=ARM_CAPABILITIES,
398
+ qualification=_qualification(),
399
+ ros=RosIntegration(
400
+ ros2_compatible=True,
401
+ joint_state_interface="/joint_states",
402
+ trajectory_interface="follow_joint_trajectory",
403
+ ),
404
+ aliases=("abb irb",),
405
+ legacy_qualification="simulation",
406
+ notes=(
407
+ "Generic IRB family placeholder; exact ABB model/controller qualification is required before physical claims.",
408
+ ),
409
+ ),
410
+ )
411
+ )
@@ -0,0 +1,76 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable, Iterator, Mapping
4
+
5
+ from .models import Resource
6
+ from .robot_platform import (
7
+ ARM_CAPABILITIES,
8
+ BUILTIN_ROBOT_PROFILES,
9
+ RobotPlatformError,
10
+ RobotProfileRegistry,
11
+ )
12
+
13
+
14
+ PROFILE_REGISTRY: RobotProfileRegistry = BUILTIN_ROBOT_PROFILES
15
+
16
+
17
+ def _resource(profile_key: str, resource_id: str) -> Resource:
18
+ return PROFILE_REGISTRY.get(profile_key).to_resource(resource_id)
19
+
20
+
21
+ def ur5e(resource_id: str = "robot_01") -> Resource:
22
+ return _resource("ur5e", resource_id)
23
+
24
+
25
+ def fanuc_crx(resource_id: str = "robot_01") -> Resource:
26
+ return _resource("fanuc_crx", resource_id)
27
+
28
+
29
+ def kuka_kr(resource_id: str = "robot_01") -> Resource:
30
+ return _resource("kuka_kr", resource_id)
31
+
32
+
33
+ def abb_irb(resource_id: str = "robot_01") -> Resource:
34
+ return _resource("abb_irb", resource_id)
35
+
36
+
37
+ class _RegistryBackedCatalog(Mapping[str, Callable[..., Resource]]):
38
+ """Compatibility view for older core code that expects CATALOG[key]().
39
+
40
+ Membership, iteration, and lookup are backed by RobotProfileRegistry. New
41
+ profiles therefore do not require changes in request/qualification core.
42
+ Aliases resolve to the profile's canonical identity before a Resource is built.
43
+
44
+ ``Mapping`` requires missing keys to raise ``KeyError``. The registry uses
45
+ ``RobotPlatformError`` for unknown robot identities, so that exception is
46
+ translated at this compatibility boundary. This preserves legacy
47
+ ``key in CATALOG`` / ``key not in CATALOG`` semantics without weakening the
48
+ registry's explicit error contract elsewhere.
49
+ """
50
+
51
+ def __init__(self, registry: RobotProfileRegistry) -> None:
52
+ self.registry = registry
53
+
54
+ def __getitem__(self, key: str) -> Callable[..., Resource]:
55
+ try:
56
+ profile = self.registry.resolve(key)
57
+ except RobotPlatformError as exc:
58
+ raise KeyError(key) from exc
59
+
60
+ def factory(resource_id: str = "robot_01") -> Resource:
61
+ return profile.to_resource(resource_id)
62
+
63
+ return factory
64
+
65
+ def __iter__(self) -> Iterator[str]:
66
+ return iter(self.registry.keys())
67
+
68
+ def __len__(self) -> int:
69
+ return len(self.registry.keys())
70
+
71
+
72
+ # Backward-compatible callable catalog used by v0.6/v0.7. New code should use
73
+ # PROFILE_REGISTRY directly for qualification and integration metadata.
74
+ CATALOG: Mapping[str, Callable[..., Resource]] = _RegistryBackedCatalog(
75
+ PROFILE_REGISTRY
76
+ )
@@ -0,0 +1,35 @@
1
+ from .acceptance import AcceptanceStage, LaptopAcceptanceReport, UR5eLaptopAcceptance
2
+ from .commands import CommandResult, SubprocessRosRunner
3
+ from .doctor import RosCheck, RosDoctor, RosDoctorReport
4
+ from .trajectory import (
5
+ JointStateSnapshot,
6
+ RosTrajectoryError,
7
+ execute_joint_trajectory,
8
+ follow_joint_trajectory_command,
9
+ read_joint_state,
10
+ )
11
+ from .ur5e import UR5eSimulationLauncher, official_ur5e_moveit_command, official_ur_motion_smoke_command
12
+ from .ur5e_adapter import UR5E_JOINTS, UR5eGazeboAdapter
13
+ from .ur5e_v10_adapter import UR5eCanonicalTwinAdapter
14
+
15
+ __all__ = [
16
+ "AcceptanceStage",
17
+ "LaptopAcceptanceReport",
18
+ "UR5eLaptopAcceptance",
19
+ "CommandResult",
20
+ "SubprocessRosRunner",
21
+ "RosCheck",
22
+ "RosDoctor",
23
+ "RosDoctorReport",
24
+ "JointStateSnapshot",
25
+ "RosTrajectoryError",
26
+ "execute_joint_trajectory",
27
+ "follow_joint_trajectory_command",
28
+ "read_joint_state",
29
+ "UR5E_JOINTS",
30
+ "UR5eGazeboAdapter",
31
+ "UR5eCanonicalTwinAdapter",
32
+ "UR5eSimulationLauncher",
33
+ "official_ur5e_moveit_command",
34
+ "official_ur_motion_smoke_command",
35
+ ]