kscale 0.0.2__tar.gz → 0.0.5__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. {kscale-0.0.2/kscale.egg-info → kscale-0.0.5}/PKG-INFO +34 -4
  2. {kscale-0.0.2 → kscale-0.0.5}/README.md +29 -0
  3. kscale-0.0.5/kscale/__init__.py +5 -0
  4. kscale-0.0.5/kscale/api.py +14 -0
  5. {kscale-0.0.2 → kscale-0.0.5}/kscale/conf.py +1 -1
  6. kscale-0.0.5/kscale/requirements.txt +9 -0
  7. kscale-0.0.5/kscale/store/api.py +20 -0
  8. {kscale-0.0.2 → kscale-0.0.5}/kscale/store/cli.py +9 -4
  9. kscale-0.0.5/kscale/store/client.py +79 -0
  10. kscale-0.0.5/kscale/store/gen/__init__.py +0 -0
  11. {kscale-0.0.2 → kscale-0.0.5}/kscale/store/gen/api.py +86 -52
  12. {kscale-0.0.2 → kscale-0.0.5}/kscale/store/pybullet.py +9 -10
  13. kscale-0.0.5/kscale/store/urdf.py +176 -0
  14. kscale-0.0.5/kscale/store/utils.py +19 -0
  15. kscale-0.0.5/kscale/utils/__init__.py +0 -0
  16. kscale-0.0.5/kscale/utils/api_base.py +6 -0
  17. {kscale-0.0.2 → kscale-0.0.5/kscale.egg-info}/PKG-INFO +34 -4
  18. {kscale-0.0.2 → kscale-0.0.5}/kscale.egg-info/SOURCES.txt +6 -1
  19. kscale-0.0.5/kscale.egg-info/entry_points.txt +2 -0
  20. kscale-0.0.5/kscale.egg-info/requires.txt +12 -0
  21. {kscale-0.0.2 → kscale-0.0.5}/setup.cfg +1 -1
  22. {kscale-0.0.2 → kscale-0.0.5}/setup.py +1 -0
  23. kscale-0.0.2/kscale/__init__.py +0 -1
  24. kscale-0.0.2/kscale/formats/mjcf.py +0 -509
  25. kscale-0.0.2/kscale/requirements.txt +0 -8
  26. kscale-0.0.2/kscale/store/__init__.py +0 -1
  27. kscale-0.0.2/kscale/store/urdf.py +0 -174
  28. kscale-0.0.2/kscale.egg-info/entry_points.txt +0 -2
  29. kscale-0.0.2/kscale.egg-info/requires.txt +0 -11
  30. {kscale-0.0.2 → kscale-0.0.5}/LICENSE +0 -0
  31. {kscale-0.0.2 → kscale-0.0.5}/MANIFEST.in +0 -0
  32. {kscale-0.0.2 → kscale-0.0.5}/kscale/py.typed +0 -0
  33. {kscale-0.0.2 → kscale-0.0.5}/kscale/requirements-dev.txt +0 -0
  34. {kscale-0.0.2/kscale/store/gen → kscale-0.0.5/kscale/store}/__init__.py +0 -0
  35. {kscale-0.0.2 → kscale-0.0.5}/kscale/store/bullet/MANIFEST.in +0 -0
  36. {kscale-0.0.2 → kscale-0.0.5}/kscale.egg-info/dependency_links.txt +0 -0
  37. {kscale-0.0.2 → kscale-0.0.5}/kscale.egg-info/top_level.txt +0 -0
  38. {kscale-0.0.2 → kscale-0.0.5}/pyproject.toml +0 -0
  39. {kscale-0.0.2 → kscale-0.0.5}/tests/test_dummy.py +0 -0
@@ -1,15 +1,16 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kscale
3
- Version: 0.0.2
3
+ Version: 0.0.5
4
4
  Summary: The kscale project
5
5
  Home-page: https://github.com/kscalelabs/kscale
6
6
  Author: Benjamin Bolte
7
7
  Requires-Python: >=3.11
8
8
  Description-Content-Type: text/markdown
9
9
  License-File: LICENSE
10
- Requires-Dist: omegaconf
11
- Requires-Dist: httpx
12
- Requires-Dist: requests
10
+ Requires-Dist: omegaconf==2.3.0
11
+ Requires-Dist: email_validator==2.2.0
12
+ Requires-Dist: httpx==0.27.0
13
+ Requires-Dist: requests==2.32.2
13
14
  Provides-Extra: dev
