luckyrobots 0.1.71__tar.gz → 0.1.73__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.
Files changed (52) hide show
  1. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/PKG-INFO +1 -1
  2. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/pyproject.toml +1 -1
  3. luckyrobots-0.1.73/src/luckyrobots/__init__.py +10 -0
  4. luckyrobots-0.1.73/src/luckyrobots/client.py +526 -0
  5. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/config/robots.yaml +72 -48
  6. luckyrobots-0.1.73/src/luckyrobots/engine/__init__.py +8 -0
  7. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/grpc/generated/agent_pb2.py +7 -3
  8. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/grpc/generated/agent_pb2_grpc.py +46 -0
  9. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/grpc/generated/scene_pb2.py +13 -3
  10. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/grpc/generated/scene_pb2_grpc.py +88 -0
  11. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/grpc/proto/agent.proto +29 -0
  12. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/grpc/proto/scene.proto +34 -0
  13. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/luckyrobots.py +37 -6
  14. luckyrobots-0.1.73/src/luckyrobots/models/__init__.py +3 -0
  15. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/models/observation.py +4 -33
  16. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/utils.py +1 -43
  17. luckyrobots-0.1.71/src/luckyrobots/__init__.py +0 -37
  18. luckyrobots-0.1.71/src/luckyrobots/client.py +0 -894
  19. luckyrobots-0.1.71/src/luckyrobots/engine/__init__.py +0 -23
  20. luckyrobots-0.1.71/src/luckyrobots/engine/check_updates.py +0 -264
  21. luckyrobots-0.1.71/src/luckyrobots/engine/download.py +0 -125
  22. luckyrobots-0.1.71/src/luckyrobots/models/__init__.py +0 -15
  23. luckyrobots-0.1.71/src/luckyrobots/models/camera.py +0 -97
  24. luckyrobots-0.1.71/src/luckyrobots/models/randomization.py +0 -77
  25. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/.gitignore +0 -0
  26. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/LICENSE +0 -0
  27. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/README.md +0 -0
  28. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/config/__init__.py +0 -0
  29. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/engine/manager.py +0 -0
  30. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/grpc/__init__.py +0 -0
  31. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/grpc/generated/__init__.py +0 -0
  32. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/grpc/generated/camera_pb2.py +0 -0
  33. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/grpc/generated/camera_pb2_grpc.py +0 -0
  34. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/grpc/generated/common_pb2.py +0 -0
  35. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/grpc/generated/common_pb2_grpc.py +0 -0
  36. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/grpc/generated/hazel_rpc_pb2.py +0 -0
  37. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/grpc/generated/hazel_rpc_pb2_grpc.py +0 -0
  38. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/grpc/generated/media_pb2.py +0 -0
  39. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/grpc/generated/media_pb2_grpc.py +0 -0
  40. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/grpc/generated/mujoco_pb2.py +0 -0
  41. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/grpc/generated/mujoco_pb2_grpc.py +0 -0
  42. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/grpc/generated/telemetry_pb2.py +0 -0
  43. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/grpc/generated/telemetry_pb2_grpc.py +0 -0
  44. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/grpc/generated/viewport_pb2.py +0 -0
  45. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/grpc/generated/viewport_pb2_grpc.py +0 -0
  46. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/grpc/proto/camera.proto +0 -0
  47. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/grpc/proto/common.proto +0 -0
  48. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/grpc/proto/hazel_rpc.proto +0 -0
  49. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/grpc/proto/media.proto +0 -0
  50. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/grpc/proto/mujoco.proto +0 -0
  51. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/grpc/proto/telemetry.proto +0 -0
  52. {luckyrobots-0.1.71 → luckyrobots-0.1.73}/src/luckyrobots/grpc/proto/viewport.proto +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: luckyrobots
3
- Version: 0.1.71
3
+ Version: 0.1.73
4
4
  Summary: Robotics-AI Training in Hyperrealistic Game Environments
5
5
  Project-URL: Homepage, https://github.com/luckyrobots/luckyrobots
