kscale 0.0.13__cp311-cp311-macosx_11_0_arm64.whl
Sign up to get free protection for your applications and to get access to all the features.
- kscale/__init__.py +9 -0
- kscale/api.py +14 -0
- kscale/artifacts/__init__.py +8 -0
- kscale/artifacts/plane.obj +18 -0
- kscale/artifacts/plane.urdf +28 -0
- kscale/cli.py +25 -0
- kscale/conf.py +45 -0
- kscale/py.typed +0 -0
- kscale/requirements-dev.txt +11 -0
- kscale/requirements.txt +17 -0
- kscale/rust.cpython-311-darwin.so +0 -0
- kscale/utils/__init__.py +0 -0
- kscale/utils/api_base.py +6 -0
- kscale/utils/checksum.py +41 -0
- kscale/utils/cli.py +28 -0
- kscale/web/__init__.py +0 -0
- kscale/web/api.py +98 -0
- kscale/web/gen/__init__.py +0 -0
- kscale/web/gen/api.py +612 -0
- kscale/web/kernels.py +207 -0
- kscale/web/krec.py +175 -0
- kscale/web/pybullet.py +188 -0
- kscale/web/urdf.py +185 -0
- kscale/web/utils.py +48 -0
- kscale/web/www_client.py +134 -0
- kscale-0.0.13.dist-info/LICENSE +21 -0
- kscale-0.0.13.dist-info/METADATA +55 -0
- kscale-0.0.13.dist-info/RECORD +31 -0
- kscale-0.0.13.dist-info/WHEEL +5 -0
- kscale-0.0.13.dist-info/entry_points.txt +2 -0
- kscale-0.0.13.dist-info/top_level.txt +1 -0
kscale/web/urdf.py
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
"""Utility functions for managing artifacts in K-Scale WWW."""
|
2
|
+
|
3
|
+
import logging
|
4
|
+
import shutil
|
5
|
+
import tarfile
|
6
|
+
from pathlib import Path
|
7
|
+
|
8
|
+
import click
|
9
|
+
import httpx
|
10
|
+
import requests
|
11
|
+
|
12
|
+
from kscale.utils.cli import coro
|
13
|
+
from kscale.web.gen.api import SingleArtifactResponse, UploadArtifactResponse
|
14
|
+
from kscale.web.utils import get_api_key, get_artifact_dir, get_cache_dir
|
15
|
+
from kscale.web.www_client import KScaleWWWClient
|
16
|
+
|
17
|
+
# Set up logging
|
18
|
+
logging.basicConfig(level=logging.INFO)
|
19
|
+
logger = logging.getLogger(__name__)
|
20
|
+
|
21
|
+
ALLOWED_SUFFIXES = {
|
22
|
+
".urdf",
|
23
|
+
".mjcf",
|
24
|
+
".stl",
|
25
|
+
".obj",
|
26
|
+
".dae",
|
27
|
+
".png",
|
28
|
+
".jpg",
|
29
|
+
".jpeg",
|
30
|
+
}
|
31
|
+
|
32
|
+
|
33
|
+
async def fetch_urdf_info(artifact_id: str, cache_dir: Path) -> SingleArtifactResponse:
|
34
|
+
response_path = cache_dir / "response.json"
|
35
|
+
if response_path.exists():
|
36
|
+
return SingleArtifactResponse.model_validate_json(response_path.read_text())
|
37
|
+
async with KScaleWWWClient() as client:
|
38
|
+
response = await client.get_artifact_info(artifact_id)
|
39
|
+
response_path.write_text(response.model_dump_json())
|
40
|
+
return response
|
41
|
+
|
42
|
+
|
43
|
+
async def download_artifact(artifact_url: str, cache_dir: Path) -> Path:
|
44
|
+
filename = cache_dir / Path(artifact_url).name
|
45
|
+
headers = {
|
46
|
+
"Authorization": f"Bearer {get_api_key()}",
|
47
|
+
}
|
48
|
+
|
49
|
+
if not filename.exists():
|
50
|
+
logger.info("Downloading artifact from %s", artifact_url)
|
51
|
+
|
52
|
+
async with httpx.AsyncClient() as client:
|
53
|
+
response = await client.get(artifact_url, headers=headers)
|
54
|
+
response.raise_for_status()
|
55
|
+
filename.write_bytes(response.content)
|
56
|
+
logger.info("Artifact downloaded to %s", filename)
|
57
|
+
else:
|
58
|
+
logger.info("Artifact already cached at %s", filename)
|
59
|
+
|
60
|
+
# Extract the .tgz file
|
61
|
+
extract_dir = cache_dir / filename.stem
|
62
|
+
if not extract_dir.exists():
|
63
|
+
logger.info("Extracting %s to %s", filename, extract_dir)
|
64
|
+
with tarfile.open(filename, "r:gz") as tar:
|
65
|
+
tar.extractall(path=extract_dir)
|
66
|
+
else:
|
67
|
+
logger.info("Artifact already extracted at %s", extract_dir)
|
68
|
+
|
69
|
+
return extract_dir
|
70
|
+
|
71
|
+
|
72
|
+
def create_tarball(folder_path: Path, output_filename: str, cache_dir: Path) -> Path:
|
73
|
+
tarball_path = cache_dir / output_filename
|
74
|
+
with tarfile.open(tarball_path, "w:gz") as tar:
|
75
|
+
for file_path in folder_path.rglob("*"):
|
76
|
+
if file_path.is_file() and file_path.suffix.lower() in ALLOWED_SUFFIXES:
|
77
|
+
tar.add(file_path, arcname=file_path.relative_to(folder_path))
|
78
|
+
logger.info("Added %s to tarball", file_path)
|
79
|
+
else:
|
80
|
+
logger.warning("Skipping %s", file_path)
|
81
|
+
logger.info("Created tarball %s", tarball_path)
|
82
|
+
return tarball_path
|
83
|
+
|
84
|
+
|
85
|
+
async def download_urdf(artifact_id: str) -> Path:
|
86
|
+
cache_dir = get_artifact_dir(artifact_id)
|
87
|
+
try:
|
88
|
+
urdf_info = await fetch_urdf_info(artifact_id, cache_dir)
|
89
|
+
artifact_url = urdf_info.urls.large
|
90
|
+
return await download_artifact(artifact_url, cache_dir)
|
91
|
+
|
92
|
+
except requests.RequestException:
|
93
|
+
logger.exception("Failed to fetch URDF info")
|
94
|
+
raise
|
95
|
+
|
96
|
+
|
97
|
+
async def show_urdf_info(artifact_id: str) -> None:
|
98
|
+
try:
|
99
|
+
urdf_info = await fetch_urdf_info(artifact_id, get_artifact_dir(artifact_id))
|
100
|
+
logger.info("URDF Artifact ID: %s", urdf_info.artifact_id)
|
101
|
+
logger.info("URDF URL: %s", urdf_info.urls.large)
|
102
|
+
except requests.RequestException:
|
103
|
+
logger.exception("Failed to fetch URDF info")
|
104
|
+
raise
|
105
|
+
|
106
|
+
|
107
|
+
async def remove_local_urdf(artifact_id: str) -> None:
|
108
|
+
try:
|
109
|
+
if artifact_id.lower() == "all":
|
110
|
+
cache_dir = get_cache_dir()
|
111
|
+
if cache_dir.exists():
|
112
|
+
logger.info("Removing all local caches at %s", cache_dir)
|
113
|
+
shutil.rmtree(cache_dir)
|
114
|
+
else:
|
115
|
+
logger.error("No local caches found")
|
116
|
+
else:
|
117
|
+
artifact_dir = get_artifact_dir(artifact_id)
|
118
|
+
if artifact_dir.exists():
|
119
|
+
logger.info("Removing local cache at %s", artifact_dir)
|
120
|
+
shutil.rmtree(artifact_dir)
|
121
|
+
else:
|
122
|
+
logger.error("No local cache found for artifact %s", artifact_id)
|
123
|
+
|
124
|
+
except Exception:
|
125
|
+
logger.error("Failed to remove local cache")
|
126
|
+
raise
|
127
|
+
|
128
|
+
|
129
|
+
async def upload_urdf(listing_id: str, root_dir: Path) -> UploadArtifactResponse:
|
130
|
+
tarball_path = create_tarball(root_dir, "robot.tgz", get_artifact_dir(listing_id))
|
131
|
+
|
132
|
+
async with KScaleWWWClient() as client:
|
133
|
+
response = await client.upload_artifact(listing_id, str(tarball_path))
|
134
|
+
|
135
|
+
logger.info("Uploaded artifacts: %s", [artifact.artifact_id for artifact in response.artifacts])
|
136
|
+
return response
|
137
|
+
|
138
|
+
|
139
|
+
async def upload_urdf_cli(listing_id: str, root_dir: Path) -> UploadArtifactResponse:
|
140
|
+
response = await upload_urdf(listing_id, root_dir)
|
141
|
+
return response
|
142
|
+
|
143
|
+
|
144
|
+
@click.group()
|
145
|
+
def cli() -> None:
|
146
|
+
"""K-Scale URDF Store CLI tool."""
|
147
|
+
pass
|
148
|
+
|
149
|
+
|
150
|
+
@cli.command()
|
151
|
+
@click.argument("artifact_id")
|
152
|
+
@coro
|
153
|
+
async def download(artifact_id: str) -> None:
|
154
|
+
"""Download a URDF artifact."""
|
155
|
+
await download_urdf(artifact_id)
|
156
|
+
|
157
|
+
|
158
|
+
@cli.command()
|
159
|
+
@click.argument("artifact_id")
|
160
|
+
@coro
|
161
|
+
async def info(artifact_id: str) -> None:
|
162
|
+
"""Show information about a URDF artifact."""
|
163
|
+
await show_urdf_info(artifact_id)
|
164
|
+
|
165
|
+
|
166
|
+
@cli.command("remove-local")
|
167
|
+
@click.argument("artifact_id")
|
168
|
+
@coro
|
169
|
+
async def remove_local(artifact_id: str) -> None:
|
170
|
+
"""Remove local cache of a URDF artifact."""
|
171
|
+
await remove_local_urdf(artifact_id)
|
172
|
+
|
173
|
+
|
174
|
+
@cli.command()
|
175
|
+
@click.argument("listing_id")
|
176
|
+
@click.argument("root_dir", type=click.Path(exists=True, path_type=Path))
|
177
|
+
@coro
|
178
|
+
async def upload(listing_id: str, root_dir: Path) -> None:
|
179
|
+
"""Upload a URDF artifact."""
|
180
|
+
await upload_urdf_cli(listing_id, root_dir)
|
181
|
+
|
182
|
+
|
183
|
+
if __name__ == "__main__":
|
184
|
+
# python -m kscale.web.urdf
|
185
|
+
cli()
|
kscale/web/utils.py
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
"""Utility functions for interacting with the K-Scale WWW API."""
|
2
|
+
|
3
|
+
import os
|
4
|
+
from pathlib import Path
|
5
|
+
|
6
|
+
from kscale.conf import Settings
|
7
|
+
|
8
|
+
DEFAULT_UPLOAD_TIMEOUT = 300.0 # 5 minutes
|
9
|
+
|
10
|
+
|
11
|
+
def get_api_root() -> str:
|
12
|
+
"""Returns the base URL for the K-Scale WWW API.
|
13
|
+
|
14
|
+
This can be overridden when targetting a different server.
|
15
|
+
|
16
|
+
Returns:
|
17
|
+
The base URL for the K-Scale WWW API.
|
18
|
+
"""
|
19
|
+
return os.getenv("KSCALE_API_ROOT", "https://api.kscale.dev")
|
20
|
+
|
21
|
+
|
22
|
+
def get_api_key() -> str:
|
23
|
+
"""Returns the API key for the K-Scale WWW API.
|
24
|
+
|
25
|
+
Returns:
|
26
|
+
The API key for the K-Scale WWW API.
|
27
|
+
"""
|
28
|
+
api_key = Settings.load().www.api_key
|
29
|
+
if api_key is None:
|
30
|
+
api_key = os.getenv("KSCALE_API_KEY")
|
31
|
+
if not api_key:
|
32
|
+
raise ValueError(
|
33
|
+
"API key not found! Get one here and set it as the `KSCALE_API_KEY` environment variable or in your "
|
34
|
+
"config file: https://kscale.dev/keys"
|
35
|
+
)
|
36
|
+
return api_key
|
37
|
+
|
38
|
+
|
39
|
+
def get_cache_dir() -> Path:
|
40
|
+
"""Returns the cache directory for artifacts."""
|
41
|
+
return Path(Settings.load().www.cache_dir).expanduser().resolve()
|
42
|
+
|
43
|
+
|
44
|
+
def get_artifact_dir(artifact_id: str) -> Path:
|
45
|
+
"""Returns the directory for a specific artifact."""
|
46
|
+
cache_dir = get_cache_dir() / artifact_id
|
47
|
+
cache_dir.mkdir(parents=True, exist_ok=True)
|
48
|
+
return cache_dir
|
kscale/web/www_client.py
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
"""Defines a typed client for the K-Scale WWW API."""
|
2
|
+
|
3
|
+
import logging
|
4
|
+
from pathlib import Path
|
5
|
+
from types import TracebackType
|
6
|
+
from typing import Any, Dict, Type
|
7
|
+
from urllib.parse import urljoin
|
8
|
+
|
9
|
+
import httpx
|
10
|
+
from pydantic import BaseModel
|
11
|
+
|
12
|
+
from kscale.web.gen.api import (
|
13
|
+
BodyAddListingListingsAddPost,
|
14
|
+
NewListingResponse,
|
15
|
+
SingleArtifactResponse,
|
16
|
+
UploadArtifactResponse,
|
17
|
+
UploadKRecRequest,
|
18
|
+
)
|
19
|
+
from kscale.web.utils import DEFAULT_UPLOAD_TIMEOUT, get_api_key, get_api_root
|
20
|
+
|
21
|
+
logger = logging.getLogger(__name__)
|
22
|
+
|
23
|
+
|
24
|
+
class KScaleWWWClient:
|
25
|
+
def __init__(self, base_url: str = get_api_root(), upload_timeout: float = DEFAULT_UPLOAD_TIMEOUT) -> None:
|
26
|
+
self.base_url = base_url
|
27
|
+
self.upload_timeout = upload_timeout
|
28
|
+
self._client: httpx.AsyncClient | None = None
|
29
|
+
|
30
|
+
@property
|
31
|
+
def client(self) -> httpx.AsyncClient:
|
32
|
+
if self._client is None:
|
33
|
+
self._client = httpx.AsyncClient(
|
34
|
+
base_url=self.base_url,
|
35
|
+
headers={"Authorization": f"Bearer {get_api_key()}"},
|
36
|
+
timeout=httpx.Timeout(30.0),
|
37
|
+
)
|
38
|
+
return self._client
|
39
|
+
|
40
|
+
async def _request(
|
41
|
+
self,
|
42
|
+
method: str,
|
43
|
+
endpoint: str,
|
44
|
+
*,
|
45
|
+
params: Dict[str, Any] | None = None,
|
46
|
+
data: BaseModel | None = None,
|
47
|
+
files: Dict[str, Any] | None = None,
|
48
|
+
) -> Dict[str, Any]:
|
49
|
+
url = urljoin(self.base_url, endpoint)
|
50
|
+
kwargs: Dict[str, Any] = {"params": params}
|
51
|
+
|
52
|
+
if data:
|
53
|
+
kwargs["json"] = data.dict(exclude_unset=True)
|
54
|
+
if files:
|
55
|
+
kwargs["files"] = files
|
56
|
+
|
57
|
+
response = await self.client.request(method, url, **kwargs)
|
58
|
+
|
59
|
+
if response.is_error:
|
60
|
+
logger.error("Error response from K-Scale: %s", response.text)
|
61
|
+
response.raise_for_status()
|
62
|
+
return response.json()
|
63
|
+
|
64
|
+
async def get_artifact_info(self, artifact_id: str) -> SingleArtifactResponse:
|
65
|
+
data = await self._request("GET", f"/artifacts/info/{artifact_id}")
|
66
|
+
return SingleArtifactResponse(**data)
|
67
|
+
|
68
|
+
async def upload_artifact(self, listing_id: str, file_path: str) -> UploadArtifactResponse:
|
69
|
+
file_name = Path(file_path).name
|
70
|
+
with open(file_path, "rb") as f:
|
71
|
+
files = {"files": (file_name, f, "application/gzip")}
|
72
|
+
data = await self._request("POST", f"/artifacts/upload/{listing_id}", files=files)
|
73
|
+
return UploadArtifactResponse(**data)
|
74
|
+
|
75
|
+
async def create_listing(self, request: BodyAddListingListingsAddPost) -> NewListingResponse:
|
76
|
+
data = await self._request("POST", "/listings", data=request)
|
77
|
+
return NewListingResponse(**data)
|
78
|
+
|
79
|
+
async def create_krec(self, request: UploadKRecRequest) -> dict:
|
80
|
+
"""Create a new K-Rec upload and get the presigned URL."""
|
81
|
+
return await self._request(
|
82
|
+
"POST",
|
83
|
+
"/krecs/upload",
|
84
|
+
data=request,
|
85
|
+
)
|
86
|
+
|
87
|
+
async def close(self) -> None:
|
88
|
+
if self._client is not None:
|
89
|
+
await self._client.aclose()
|
90
|
+
self._client = None
|
91
|
+
|
92
|
+
async def __aenter__(self) -> "KScaleWWWClient":
|
93
|
+
return self
|
94
|
+
|
95
|
+
async def __aexit__(
|
96
|
+
self,
|
97
|
+
exc_type: Type[BaseException] | None,
|
98
|
+
exc_val: BaseException | None,
|
99
|
+
exc_tb: TracebackType | None,
|
100
|
+
) -> None:
|
101
|
+
await self.close()
|
102
|
+
|
103
|
+
async def upload_to_presigned_url(self, url: str, file_path: str) -> None:
|
104
|
+
"""Upload a file using a presigned URL."""
|
105
|
+
with open(file_path, "rb") as f:
|
106
|
+
async with httpx.AsyncClient(timeout=httpx.Timeout(timeout=self.upload_timeout)) as client:
|
107
|
+
response = await client.put(url, content=f.read(), headers={"Content-Type": "application/octet-stream"})
|
108
|
+
response.raise_for_status()
|
109
|
+
|
110
|
+
async def get_presigned_url(self, listing_id: str, file_name: str, checksum: str | None = None) -> dict:
|
111
|
+
"""Get a presigned URL for uploading an artifact."""
|
112
|
+
params = {"filename": file_name}
|
113
|
+
if checksum:
|
114
|
+
params["checksum"] = checksum
|
115
|
+
return await self._request("POST", f"/artifacts/presigned/{listing_id}", params=params)
|
116
|
+
|
117
|
+
async def get_krec_info(self, krec_id: str) -> dict:
|
118
|
+
"""Get information about a K-Rec."""
|
119
|
+
logger.info("Getting K-Rec info for ID: %s", krec_id)
|
120
|
+
try:
|
121
|
+
data = await self._request("GET", f"/krecs/download/{krec_id}")
|
122
|
+
if not isinstance(data, dict):
|
123
|
+
logger.error("Server returned unexpected type: %s", type(data))
|
124
|
+
logger.error("Response data: %s", data)
|
125
|
+
raise ValueError(f"Server returned {type(data)} instead of dictionary")
|
126
|
+
|
127
|
+
return {
|
128
|
+
"url": data.get("url"),
|
129
|
+
"filename": data.get("filename"),
|
130
|
+
"checksum": data.get("checksum"),
|
131
|
+
}
|
132
|
+
except Exception as e:
|
133
|
+
logger.error("Failed to get K-Rec info: %s", str(e))
|
134
|
+
raise
|
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2023 Benjamin Bolte
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
@@ -0,0 +1,55 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: kscale
|
3
|
+
Version: 0.0.13
|
4
|
+
Summary: The kscale project
|
5
|
+
Home-page: https://github.com/kscalelabs/kscale
|
6
|
+
Author: Benjamin Bolte
|
7
|
+
Requires-Python: >=3.11
|
8
|
+
Description-Content-Type: text/markdown
|
9
|
+
License-File: LICENSE
|
10
|
+
Requires-Dist: omegaconf
|
11
|
+
Requires-Dist: email_validator
|
12
|
+
Requires-Dist: httpx
|
13
|
+
Requires-Dist: requests
|
14
|
+
Requires-Dist: pydantic
|
15
|
+
Requires-Dist: click
|
16
|
+
Requires-Dist: aiofiles
|
17
|
+
Requires-Dist: krec
|
18
|
+
Provides-Extra: dev
|
19
|
+
Requires-Dist: black; extra == "dev"
|
20
|
+
Requires-Dist: darglint; extra == "dev"
|
21
|
+
Requires-Dist: mypy; extra == "dev"
|
22
|
+
Requires-Dist: pytest; extra == "dev"
|
23
|
+
Requires-Dist: ruff; extra == "dev"
|
24
|
+
Requires-Dist: datamodel-code-generator; extra == "dev"
|
25
|
+
|
26
|
+
<p align="center">
|
27
|
+
<picture>
|
28
|
+
<img alt="K-Scale Open Source Robotics" src="https://media.kscale.dev/kscale-open-source-header.png" style="max-width: 100%;">
|
29
|
+
</picture>
|
30
|
+
</p>
|
31
|
+
|
32
|
+
<div align="center">
|
33
|
+
|
34
|
+
[![License](https://img.shields.io/badge/license-MIT-green)](https://github.com/kscalelabs/ksim/blob/main/LICENSE)
|
35
|
+
[![Discord](https://img.shields.io/discord/1224056091017478166)](https://discord.gg/k5mSvCkYQh)
|
36
|
+
[![Wiki](https://img.shields.io/badge/wiki-humanoids-black)](https://humanoids.wiki)
|
37
|
+
<br />
|
38
|
+
[![python](https://img.shields.io/badge/-Python_3.11-blue?logo=python&logoColor=white)](https://github.com/pre-commit/pre-commit)
|
39
|
+
[![black](https://img.shields.io/badge/Code%20Style-Black-black.svg?labelColor=gray)](https://black.readthedocs.io/en/stable/)
|
40
|
+
[![ruff](https://img.shields.io/badge/Linter-Ruff-red.svg?labelColor=gray)](https://github.com/charliermarsh/ruff)
|
41
|
+
<br />
|
42
|
+
[![Python Checks](https://github.com/kscalelabs/kscale/actions/workflows/test.yml/badge.svg)](https://github.com/kscalelabs/kscale/actions/workflows/test.yml)
|
43
|
+
[![Publish Python Package](https://github.com/kscalelabs/kscale/actions/workflows/publish.yml/badge.svg)](https://github.com/kscalelabs/kscale/actions/workflows/publish.yml)
|
44
|
+
|
45
|
+
</div>
|
46
|
+
|
47
|
+
# K-Scale Command Line Interface
|
48
|
+
|
49
|
+
This is a command line tool for interacting with various services provided by K-Scale Labs. For more information, see the [documentation](https://docs.kscale.dev/pkg/intro).
|
50
|
+
|
51
|
+
## Installation
|
52
|
+
|
53
|
+
```bash
|
54
|
+
pip install kscale
|
55
|
+
```
|
@@ -0,0 +1,31 @@
|
|
1
|
+
kscale/requirements.txt,sha256=zj7Rg5vE2Fl3ao66K9jmOlhv5_8SybHvHK5-4xOGZBs,141
|
2
|
+
kscale/conf.py,sha256=iI6eWgzIhzmqqy9EhVh6MbsmNpqCbTlSVdLEYfxyjvI,1416
|
3
|
+
kscale/__init__.py,sha256=FOc1X2Sjq58lZ0IMP9e0P4HQ2MM_7yBHk-KbuvApm-0,173
|
4
|
+
kscale/api.py,sha256=314tgy4e9fbQWsKPYLyofZyiKfKr9RmvstldXs2jDvY,322
|
5
|
+
kscale/cli.py,sha256=kfu-5dCr0DIEDEnF80tg5md56bEI2GsyA7mEuad1cDw,655
|
6
|
+
kscale/requirements-dev.txt,sha256=WI7-ea4IRJakmqVMN8QKhOsDGrghwtvk03aIsFaNSIw,130
|
7
|
+
kscale/rust.cpython-311-darwin.so,sha256=_mrfXiTsFL75KVnkmr0ikDDVLuftiP7lnleR1iQkVrE,431968
|
8
|
+
kscale/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
+
kscale/artifacts/plane.obj,sha256=x59-IIrWpLjhotChiqT2Ul6U8s0RcHkaEeUZb4KXL1c,348
|
10
|
+
kscale/artifacts/__init__.py,sha256=RK8wdybtCJPgdLLJ8R8-YMi1Ph5ojqAKVJZowHONtgo,232
|
11
|
+
kscale/artifacts/plane.urdf,sha256=LCiTk14AyTHjkZ1jvsb0hNaEaJUxDb8Z1JjsgpXu3YM,819
|
12
|
+
kscale/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
|
+
kscale/web/pybullet.py,sha256=_at_1yf63Fzy7LmXbR-4fvdM9LqPmzYiMniqhKU-BtE,7395
|
14
|
+
kscale/web/api.py,sha256=f7_BPwMnByXSBDE1Rzuhr7IIqoIBY2wqDGoqFLQ_0ng,3809
|
15
|
+
kscale/web/www_client.py,sha256=Vu_rf2NuJ7fSiqTEb1B71OoTurdNrCtSVKc7K8AIk24,4963
|
16
|
+
kscale/web/utils.py,sha256=mXfrtH2TuxXkSF01QpcUyC7OHAszQPsiFYGZcHHgTDk,1337
|
17
|
+
kscale/web/urdf.py,sha256=rJjQBG3q5HBRxebNAccOqD-rXMS-2fpNdh8k5-6p4p0,5821
|
18
|
+
kscale/web/kernels.py,sha256=ddp8IEFSLS9raETQZIphDjxbrj9Jd0gfeQ6Fad0ee-Q,7242
|
19
|
+
kscale/web/krec.py,sha256=k9AhBHwLoxRDRUSllgQ88C_xMzwHCY4mHRrBsQFP1EA,5910
|
20
|
+
kscale/web/gen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
21
|
+
kscale/web/gen/api.py,sha256=JqNit9Q6zUuWz-SrWjcKx3BQIzSmL50r0BT1CfT2hbo,23092
|
22
|
+
kscale/utils/checksum.py,sha256=jt6QmmQND9zrOEnUtOfZpLYROhgto4Gh3OpdUWk4tZA,1093
|
23
|
+
kscale/utils/api_base.py,sha256=Kk_WtRDdJHmOg6NtHmVxVrcfARSUkhfr29ypLch_pO0,112
|
24
|
+
kscale/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
25
|
+
kscale/utils/cli.py,sha256=JoaY5x5SdUx97KmMM9j5AjRRUqqrTlJ9qVckZptEsYA,827
|
26
|
+
kscale-0.0.13.dist-info/RECORD,,
|
27
|
+
kscale-0.0.13.dist-info/LICENSE,sha256=HCN2bImAzUOXldAZZI7JZ9PYq6OwMlDAP_PpX1HnuN0,1071
|
28
|
+
kscale-0.0.13.dist-info/WHEEL,sha256=aDf-IWQmq1RVXR_5Ck1qPm3e7xxy4ZeSSdW4ZZA07R8,109
|
29
|
+
kscale-0.0.13.dist-info/entry_points.txt,sha256=c2pxylFuUJe3aQ_GLEgZrrBW98PPNkiH8Lg0NL_vnrA,42
|
30
|
+
kscale-0.0.13.dist-info/top_level.txt,sha256=C2ynjYwopg6YjgttnI2dJjasyq3EKNmYp-IfQg9Xms4,7
|
31
|
+
kscale-0.0.13.dist-info/METADATA,sha256=-Nmkr2zrj4ydnMtEW2miV_dqgoyY5PJuiirwUrIRsF8,2174
|
@@ -0,0 +1 @@
|
|
1
|
+
kscale
|