14
15
  Requires-Dist: black; extra == "dev"
15
16
  Requires-Dist: darglint; extra == "dev"
@@ -50,3 +51,32 @@ This is a command line tool for interacting with various services provided by K-
50
51
  ```bash
51
52
  pip install kscale
52
53
  ```
54
+
55
+ ## Usage
56
+
57
+ ### CLI
58
+
59
+ Download a URDF from the K-Scale Store:
60
+
61
+ ```bash
62
+ kscale urdf download <artifact_id>
63
+ ```
64
+
65
+ Upload a URDF to the K-Scale Store:
66
+
67
+ ```bash
68
+ kscale urdf upload <artifact_id> <root_dir>
69
+ ```
70
+
71
+ ### Python API
72
+
73
+ Reference a URDF by ID from the K-Scale Store:
74
+
75
+ ```python
76
+ from kscale import KScale
77
+
78
+ async def main():
79
+ kscale = KScale()
80
+ urdf_dir_path = await kscale.store.urdf("123456")
81
+ print(urdf_dir_path)
82
+ ```
@@ -30,3 +30,32 @@ This is a command line tool for interacting with various services provided by K-
30
30
  ```bash
31
31
  pip install kscale
32
32
  ```
33
+
34
+ ## Usage
35
+
36
+ ### CLI
37
+
38
+ Download a URDF from the K-Scale Store:
39
+
40
+ ```bash
41
+ kscale urdf download <artifact_id>
42
+ ```
43
+
44
+ Upload a URDF to the K-Scale Store:
45
+
46
+ ```bash
47
+ kscale urdf upload <artifact_id> <root_dir>
48
+ ```
49
+
50
+ ### Python API
51
+
52
+ Reference a URDF by ID from the K-Scale Store:
53
+
54
+ ```python
55
+ from kscale import KScale
56
+
57
+ async def main():
58
+ kscale = KScale()
59
+ urdf_dir_path = await kscale.store.urdf("123456")
60
+ print(urdf_dir_path)
61
+ ```
@@ -0,0 +1,5 @@
1
+ """Defines the common interface for the K-Scale Python API."""
2
+
3
+ __version__ = "0.0.5"
4
+
5
+ from kscale.api import KScale
@@ -0,0 +1,14 @@
1
+ """Defines common functionality for the K-Scale API."""
2
+
3
+ from kscale.store.api import StoreAPI
4
+ from kscale.utils.api_base import APIBase
5
+
6
+
7
+ class KScale(
8
+ StoreAPI,
9
+ APIBase,
10
+ ):
11
+ """Defines a common interface for the K-Scale API."""
12
+
13
+ def __init__(self, api_key: str | None = None) -> None:
14
+ self.api_key = api_key
@@ -17,7 +17,7 @@ def get_path() -> Path:
17
17
 
18
18
  @dataclass
19
19
  class StoreSettings:
20
- api_key: str = field(default=II("oc.env:KSCALE_API_KEY,"))
20
+ api_key: str | None = field(default=None)
21
21
  cache_dir: str = field(default=II("oc.env:KSCALE_CACHE_DIR,'~/.kscale/cache/'"))
22
22
 
23
23
 
@@ -0,0 +1,9 @@
1
+ # requirements.txt
2
+
3
+ # Configuration
4
+ omegaconf==2.3.0
5
+ email_validator==2.2.0
6
+
7
+ # HTTP requests
8
+ httpx==0.27.0
9
+ requests==2.32.2
@@ -0,0 +1,20 @@
1
+ """Defines a common interface for the K-Scale Store API."""
2
+
3
+ from pathlib import Path
4
+
5
+ from kscale.store.urdf import download_urdf
6
+ from kscale.utils.api_base import APIBase
7
+
8
+
9
+ class StoreAPI(APIBase):
10
+ def __init__(
11
+ self,
12
+ *,
13
+ api_key: str | None = None,
14
+ ) -> None:
15
+ super().__init__()
16
+
17
+ self.api_key = api_key
18
+
19
+ async def urdf(self, artifact_id: str) -> Path:
20
+ return await download_urdf(artifact_id)
@@ -1,12 +1,13 @@
1
1
  """Defines the top-level KOL CLI."""
2
2
 
3
3
  import argparse
4
+ import asyncio
4
5
  from typing import Sequence