6
6
  Project-URL: Documentation, https://luckyrobots.readthedocs.io
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "luckyrobots"
7
- version = "0.1.71"
7
+ version = "0.1.73"
8
8
  description = "Robotics-AI Training in Hyperrealistic Game Environments"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -0,0 +1,10 @@
1
+ """LuckyRobots - Robotics simulation framework with gRPC communication.
2
+
3
+ This package provides a Python API for controlling robots in the LuckyEngine
4
+ simulation environment via gRPC.
5
+ """
6
+
7
+ from luckyrobots.client import GrpcConnectionError as GrpcConnectionError
8
+ from luckyrobots.client import LuckyEngineClient as LuckyEngineClient
9
+ from luckyrobots.luckyrobots import LuckyRobots as LuckyRobots
10
+ from luckyrobots.models import ObservationResponse as ObservationResponse
@@ -0,0 +1,526 @@
1
+ """
2
+ LuckyEngine gRPC client.
3
+
4
+ Uses checked-in Python stubs generated from the `.proto` files under
5
+ `src/luckyrobots/grpc/proto/`.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ import time
12
+ from types import SimpleNamespace
13
+ from typing import Any, Optional
14
+
15
+ logger = logging.getLogger("luckyrobots.client")
16
+
17
+ try:
18
+ from .grpc.generated import agent_pb2 # type: ignore
19
+ from .grpc.generated import agent_pb2_grpc # type: ignore
20
+ from .grpc.generated import common_pb2 # type: ignore
21
+ from .grpc.generated import mujoco_pb2 # type: ignore
22
+ from .grpc.generated import mujoco_pb2_grpc # type: ignore
23
+ from .grpc.generated import scene_pb2 # type: ignore
24
+ from .grpc.generated import scene_pb2_grpc # type: ignore
25
+ except Exception as e: # pragma: no cover
26
+ raise ImportError(
27
+ "Missing generated gRPC stubs. Regenerate them from the protos in "
28
+ "src/luckyrobots/grpc/proto into src/luckyrobots/grpc/generated."
29
+ ) from e
30
+
31
+ from .models import ObservationResponse
32
+
33
+
34
+ class GrpcConnectionError(Exception):
35
+ """Raised when gRPC connection fails."""
36
+
37
+ def __init__(self, message: str):
38
+ super().__init__(message)
39
+ logger.warning("gRPC connection error: %s", message)
40
+
41
+
42
+ class LuckyEngineClient:
43
+ """
44
+ Client for connecting to the LuckyEngine gRPC server.
45
+
46
+ Provides access to gRPC services for RL training:
47
+ - AgentService: observations, stepping, resets
48
+ - SceneService: simulation mode control
49
+ - MujocoService: health checks
50
+
51
+ Usage:
52
+ client = LuckyEngineClient(host="127.0.0.1", port=50051)
53
+ client.connect()
54
+ client.wait_for_server()
55
+
56
+ schema = client.get_agent_schema()
57
+ obs = client.step(actions=[0.0] * 12)
58
+
59
+ client.close()
60
+ """
61
+
62
+ def __init__(
63
+ self,
64
+ host: str = "127.0.0.1",
65
+ port: int = 50051,
66
+ timeout: float = 5.0,
67
+ *,
68
+ robot_name: Optional[str] = None,
69
+ ) -> None:
70
+ """
71
+ Initialize the LuckyEngine gRPC client.
72
+
73
+ Args:
74
+ host: gRPC server host address.
75
+ port: gRPC server port.
76
+ timeout: Default timeout for RPC calls in seconds.
77
+ robot_name: Default robot name for calls that require it.
78
+ """
79
+ self.host = host
80
+ self.port = port
81
+ self.timeout = timeout
82
+ self._robot_name = robot_name
83
+
84
+ self._channel = None
85
+
86
+ # Service stubs (populated after connect)
87
+ self._scene = None
88
+ self._mujoco = None
89
+ self._agent = None
90
+
91
+ # Cached agent schemas: agent_name -> (observation_names, action_names)
92
+ self._schema_cache: dict[str, tuple[list[str], list[str]]] = {}
93
+
94
+ # Protobuf modules (for discoverability + explicit imports).
95
+ self._pb = SimpleNamespace(
96
+ common=common_pb2,
97
+ scene=scene_pb2,
98
+ mujoco=mujoco_pb2,
99
+ agent=agent_pb2,
100
+ )
101
+
102
+ def connect(self) -> None:
103
+ """
104
+ Connect to the LuckyEngine gRPC server.
105
+
106
+ Raises:
107
+ GrpcConnectionError: If connection fails.
108
+ """
109
+ try:
110
+ import grpc # type: ignore
111
+ except ImportError as e:
112
+ raise RuntimeError(
113
+ "Missing grpcio. Install with: pip install grpcio protobuf"
114
+ ) from e
115
+
116
+ target = f"{self.host}:{self.port}"
117
+ logger.info(f"Connecting to LuckyEngine gRPC server at {target}")
118
+
119
+ self._channel = grpc.insecure_channel(target)
120
+
121
+ # Create service stubs
122
+ self._scene = scene_pb2_grpc.SceneServiceStub(self._channel)
123
+ self._mujoco = mujoco_pb2_grpc.MujocoServiceStub(self._channel)
124
+ self._agent = agent_pb2_grpc.AgentServiceStub(self._channel)
125
+
126
+ logger.info(f"Channel opened to {target} (server not verified yet)")
127
+
128
+ def close(self) -> None:
129
+ """Close the gRPC channel."""
130
+ if self._channel is not None:
131
+ try:
132
+ self._channel.close()
133
+ except Exception as e:
134
+ logger.debug(f"Error closing gRPC channel: {e}")
135
+ self._channel = None
136
+ self._scene = None
137
+ self._mujoco = None
138
+ self._agent = None
139
+ logger.info("gRPC channel closed")
140
+
141
+ def is_connected(self) -> bool:
142
+ """Check if the client is connected."""
143
+ return self._channel is not None
144
+
145
+ def health_check(self, timeout: Optional[float] = None) -> bool:
146
+ """
147
+ Perform a health check by calling GetMujocoInfo.
148
+
149
+ Args:
150
+ timeout: Timeout in seconds (uses default if None).
151
+
152
+ Returns:
153
+ True if server responds, False otherwise.
154
+ """
155
+ if not self.is_connected():
156
+ return False
157
+
158
+ timeout = timeout or self.timeout
159
+ try:
160
+ self._mujoco.GetMujocoInfo(
161
+ self.pb.mujoco.GetMujocoInfoRequest(robot_name=self._robot_name or ""),
162
+ timeout=timeout,
163
+ )
164
+ return True
165
+ except Exception as e:
166
+ logger.debug(f"Health check failed: {e}")
167
+ return False
168
+
169
+ def wait_for_server(
170
+ self, timeout: float = 30.0, poll_interval: float = 0.5
171
+ ) -> bool:
172
+ """
173
+ Wait for the gRPC server to become available.
174
+
175
+ Args:
176
+ timeout: Maximum time to wait in seconds.
177
+ poll_interval: Time between connection attempts.
178
+
179
+ Returns:
180
+ True if server became available, False if timeout.
181
+ """
182
+ start = time.perf_counter()
183
+
184
+ while time.perf_counter() - start < timeout:
185
+ if not self.is_connected():
186
+ try:
187
+ self.connect()
188
+ except Exception:
189
+ pass
190
+
191
+ if self.health_check(timeout=10.0):
192
+ logger.info(f"Connected to LuckyEngine gRPC server at {self.host}:{self.port}")
193
+ return True
194
+
195
+ time.sleep(poll_interval)
196
+
197
+ return False
198
+
199
+ @property
200
+ def pb(self) -> Any:
201
+ """Access protobuf modules grouped by domain (e.g., `client.pb.scene`)."""
202
+ return self._pb
203
+
204
+ @property
205
+ def robot_name(self) -> Optional[str]:
206
+ """Default robot name used by calls that accept an optional robot_name."""
207
+ return self._robot_name
208
+
209
+ def set_robot_name(self, robot_name: str) -> None:
210
+ """Set the default robot name used by calls that accept an optional robot_name."""
211
+ self._robot_name = robot_name
212
+
213
+ @property
214
+ def scene(self) -> Any:
215
+ """SceneService stub."""
216
+ if self._scene is None:
217
+ raise GrpcConnectionError("Not connected. Call connect() first.")
218
+ return self._scene
219
+
220
+ @property
221
+ def mujoco(self) -> Any:
222
+ """MujocoService stub."""
223
+ if self._mujoco is None:
224
+ raise GrpcConnectionError("Not connected. Call connect() first.")
225
+ return self._mujoco
226
+
227
+ @property
228
+ def agent(self) -> Any:
229
+ """AgentService stub."""
230
+ if self._agent is None:
231
+ raise GrpcConnectionError("Not connected. Call connect() first.")
232
+ return self._agent
233
+
234
+ def get_mujoco_info(self, robot_name: str = "", timeout: Optional[float] = None):
235
+ """Get MuJoCo model information (joint names, limits, etc.)."""
236
+ timeout = timeout or self.timeout
237
+ robot_name = robot_name or self._robot_name
238
+ if not robot_name:
239
+ raise ValueError("robot_name is required")
240
+ return self.mujoco.GetMujocoInfo(
241
+ self.pb.mujoco.GetMujocoInfoRequest(robot_name=robot_name),
242
+ timeout=timeout,
243
+ )
244
+
245
+ def get_agent_schema(self, agent_name: str = "", timeout: Optional[float] = None):
246
+ """Get agent schema (observation/action sizes and names).
247
+
248
+ The schema is cached for subsequent get_observation() calls to enable
249
+ named access to observation values.
250
+
251
+ Args:
252
+ agent_name: Agent name (empty = default agent).
253
+ timeout: RPC timeout.
254
+
255
+ Returns:
256
+ GetAgentSchemaResponse with schema containing observation_names,
257
+ action_names, observation_size, and action_size.
258
+ """
259
+ timeout = timeout or self.timeout
260
+ resp = self.agent.GetAgentSchema(
261
+ self.pb.agent.GetAgentSchemaRequest(agent_name=agent_name),
262
+ timeout=timeout,
263
+ )
264
+
265
+ # Cache the schema for named observation access
266
+ schema = getattr(resp, "schema", None)
267
+ if schema is not None:
268
+ cache_key = agent_name or "agent_0"
269
+ obs_names = list(schema.observation_names) if schema.observation_names else []
270
+ action_names = list(schema.action_names) if schema.action_names else []
271
+ self._schema_cache[cache_key] = (obs_names, action_names)
272
+ logger.debug(
273
+ "Cached schema for %s: %d obs names, %d action names",
274
+ cache_key,
275
+ len(obs_names),
276
+ len(action_names),
277
+ )
278
+
279
+ return resp
280
+
281
+ def get_observation(
282
+ self,
283
+ agent_name: str = "",
284
+ timeout: Optional[float] = None,
285
+ ) -> ObservationResponse:
286
+ """
287
+ Get the RL observation vector for an agent.
288
+
289
+ Args:
290
+ agent_name: Agent name (empty = default agent).
291
+ timeout: RPC timeout.
292
+
293
+ Returns:
294
+ ObservationResponse with observation vector, actions, timestamp.
295
+ """
296
+ timeout = timeout or self.timeout
297
+
298
+ resolved_robot_name = self._robot_name
299
+ if not resolved_robot_name:
300
+ raise ValueError(
301
+ "robot_name is required (set it once via "
302
+ "LuckyEngineClient(robot_name=...) / client.set_robot_name(...))."
303
+ )
304
+
305
+ resp = self.agent.GetObservation(
306
+ self.pb.agent.GetObservationRequest(
307
+ robot_name=resolved_robot_name,
308
+ agent_name=agent_name,
309
+ include_joint_state=False,
310
+ include_agent_frame=True,
311
+ include_telemetry=False,
312
+ ),
313
+ timeout=timeout,
314
+ )
315
+
316
+ agent_frame = getattr(resp, "agent_frame", None)
317
+ observations = []
318
+ actions = []
319
+ timestamp_ms = getattr(resp, "timestamp_ms", 0)
320
+ frame_number = getattr(resp, "frame_number", 0)
321
+
322
+ if agent_frame is not None:
323
+ observations = list(agent_frame.observations) if agent_frame.observations else []
324
+ actions = list(agent_frame.actions) if agent_frame.actions else []
325
+ timestamp_ms = getattr(agent_frame, "timestamp_ms", timestamp_ms)
326
+ frame_number = getattr(agent_frame, "frame_number", frame_number)
327
+
328
+ cache_key = agent_name or "agent_0"
329
+ obs_names, action_names = self._schema_cache.get(cache_key, (None, None))
330
+
331
+ return ObservationResponse(
332
+ observation=observations,
333
+ actions=actions,
334
+ timestamp_ms=timestamp_ms,
335
+ frame_number=frame_number,
336
+ agent_name=cache_key,
337
+ observation_names=obs_names,
338
+ action_names=action_names,
339
+ )
340
+
341
+ def reset_agent(
342
+ self,
343
+ agent_name: str = "",
344
+ randomization_cfg: Optional[Any] = None,
345
+ timeout: Optional[float] = None,
346
+ ):
347
+ """
348
+ Reset a specific agent.
349
+
350
+ Args:
351
+ agent_name: Agent logical name. Empty string means default agent.
352
+ randomization_cfg: Optional domain randomization config for this reset.
353
+ timeout: Timeout in seconds (uses default if None).
354
+
355
+ Returns:
356
+ ResetAgentResponse with success and message fields.
357
+ """
358
+ timeout = timeout or self.timeout
359
+
360
+ request_kwargs = {"agent_name": agent_name}
361
+
362
+ if randomization_cfg is not None:
363
+ randomization_proto = self._randomization_to_proto(randomization_cfg)
364
+ request_kwargs["dr_config"] = randomization_proto
365
+
366
+ return self.agent.ResetAgent(
367
+ self.pb.agent.ResetAgentRequest(**request_kwargs),
368
+ timeout=timeout,
369
+ )
370
+
371
+ def step(
372
+ self,
373
+ actions: list[float],
374
+ agent_name: str = "",
375
+ timeout_ms: int = 0,
376
+ timeout: Optional[float] = None,
377
+ ) -> ObservationResponse:
378
+ """
379
+ Synchronous RL step: apply action, wait for physics, return observation.
380
+
381
+ Args:
382
+ actions: Action vector to apply for this step.
383
+ agent_name: Agent name (empty = default agent).
384
+ timeout_ms: Server-side timeout for the step in milliseconds.
385
+ timeout: RPC timeout in seconds.
386
+
387
+ Returns:
388
+ ObservationResponse with observation after physics step.
389
+ """
390
+ timeout = timeout or self.timeout
391
+
392
+ resp = self.agent.Step(
393
+ self.pb.agent.StepRequest(
394
+ agent_name=agent_name,
395
+ actions=actions,
396
+ timeout_ms=timeout_ms,
397
+ ),
398
+ timeout=timeout,
399
+ )
400
+
401
+ if not resp.success:
402
+ raise RuntimeError(f"Step failed: {resp.message}")
403
+
404
+ agent_frame = resp.observation
405
+ observations = list(agent_frame.observations) if agent_frame.observations else []
406
+ actions_out = list(agent_frame.actions) if agent_frame.actions else []
407
+ timestamp_ms = getattr(agent_frame, "timestamp_ms", 0)
408
+ frame_number = getattr(agent_frame, "frame_number", 0)
409
+
410
+ cache_key = agent_name or "agent_0"
411
+ obs_names, action_names = self._schema_cache.get(cache_key, (None, None))
412
+
413
+ return ObservationResponse(
414
+ observation=observations,
415
+ actions=actions_out,
416
+ timestamp_ms=timestamp_ms,
417
+ frame_number=frame_number,
418
+ agent_name=cache_key,
419
+ observation_names=obs_names,
420
+ action_names=action_names,
421
+ )
422
+
423
+ def set_simulation_mode(
424
+ self,
425
+ mode: str = "fast",
426
+ timeout: Optional[float] = None,
427
+ ):
428
+ """
429
+ Set simulation timing mode.
430
+
431
+ Args:
432
+ mode: "realtime", "deterministic", or "fast"
433
+ - realtime: Physics runs at 1x wall-clock speed
434
+ - deterministic: Physics runs at fixed rate
435
+ - fast: Physics runs as fast as possible (for RL training)
436
+ timeout: RPC timeout in seconds.
437
+
438
+ Returns:
439
+ SetSimulationModeResponse with success and current mode.
440
+ """
441
+ timeout = timeout or self.timeout
442
+
443
+ mode_map = {
444
+ "realtime": 0,
445
+ "deterministic": 1,
446
+ "fast": 2,
447
+ }
448
+ mode_value = mode_map.get(mode.lower(), 2)
449
+
450
+ return self.scene.SetSimulationMode(
451
+ self.pb.scene.SetSimulationModeRequest(mode=mode_value),
452
+ timeout=timeout,
453
+ )
454
+
455
+ def _randomization_to_proto(self, randomization_cfg: Any):
456
+ """Convert domain randomization config to proto message."""
457
+ proto_kwargs = {}
458
+
459
+ def get_val(name: str, default=None):
460
+ val = getattr(randomization_cfg, name, default)
461
+ if val is None or (isinstance(val, (tuple, list)) and len(val) == 0):
462
+ return None
463
+ return val
464
+
465
+ # Initial state randomization
466
+ pose_pos = get_val("pose_position_noise")
467
+ if pose_pos is not None:
468
+ proto_kwargs["pose_position_noise"] = list(pose_pos)
469
+
470
+ pose_ori = get_val("pose_orientation_noise")
471
+ if pose_ori is not None and pose_ori != 0.0:
472
+ proto_kwargs["pose_orientation_noise"] = pose_ori
473
+
474
+ joint_pos = get_val("joint_position_noise")
475
+ if joint_pos is not None and joint_pos != 0.0:
476
+ proto_kwargs["joint_position_noise"] = joint_pos
477
+
478
+ joint_vel = get_val("joint_velocity_noise")
479
+ if joint_vel is not None and joint_vel != 0.0:
480
+ proto_kwargs["joint_velocity_noise"] = joint_vel
481
+
482
+ # Physics parameters
483
+ friction = get_val("friction_range")
484
+ if friction is not None:
485
+ proto_kwargs["friction_range"] = list(friction)
486
+
487
+ restitution = get_val("restitution_range")
488
+ if restitution is not None:
489
+ proto_kwargs["restitution_range"] = list(restitution)
490
+
491
+ mass_scale = get_val("mass_scale_range")
492
+ if mass_scale is not None:
493
+ proto_kwargs["mass_scale_range"] = list(mass_scale)
494
+
495
+ com_offset = get_val("com_offset_range")
496
+ if com_offset is not None:
497
+ proto_kwargs["com_offset_range"] = list(com_offset)
498
+
499
+ # Motor/actuator
500
+ motor_strength = get_val("motor_strength_range")
501
+ if motor_strength is not None:
502
+ proto_kwargs["motor_strength_range"] = list(motor_strength)
503
+
504
+ motor_offset = get_val("motor_offset_range")
505
+ if motor_offset is not None:
506
+ proto_kwargs["motor_offset_range"] = list(motor_offset)
507
+
508
+ # External disturbances
509
+ push_interval = get_val("push_interval_range")
510
+ if push_interval is not None:
511
+ proto_kwargs["push_interval_range"] = list(push_interval)
512
+
513
+ push_velocity = get_val("push_velocity_range")
514
+ if push_velocity is not None:
515
+ proto_kwargs["push_velocity_range"] = list(push_velocity)
516
+
517
+ # Terrain
518
+ terrain_type = get_val("terrain_type")
519
+ if terrain_type is not None and terrain_type != "":
520
+ proto_kwargs["terrain_type"] = terrain_type
521
+
522
+ terrain_diff = get_val("terrain_difficulty")
523
+ if terrain_diff is not None and terrain_diff != 0.0:
524
+ proto_kwargs["terrain_difficulty"] = terrain_diff
525
+
526
+ return self.pb.agent.DomainRandomizationConfig(**proto_kwargs)