5
6
 
6
7
  from kscale.store import pybullet, urdf
7
8
 
8
9
 
9
- def main(args: Sequence[str] | None = None) -> None:
10
+ async def main(args: Sequence[str] | None = None) -> None:
10
11
  parser = argparse.ArgumentParser(description="K-Scale OnShape Library", add_help=False)
11
12
  parser.add_argument(
12
13
  "subcommand",
@@ -20,11 +21,15 @@ def main(args: Sequence[str] | None = None) -> None:
20
21
 
21
22
  match parsed_args.subcommand:
22
23
  case "urdf":
23
- urdf.main(remaining_args)
24
+ await urdf.main(remaining_args)
24
25
  case "pybullet":
25
- pybullet.main(remaining_args)
26
+ await pybullet.main(remaining_args)
27
+
28
+
29
+ def sync_main(args: Sequence[str] | None = None) -> None:
30
+ asyncio.run(main(args))
26
31
 
27
32
 
28
33
  if __name__ == "__main__":
29
34
  # python3 -m kscale.store.cli
30
- main()
35
+ sync_main()
@@ -0,0 +1,79 @@
1
+ """Defines a typed client for the K-Scale Store API."""
2
+
3
+ import logging
4
+ from types import TracebackType
5
+ from typing import Any, Dict, Type
6
+ from urllib.parse import urljoin
7
+
8
+ import httpx
9
+ from pydantic import BaseModel
10
+
11
+ from kscale.store.gen.api import (
12
+ NewListingRequest,
13
+ NewListingResponse,
14
+ SingleArtifactResponse,
15
+ UploadArtifactResponse,
16
+ )
17
+ from kscale.store.utils import API_ROOT, get_api_key
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class KScaleStoreClient:
23
+ def __init__(self, base_url: str = API_ROOT) -> None:
24
+ self.base_url = base_url
25
+ self.client = httpx.AsyncClient(
26
+ base_url=self.base_url,
27
+ headers={"Authorization": f"Bearer {get_api_key()}"},
28
+ )
29
+
30
+ async def _request(
31
+ self,
32
+ method: str,
33
+ endpoint: str,
34
+ *,
35
+ params: Dict[str, Any] | None = None,
36
+ data: BaseModel | None = None,
37
+ files: Dict[str, Any] | None = None,
38
+ ) -> Dict[str, Any]:
39
+ url = urljoin(self.base_url, endpoint)
40
+ kwargs: Dict[str, Any] = {"params": params}
41
+
42
+ if data:
43
+ kwargs["json"] = data.dict(exclude_unset=True)
44
+ if files:
45
+ kwargs["files"] = files
46
+
47
+ response = await self.client.request(method, url, **kwargs)
48
+ if response.is_error:
49
+ logger.error(f"Error response from K-Scale Store: {response.text}")
50
+ response.raise_for_status()
51
+ return response.json()
52
+
53
+ async def get_artifact_info(self, artifact_id: str) -> SingleArtifactResponse:
54
+ data = await self._request("GET", f"/artifacts/info/{artifact_id}")
55
+ return SingleArtifactResponse(**data)
56
+
57
+ async def upload_artifact(self, listing_id: str, file_path: str) -> UploadArtifactResponse:
58
+ with open(file_path, "rb") as f:
59
+ files = {"files": (f.name, f, "application/gzip")}
60
+ data = await self._request("POST", f"/artifacts/upload/{listing_id}", files=files)
61
+ return UploadArtifactResponse(**data)
62
+
63
+ async def create_listing(self, request: NewListingRequest) -> NewListingResponse:
64
+ data = await self._request("POST", "/listings", data=request)
65
+ return NewListingResponse(**data)
66
+
67
+ async def close(self) -> None:
68
+ await self.client.aclose()
69
+
70
+ async def __aenter__(self) -> "KScaleStoreClient":
71
+ return self
72
+
73
+ async def __aexit__(
74
+ self,
75
+ exc_type: Type[BaseException] | None,
76
+ exc_val: BaseException | None,
77
+ exc_tb: TracebackType | None,
78
+ ) -> None:
79
+ await self.close()
File without changes
@@ -2,12 +2,12 @@
2
2
 
3
3
  # generated by datamodel-codegen:
4
4
  # filename: openapi.json
5
- # timestamp: 2024-08-19T06:07:36+00:00
5
+ # timestamp: 2024-09-04T04:33:58+00:00
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
9
  from enum import Enum
10
- from typing import List, Optional, Union
10
+ from typing import Dict, List, Optional, Union
11
11
 
12
12
  from pydantic import BaseModel, EmailStr, Field
13
13
 
@@ -21,8 +21,9 @@ class AuthResponse(BaseModel):
21
21
  api_key: str = Field(..., title="Api Key")
22
22
 
23
23
 
24
- class BodySetUrdfUrdfUploadListingIdPost(BaseModel):
25
- file: bytes = Field(..., title="File")
24
+ class BodyPullOnshapeDocumentOnshapePullListingIdGet(BaseModel):
25
+ suffix_to_joint_effort: Optional[Dict[str, float]] = Field(None, title="Suffix To Joint Effort")
26
+ suffix_to_joint_velocity: Optional[Dict[str, float]] = Field(None, title="Suffix To Joint Velocity")
26
27
 
27
28
 
28
29
  class BodyUploadArtifactsUploadListingIdPost(BaseModel):
@@ -51,7 +52,13 @@ class GetListingResponse(BaseModel):
51
52
  description: Optional[str] = Field(..., title="Description")
52
53
  child_ids: List[str] = Field(..., title="Child Ids")
53
54
  tags: List[str] = Field(..., title="Tags")
55
+ onshape_url: Optional[str] = Field(..., title="Onshape Url")
54
56
  can_edit: bool = Field(..., title="Can Edit")
57
+ created_at: int = Field(..., title="Created At")
58
+ views: int = Field(..., title="Views")
59
+ score: int = Field(..., title="Score")
60
+ user_vote: Optional[bool] = Field(..., title="User Vote")
61
+ creator_id: str = Field(..., title="Creator Id")
55
62
 
56
63
 
57
64
  class GetTokenResponse(BaseModel):
@@ -82,42 +89,6 @@ class KeysResponseItem(BaseModel):
82
89
  permissions: Optional[List[Permission]] = Field(..., title="Permissions")
83
90
 
84
91
 
85
- class ArtifactType(Enum):
86
- image = "image"
87
-
88
-
89
- class ArtifactType1(Enum):
90
- urdf = "urdf"
91
- mjcf = "mjcf"
92
-
93
-
94
- class ArtifactType2(Enum):
95
- stl = "stl"
96
- obj = "obj"
97
- dae = "dae"
98
- ply = "ply"
99
-
100
-
101
- class ArtifactType3(Enum):
102
- tgz = "tgz"
103
- zip = "zip"
104
-
105
-
106
- class ListArtifactsItem(BaseModel):
107
- artifact_id: str = Field(..., title="Artifact Id")
108
- listing_id: str = Field(..., title="Listing Id")
109
- name: str = Field(..., title="Name")
110
- artifact_type: Union[ArtifactType, ArtifactType1, ArtifactType2, ArtifactType3] = Field(..., title="Artifact Type")
111
- description: Optional[str] = Field(..., title="Description")
112
- timestamp: int = Field(..., title="Timestamp")
113
- urls: ArtifactUrls
114
- is_new: Optional[bool] = Field(None, title="Is New")
115
-
116
-
117
- class ListArtifactsResponse(BaseModel):
118
- artifacts: List[ListArtifactsItem] = Field(..., title="Artifacts")
119
-
120
-
121
92
  class ListKeysResponse(BaseModel):
122
93
  keys: List[KeysResponseItem] = Field(..., title="Keys")
123
94
 
@@ -130,9 +101,16 @@ class ListListingsResponse(BaseModel):
130
101
  class Listing(BaseModel):
131
102
  id: str = Field(..., title="Id")
132
103
  user_id: str = Field(..., title="User Id")
104
+ created_at: int = Field(..., title="Created At")
105
+ updated_at: int = Field(..., title="Updated At")
133
106
  name: str = Field(..., title="Name")
134
107
  child_ids: List[str] = Field(..., title="Child Ids")
135
- description: Optional[str] = Field(..., title="Description")
108
+ description: Optional[str] = Field(None, title="Description")
109
+ onshape_url: Optional[str] = Field(None, title="Onshape Url")
110
+ views: Optional[int] = Field(0, title="Views")
111
+ upvotes: Optional[int] = Field(0, title="Upvotes")
112
+ downvotes: Optional[int] = Field(0, title="Downvotes")
113
+ score: Optional[int] = Field(0, title="Score")
136
114
 
137
115
 
138
116
  class ListingInfoResponse(BaseModel):
@@ -141,6 +119,11 @@ class ListingInfoResponse(BaseModel):
141
119
  description: Optional[str] = Field(..., title="Description")
142
120
  child_ids: List[str] = Field(..., title="Child Ids")
143
121
  image_url: Optional[str] = Field(..., title="Image Url")
122
+ onshape_url: Optional[str] = Field(..., title="Onshape Url")
123
+ created_at: int = Field(..., title="Created At")
124
+ views: int = Field(..., title="Views")
125
+ score: int = Field(..., title="Score")
126
+ user_vote: Optional[bool] = Field(..., title="User Vote")
144
127
 
145
128
 
146
129
  class LoginRequest(BaseModel):
@@ -163,6 +146,10 @@ class MyUserInfoResponse(BaseModel):
163
146
  github_id: Optional[str] = Field(..., title="Github Id")
164
147
  google_id: Optional[str] = Field(..., title="Google Id")
165
148
  permissions: Optional[List[Permission1]] = Field(..., title="Permissions")
149
+ first_name: Optional[str] = Field(..., title="First Name")
150
+ last_name: Optional[str] = Field(..., title="Last Name")
151
+ name: Optional[str] = Field(..., title="Name")
152
+ bio: Optional[str] = Field(..., title="Bio")
166
153
 
167
154
 
168
155
  class NewKeyRequest(BaseModel):
@@ -200,6 +187,48 @@ class PublicUsersInfoResponse(BaseModel):
200
187
  users: List[PublicUserInfoResponseItem] = Field(..., title="Users")
201
188
 
202
189
 
190
+ class SetRequest(BaseModel):
191
+ onshape_url: Optional[str] = Field(..., title="Onshape Url")
192
+
193
+
194
+ class ArtifactType(Enum):
195
+ image = "image"
196
+
197
+
198
+ class ArtifactType1(Enum):
199
+ urdf = "urdf"
200
+ mjcf = "mjcf"
201
+
202
+
203
+ class ArtifactType2(Enum):
204
+ stl = "stl"
205
+ obj = "obj"
206
+ dae = "dae"
207
+ ply = "ply"
208
+
209
+
210
+ class ArtifactType3(Enum):
211
+ tgz = "tgz"
212
+ zip = "zip"
213
+
214
+
215
+ class SingleArtifactResponse(BaseModel):
216
+ artifact_id: str = Field(..., title="Artifact Id")
217
+ listing_id: str = Field(..., title="Listing Id")
218
+ name: str = Field(..., title="Name")
219
+ artifact_type: Union[ArtifactType, ArtifactType1, ArtifactType2, ArtifactType3] = Field(..., title="Artifact Type")
220
+ description: Optional[str] = Field(..., title="Description")
221
+ timestamp: int = Field(..., title="Timestamp")
222
+ urls: ArtifactUrls
223
+ is_new: Optional[bool] = Field(None, title="Is New")
224
+
225
+
226
+ class SortOption(Enum):
227
+ newest = "newest"
228
+ most_viewed = "most_viewed"
229
+ most_upvoted = "most_upvoted"
230
+
231
+
203
232
  class UpdateArtifactRequest(BaseModel):
204
233
  name: Optional[str] = Field(None, title="Name")
205
234
  description: Optional[str] = Field(None, title="Description")
@@ -212,18 +241,19 @@ class UpdateListingRequest(BaseModel):
212
241
  tags: Optional[List[str]] = Field(None, title="Tags")
213
242
 
214
243
 
215
- class UploadArtifactResponse(BaseModel):
216
- artifacts: List[ListArtifactsItem] = Field(..., title="Artifacts")
217
-
218
-
219
- class UrdfInfo(BaseModel):
220
- artifact_id: str = Field(..., title="Artifact Id")
221
- url: str = Field(..., title="Url")
244
+ class UpdateUserRequest(BaseModel):
245
+ email: Optional[str] = Field(None, title="Email")
246
+ password: Optional[str] = Field(None, title="Password")
247
+ github_id: Optional[str] = Field(None, title="Github Id")
248
+ google_id: Optional[str] = Field(None, title="Google Id")
249
+ first_name: Optional[str] = Field(None, title="First Name")
250
+ last_name: Optional[str] = Field(None, title="Last Name")
251
+ name: Optional[str] = Field(None, title="Name")
252
+ bio: Optional[str] = Field(None, title="Bio")
222
253
 
223
254
 
224
- class UrdfResponse(BaseModel):
225
- urdf: Optional[UrdfInfo]
226
- listing_id: str = Field(..., title="Listing Id")
255
+ class UploadArtifactResponse(BaseModel):
256
+ artifacts: List[SingleArtifactResponse] = Field(..., title="Artifacts")
227
257
 
228
258
 
229
259
  class UserInfoResponseItem(BaseModel):
@@ -235,7 +265,7 @@ class UserPublic(BaseModel):
235
265
  id: str = Field(..., title="Id")
236
266
  email: str = Field(..., title="Email")
237
267
  permissions: Optional[List[Permission1]] = Field(None, title="Permissions")
238
- created_at: Optional[int] = Field(None, title="Created At")
268
+ created_at: int = Field(..., title="Created At")
239
269
  updated_at: Optional[int] = Field(None, title="Updated At")
240
270
  first_name: Optional[str] = Field(None, title="First Name")
241
271
  last_name: Optional[str] = Field(None, title="Last Name")
@@ -265,3 +295,7 @@ class GetBatchListingsResponse(BaseModel):
265
295
 
266
296
  class HTTPValidationError(BaseModel):
267
297
  detail: Optional[List[ValidationError]] = Field(None, title="Detail")
298
+
299
+
300
+ class ListArtifactsResponse(BaseModel):
301
+ artifacts: List[SingleArtifactResponse] = Field(..., title="Artifacts")
@@ -1,6 +1,7 @@
1
1
  """Simple script to interact with a URDF in PyBullet."""
2
2
 
3
3
  import argparse
4
+ import asyncio
4
5
  import itertools
5
6
  import logging
6
7
  import math
@@ -8,12 +9,14 @@ import time
8
9
  from pathlib import Path
9
10
  from typing import Sequence
10
11
 
12
+ from kscale.store.urdf import download_urdf
13
+
11
14
  logger = logging.getLogger(__name__)
12
15
 
13
16
 
14
- def main(args: Sequence[str] | None = None) -> None:
17
+ async def main(args: Sequence[str] | None = None) -> None:
15
18
  parser = argparse.ArgumentParser(description="Show a URDF in PyBullet")
16
- parser.add_argument("urdf", nargs="?", help="Path to the URDF file")
19
+ parser.add_argument("listing_id", help="Listing ID for the URDF")
17
20
  parser.add_argument("--dt", type=float, default=0.01, help="Time step")
18
21
  parser.add_argument("-n", "--hide-gui", action="store_true", help="Hide the GUI")
19
22
  parser.add_argument("--no-merge", action="store_true", help="Do not merge fixed links")
@@ -23,6 +26,9 @@ def main(args: Sequence[str] | None = None) -> None:
23
26
  parser.add_argument("--show-collision", action="store_true", help="Show collision meshes")
24
27
  parsed_args = parser.parse_args(args)
25
28
 
29
+ # Gets the URDF path.
30
+ urdf_path = await download_urdf(parsed_args.listing_id)
31
+
26
32
  try:
27
33
  import pybullet as p # type: ignore[import-not-found]
28
34
  except ImportError:
@@ -46,13 +52,6 @@ def main(args: Sequence[str] | None = None) -> None:
46
52
  # Loads the floor plane.
47
53
  floor = p.loadURDF(str((Path(__file__).parent / "bullet" / "plane.urdf").resolve()))
48
54
 
49
- urdf_path = Path("robot" if parsed_args.urdf is None else parsed_args.urdf)
50
- if urdf_path.is_dir():
51
- try:
52
- urdf_path = next(urdf_path.glob("*.urdf"))
53
- except StopIteration:
54
- raise FileNotFoundError(f"No URDF files found in {urdf_path}")
55
-
56
55
  # Load the robot URDF.
57
56
  start_position = [0.0, 0.0, 1.0]
58
57
  start_orientation = p.getQuaternionFromEuler([0.0, 0.0, 0.0])
@@ -175,4 +174,4 @@ def main(args: Sequence[str] | None = None) -> None:
175
174
 
176
175
  if __name__ == "__main__":
177
176
  # python -m kscale.store.pybullet
178
- main()
177
+ asyncio.run(main())