kscale 0.0.1__py3-none-any.whl → 0.0.2__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.
- kscale/__init__.py +1 -1
- kscale/conf.py +3 -11
- kscale/store/__init__.py +1 -0
- kscale/store/cli.py +30 -0
- kscale/store/pybullet.py +178 -0
- kscale/store/urdf.py +173 -0
- kscale-0.0.2.dist-info/METADATA +52 -0
- kscale-0.0.2.dist-info/RECORD +15 -0
- {kscale-0.0.1.dist-info → kscale-0.0.2.dist-info}/WHEEL +1 -1
- kscale-0.0.2.dist-info/entry_points.txt +2 -0
- kscale/store/auth.py +0 -13
- kscale-0.0.1.dist-info/METADATA +0 -29
- kscale-0.0.1.dist-info/RECORD +0 -13
- {kscale-0.0.1.dist-info → kscale-0.0.2.dist-info}/LICENSE +0 -0
- {kscale-0.0.1.dist-info → kscale-0.0.2.dist-info}/top_level.txt +0 -0
kscale/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.0.
|
1
|
+
__version__ = "0.0.2"
|
kscale/conf.py
CHANGED
@@ -17,21 +17,13 @@ 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"))
|
21
|
-
|
22
|
-
def get_api_key(self) -> str:
|
23
|
-
try:
|
24
|
-
return self.api_key
|
25
|
-
except AttributeError:
|
26
|
-
raise ValueError(
|
27
|
-
"API key not found! Get one here and set it as the `KSCALE_API_KEY` "
|
28
|
-
"environment variable: https://kscale.store/keys"
|
29
|
-
)
|
20
|
+
api_key: str = field(default=II("oc.env:KSCALE_API_KEY,"))
|
21
|
+
cache_dir: str = field(default=II("oc.env:KSCALE_CACHE_DIR,'~/.kscale/cache/'"))
|
30
22
|
|
31
23
|
|
32
24
|
@dataclass
|
33
25
|
class Settings:
|
34
|
-
store: StoreSettings = StoreSettings
|
26
|
+
store: StoreSettings = field(default_factory=StoreSettings)
|
35
27
|
|
36
28
|
def save(self) -> None:
|
37
29
|
(dir_path := get_path()).mkdir(parents=True, exist_ok=True)
|
kscale/store/__init__.py
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
__version__ = "0.0.1"
|
kscale/store/cli.py
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
"""Defines the top-level KOL CLI."""
|
2
|
+
|
3
|
+
import argparse
|
4
|
+
from typing import Sequence
|
5
|
+
|
6
|
+
from kscale.store import pybullet, urdf
|
7
|
+
|
8
|
+
|
9
|
+
def main(args: Sequence[str] | None = None) -> None:
|
10
|
+
parser = argparse.ArgumentParser(description="K-Scale OnShape Library", add_help=False)
|
11
|
+
parser.add_argument(
|
12
|
+
"subcommand",
|
13
|
+
choices=[
|
14
|
+
"urdf",
|
15
|
+
"pybullet",
|
16
|
+
],
|
17
|
+
help="The subcommand to run",
|
18
|
+
)
|
19
|
+
parsed_args, remaining_args = parser.parse_known_args(args)
|
20
|
+
|
21
|
+
match parsed_args.subcommand:
|
22
|
+
case "urdf":
|
23
|
+
urdf.main(remaining_args)
|
24
|
+
case "pybullet":
|
25
|
+
pybullet.main(remaining_args)
|
26
|
+
|
27
|
+
|
28
|
+
if __name__ == "__main__":
|
29
|
+
# python3 -m kscale.store.cli
|
30
|
+
main()
|
kscale/store/pybullet.py
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
"""Simple script to interact with a URDF in PyBullet."""
|
2
|
+
|
3
|
+
import argparse
|
4
|
+
import itertools
|
5
|
+
import logging
|
6
|
+
import math
|
7
|
+
import time
|
8
|
+
from pathlib import Path
|
9
|
+
from typing import Sequence
|
10
|
+
|
11
|
+
logger = logging.getLogger(__name__)
|
12
|
+
|
13
|
+
|
14
|
+
def main(args: Sequence[str] | None = None) -> None:
|
15
|
+
parser = argparse.ArgumentParser(description="Show a URDF in PyBullet")
|
16
|
+
parser.add_argument("urdf", nargs="?", help="Path to the URDF file")
|
17
|
+
parser.add_argument("--dt", type=float, default=0.01, help="Time step")
|
18
|
+
parser.add_argument("-n", "--hide-gui", action="store_true", help="Hide the GUI")
|
19
|
+
parser.add_argument("--no-merge", action="store_true", help="Do not merge fixed links")
|
20
|
+
parser.add_argument("--hide-origin", action="store_true", help="Do not show the origin")
|
21
|
+
parser.add_argument("--show-inertia", action="store_true", help="Visualizes the inertia frames")
|
22
|
+
parser.add_argument("--see-thru", action="store_true", help="Use see-through mode")
|
23
|
+
parser.add_argument("--show-collision", action="store_true", help="Show collision meshes")
|
24
|
+
parsed_args = parser.parse_args(args)
|
25
|
+
|
26
|
+
try:
|
27
|
+
import pybullet as p # type: ignore[import-not-found]
|
28
|
+
except ImportError:
|
29
|
+
raise ImportError("pybullet is required to run this script")
|
30
|
+
|
31
|
+
# Connect to PyBullet.
|
32
|
+
p.connect(p.GUI)
|
33
|
+
p.setGravity(0, 0, -9.81)
|
34
|
+
p.setRealTimeSimulation(0)
|
35
|
+
|
36
|
+
# Turn off panels.
|
37
|
+
if parsed_args.hide_gui:
|
38
|
+
p.configureDebugVisualizer(p.COV_ENABLE_GUI, 0)
|
39
|
+
p.configureDebugVisualizer(p.COV_ENABLE_SEGMENTATION_MARK_PREVIEW, 0)
|
40
|
+
p.configureDebugVisualizer(p.COV_ENABLE_DEPTH_BUFFER_PREVIEW, 0)
|
41
|
+
p.configureDebugVisualizer(p.COV_ENABLE_RGB_BUFFER_PREVIEW, 0)
|
42
|
+
|
43
|
+
# Enable mouse picking.
|
44
|
+
p.configureDebugVisualizer(p.COV_ENABLE_MOUSE_PICKING, 1)
|
45
|
+
|
46
|
+
# Loads the floor plane.
|
47
|
+
floor = p.loadURDF(str((Path(__file__).parent / "bullet" / "plane.urdf").resolve()))
|
48
|
+
|
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
|
+
# Load the robot URDF.
|
57
|
+
start_position = [0.0, 0.0, 1.0]
|
58
|
+
start_orientation = p.getQuaternionFromEuler([0.0, 0.0, 0.0])
|
59
|
+
flags = p.URDF_USE_INERTIA_FROM_FILE
|
60
|
+
if not parsed_args.no_merge:
|
61
|
+
flags |= p.URDF_MERGE_FIXED_LINKS
|
62
|
+
robot = p.loadURDF(str(urdf_path), start_position, start_orientation, flags=flags, useFixedBase=0)
|
63
|
+
|
64
|
+
# Display collision meshes as separate object.
|
65
|
+
if parsed_args.show_collision:
|
66
|
+
collision_flags = p.URDF_USE_INERTIA_FROM_FILE | p.URDF_USE_SELF_COLLISION_EXCLUDE_ALL_PARENTS
|
67
|
+
collision = p.loadURDF(str(urdf_path), start_position, start_orientation, flags=collision_flags, useFixedBase=0)
|
68
|
+
|
69
|
+
# Make collision shapes semi-transparent.
|
70
|
+
joint_ids = [i for i in range(p.getNumJoints(collision))] + [-1]
|
71
|
+
for i in joint_ids:
|
72
|
+
p.changeVisualShape(collision, i, rgbaColor=[1, 0, 0, 0.5])
|
73
|
+
|
74
|
+
# Initializes physics parameters.
|
75
|
+
p.changeDynamics(floor, -1, lateralFriction=1, spinningFriction=-1, rollingFriction=-1)
|
76
|
+
p.setPhysicsEngineParameter(fixedTimeStep=parsed_args.dt, maxNumCmdPer1ms=1000)
|
77
|
+
|
78
|
+
# Shows the origin of the robot.
|
79
|
+
if not parsed_args.hide_origin:
|
80
|
+
p.addUserDebugLine([0, 0, 0], [0.1, 0, 0], [1, 0, 0], parentObjectUniqueId=robot, parentLinkIndex=-1)
|
81
|
+
p.addUserDebugLine([0, 0, 0], [0, 0.1, 0], [0, 1, 0], parentObjectUniqueId=robot, parentLinkIndex=-1)
|
82
|
+
p.addUserDebugLine([0, 0, 0], [0, 0, 0.1], [0, 0, 1], parentObjectUniqueId=robot, parentLinkIndex=-1)
|
83
|
+
|
84
|
+
# Make the robot see-through.
|
85
|
+
joint_ids = [i for i in range(p.getNumJoints(robot))] + [-1]
|
86
|
+
if parsed_args.see_thru:
|
87
|
+
for i in joint_ids:
|
88
|
+
p.changeVisualShape(robot, i, rgbaColor=[1, 1, 1, 0.5])
|
89
|
+
|
90
|
+
def draw_box(pt: list[list[float]], color: tuple[float, float, float], obj_id: int, link_id: int) -> None:
|
91
|
+
assert len(pt) == 8
|
92
|
+
assert all(len(p) == 3 for p in pt)
|
93
|
+
|
94
|
+
mapping = [1, 3, 0, 2]
|
95
|
+
for i in range(4):
|
96
|
+
p.addUserDebugLine(pt[i], pt[i + 4], color, 1, parentObjectUniqueId=obj_id, parentLinkIndex=link_id)
|
97
|
+
p.addUserDebugLine(pt[i], pt[mapping[i]], color, 1, parentObjectUniqueId=obj_id, parentLinkIndex=link_id)
|
98
|
+
p.addUserDebugLine(
|
99
|
+
pt[i + 4], pt[mapping[i] + 4], color, 1, parentObjectUniqueId=obj_id, parentLinkIndex=link_id
|
100
|
+
)
|
101
|
+
|
102
|
+
# Shows bounding boxes around each part of the robot representing the inertia frame.
|
103
|
+
if parsed_args.show_inertia:
|
104
|
+
for i in joint_ids:
|
105
|
+
dynamics_info = p.getDynamicsInfo(robot, i)
|
106
|
+
mass = dynamics_info[0]
|
107
|
+
if mass <= 0:
|
108
|
+
continue
|
109
|
+
inertia = dynamics_info[2]
|
110
|
+
ixx = inertia[0]
|
111
|
+
iyy = inertia[1]
|
112
|
+
izz = inertia[2]
|
113
|
+
box_scale_x = 0.5 * math.sqrt(6 * (izz + iyy - ixx) / mass)
|
114
|
+
box_scale_y = 0.5 * math.sqrt(6 * (izz + ixx - iyy) / mass)
|
115
|
+
box_scale_z = 0.5 * math.sqrt(6 * (ixx + iyy - izz) / mass)
|
116
|
+
|
117
|
+
half_extents = [box_scale_x, box_scale_y, box_scale_z]
|
118
|
+
pt = [
|
119
|
+
[x, y, z]
|
120
|
+
for x, y, z in itertools.product(
|
121
|
+
[-half_extents[0], half_extents[0]],
|
122
|
+
[-half_extents[1], half_extents[1]],
|
123
|
+
[-half_extents[2], half_extents[2]],
|
124
|
+
)
|
125
|
+
]
|
126
|
+
draw_box(pt, (1, 0, 0), robot, i)
|
127
|
+
|
128
|
+
# Show joint controller.
|
129
|
+
joints: dict[str, int] = {}
|
130
|
+
controls: dict[str, float] = {}
|
131
|
+
for i in range(p.getNumJoints(robot)):
|
132
|
+
joint_info = p.getJointInfo(robot, i)
|
133
|
+
name = joint_info[1].decode("utf-8")
|
134
|
+
joint_type = joint_info[2]
|
135
|
+
joints[name] = i
|
136
|
+
if joint_type == p.JOINT_PRISMATIC:
|
137
|
+
joint_min, joint_max = joint_info[8:10]
|
138
|
+
controls[name] = p.addUserDebugParameter(name, joint_min, joint_max, 0.0)
|
139
|
+
elif joint_type == p.JOINT_REVOLUTE:
|
140
|
+
joint_min, joint_max = joint_info[8:10]
|
141
|
+
controls[name] = p.addUserDebugParameter(name, joint_min, joint_max, 0.0)
|
142
|
+
|
143
|
+
# Run the simulation until the user closes the window.
|
144
|
+
last_time = time.time()
|
145
|
+
prev_control_values = {k: 0.0 for k in controls}
|
146
|
+
while p.isConnected():
|
147
|
+
# Reset the simulation if "r" was pressed.
|
148
|
+
keys = p.getKeyboardEvents()
|
149
|
+
if ord("r") in keys and keys[ord("r")] & p.KEY_WAS_TRIGGERED:
|
150
|
+
p.resetBasePositionAndOrientation(robot, start_position, start_orientation)
|
151
|
+
p.setJointMotorControlArray(
|
152
|
+
robot,
|
153
|
+
range(p.getNumJoints(robot)),
|
154
|
+
p.POSITION_CONTROL,
|
155
|
+
targetPositions=[0] * p.getNumJoints(robot),
|
156
|
+
)
|
157
|
+
|
158
|
+
# Set joint positions.
|
159
|
+
for k, v in controls.items():
|
160
|
+
try:
|
161
|
+
target_position = p.readUserDebugParameter(v)
|
162
|
+
if target_position != prev_control_values[k]:
|
163
|
+
prev_control_values[k] = target_position
|
164
|
+
p.setJointMotorControl2(robot, joints[k], p.POSITION_CONTROL, target_position)
|
165
|
+
except p.error:
|
166
|
+
logger.debug("Failed to set joint %s", k)
|
167
|
+
pass
|
168
|
+
|
169
|
+
# Step simulation.
|
170
|
+
p.stepSimulation()
|
171
|
+
cur_time = time.time()
|
172
|
+
time.sleep(max(0, parsed_args.dt - (cur_time - last_time)))
|
173
|
+
last_time = cur_time
|
174
|
+
|
175
|
+
|
176
|
+
if __name__ == "__main__":
|
177
|
+
# python -m kscale.store.pybullet
|
178
|
+
main()
|
kscale/store/urdf.py
CHANGED
@@ -1 +1,174 @@
|
|
1
1
|
"""Utility functions for managing artifacts in the K-Scale store."""
|
2
|
+
|
3
|
+
import argparse
|
4
|
+
import asyncio
|
5
|
+
import logging
|
6
|
+
import os
|
7
|
+
import sys
|
8
|
+
import tarfile
|
9
|
+
from pathlib import Path
|
10
|
+
from typing import Literal, Sequence
|
11
|
+
|
12
|
+
import httpx
|
13
|
+
import requests
|
14
|
+
|
15
|
+
from kscale.conf import Settings
|
16
|
+
from kscale.store.gen.api import UrdfResponse
|
17
|
+
|
18
|
+
# Set up logging
|
19
|
+
logging.basicConfig(level=logging.INFO)
|
20
|
+
logger = logging.getLogger(__name__)
|
21
|
+
|
22
|
+
|
23
|
+
def get_api_key() -> str:
|
24
|
+
api_key = Settings.load().store.api_key
|
25
|
+
if not api_key:
|
26
|
+
raise ValueError(
|
27
|
+
"API key not found! Get one here and set it as the `KSCALE_API_KEY` environment variable or in your"
|
28
|
+
"config file: https://kscale.store/keys"
|
29
|
+
)
|
30
|
+
return api_key
|
31
|
+
|
32
|
+
|
33
|
+
def get_cache_dir() -> Path:
|
34
|
+
return Path(Settings.load().store.cache_dir).expanduser().resolve()
|
35
|
+
|
36
|
+
|
37
|
+
def fetch_urdf_info(listing_id: str) -> UrdfResponse:
|
38
|
+
url = f"https://api.kscale.store/urdf/info/{listing_id}"
|
39
|
+
headers = {
|
40
|
+
"Authorization": f"Bearer {get_api_key()}",
|
41
|
+
}
|
42
|
+
response = requests.get(url, headers=headers)
|
43
|
+
response.raise_for_status()
|
44
|
+
return UrdfResponse(**response.json())
|
45
|
+
|
46
|
+
|
47
|
+
async def download_artifact(artifact_url: str, cache_dir: Path) -> str:
|
48
|
+
filename = os.path.join(cache_dir, artifact_url.split("/")[-1])
|
49
|
+
headers = {
|
50
|
+
"Authorization": f"Bearer {get_api_key()}",
|
51
|
+
}
|
52
|
+
|
53
|
+
if not os.path.exists(filename):
|
54
|
+
logger.info("Downloading artifact from %s" % artifact_url)
|
55
|
+
|
56
|
+
async with httpx.AsyncClient() as client:
|
57
|
+
response = await client.get(artifact_url, headers=headers)
|
58
|
+
response.raise_for_status()
|
59
|
+
with open(filename, "wb") as f:
|
60
|
+
for chunk in response.iter_bytes(chunk_size=8192):
|
61
|
+
f.write(chunk)
|
62
|
+
logger.info("Artifact downloaded to %s" % filename)
|
63
|
+
else:
|
64
|
+
logger.info("Artifact already cached at %s" % filename)
|
65
|
+
|
66
|
+
# Extract the .tgz file
|
67
|
+
extract_dir = os.path.join(cache_dir, os.path.splitext(os.path.basename(filename))[0])
|
68
|
+
if not os.path.exists(extract_dir):
|
69
|
+
logger.info(f"Extracting {filename} to {extract_dir}")
|
70
|
+
with tarfile.open(filename, "r:gz") as tar:
|
71
|
+
tar.extractall(path=extract_dir)
|
72
|
+
logger.info("Extraction complete")
|
73
|
+
else:
|
74
|
+
logger.info("Artifact already extracted at %s" % extract_dir)
|
75
|
+
|
76
|
+
return extract_dir
|
77
|
+
|
78
|
+
|
79
|
+
def create_tarball(folder_path: str | Path, output_filename: str, cache_dir: Path) -> str:
|
80
|
+
tarball_path = os.path.join(cache_dir, output_filename)
|
81
|
+
with tarfile.open(tarball_path, "w:gz") as tar:
|
82
|
+
for root, _, files in os.walk(folder_path):
|
83
|
+
for file in files:
|
84
|
+
file_path = os.path.join(root, file)
|
85
|
+
arcname = os.path.relpath(file_path, start=folder_path)
|
86
|
+
tar.add(file_path, arcname=arcname)
|
87
|
+
logger.info("Added %s as %s" % (file_path, arcname))
|
88
|
+
logger.info("Created tarball %s" % tarball_path)
|
89
|
+
return tarball_path
|
90
|
+
|
91
|
+
|
92
|
+
async def upload_artifact(tarball_path: str, listing_id: str) -> None:
|
93
|
+
url = f"https://api.kscale.store/urdf/upload/{listing_id}"
|
94
|
+
headers = {
|
95
|
+
"Authorization": f"Bearer {get_api_key()}",
|
96
|
+
}
|
97
|
+
|
98
|
+
async with httpx.AsyncClient() as client:
|
99
|
+
with open(tarball_path, "rb") as f:
|
100
|
+
files = {"file": (f.name, f, "application/gzip")}
|
101
|
+
response = await client.post(url, headers=headers, files=files)
|
102
|
+
|
103
|
+
response.raise_for_status()
|
104
|
+
|
105
|
+
logger.info("Uploaded artifact to %s" % url)
|
106
|
+
|
107
|
+
|
108
|
+
def main(args: Sequence[str] | None = None) -> None:
|
109
|
+
parser = argparse.ArgumentParser(description="K-Scale URDF Store", add_help=False)
|
110
|
+
parser.add_argument(
|
111
|
+
"command",
|
112
|
+
choices=["get", "info", "upload"],
|
113
|
+
help="The command to run",
|
114
|
+
)
|
115
|
+
parser.add_argument("listing_id", help="The listing ID to operate on")
|
116
|
+
parsed_args, remaining_args = parser.parse_known_args(args)
|
117
|
+
|
118
|
+
command: Literal["get", "info", "upload"] = parsed_args.command
|
119
|
+
listing_id: str = parsed_args.listing_id
|
120
|
+
|
121
|
+
def get_listing_dir() -> Path:
|
122
|
+
(cache_dir := get_cache_dir() / listing_id).mkdir(parents=True, exist_ok=True)
|
123
|
+
return cache_dir
|
124
|
+
|
125
|
+
match command:
|
126
|
+
case "get":
|
127
|
+
try:
|
128
|
+
urdf_info = fetch_urdf_info(listing_id)
|
129
|
+
|
130
|
+
if urdf_info.urdf:
|
131
|
+
artifact_url = urdf_info.urdf.url
|
132
|
+
asyncio.run(download_artifact(artifact_url, get_listing_dir()))
|
133
|
+
else:
|
134
|
+
logger.info("No URDF found for listing %s" % listing_id)
|
135
|
+
except requests.RequestException as e:
|
136
|
+
logger.error("Failed to fetch URDF info: %s" % e)
|
137
|
+
sys.exit(1)
|
138
|
+
|
139
|
+
case "info":
|
140
|
+
try:
|
141
|
+
urdf_info = fetch_urdf_info(listing_id)
|
142
|
+
|
143
|
+
if urdf_info.urdf:
|
144
|
+
logger.info("URDF Artifact ID: %s" % urdf_info.urdf.artifact_id)
|
145
|
+
logger.info("URDF URL: %s" % urdf_info.urdf.url)
|
146
|
+
else:
|
147
|
+
logger.info("No URDF found for listing %s" % listing_id)
|
148
|
+
except requests.RequestException as e:
|
149
|
+
logger.error("Failed to fetch URDF info: %s" % e)
|
150
|
+
sys.exit(1)
|
151
|
+
|
152
|
+
case "upload":
|
153
|
+
parser = argparse.ArgumentParser(description="Upload a URDF artifact to the K-Scale store")
|
154
|
+
parser.add_argument("folder_path", help="The path to the folder containing the URDF files")
|
155
|
+
parsed_args = parser.parse_args(remaining_args)
|
156
|
+
folder_path = Path(parsed_args.folder_path).expanduser().resolve()
|
157
|
+
|
158
|
+
output_filename = f"{listing_id}.tgz"
|
159
|
+
tarball_path = create_tarball(folder_path, output_filename, get_listing_dir())
|
160
|
+
|
161
|
+
try:
|
162
|
+
urdf_info = fetch_urdf_info(listing_id)
|
163
|
+
asyncio.run(upload_artifact(tarball_path, listing_id))
|
164
|
+
except requests.RequestException as e:
|
165
|
+
logger.error("Failed to upload artifact: %s" % e)
|
166
|
+
sys.exit(1)
|
167
|
+
|
168
|
+
case _:
|
169
|
+
logger.error("Invalid command")
|
170
|
+
sys.exit(1)
|
171
|
+
|
172
|
+
|
173
|
+
if __name__ == "__main__":
|
174
|
+
main()
|
@@ -0,0 +1,52 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: kscale
|
3
|
+
Version: 0.0.2
|
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: httpx
|
12
|
+
Requires-Dist: requests
|
13
|
+
Provides-Extra: dev
|
14
|
+
Requires-Dist: black ; extra == 'dev'
|
15
|
+
Requires-Dist: darglint ; extra == 'dev'
|
16
|
+
Requires-Dist: mypy ; extra == 'dev'
|
17
|
+
Requires-Dist: pytest ; extra == 'dev'
|
18
|
+
Requires-Dist: ruff ; extra == 'dev'
|
19
|
+
Requires-Dist: datamodel-code-generator ; extra == 'dev'
|
20
|
+
|
21
|
+
<p align="center">
|
22
|
+
<picture>
|
23
|
+
<img alt="K-Scale Open Source Robotics" src="https://media.kscale.dev/kscale-open-source-header.png" style="max-width: 100%;">
|
24
|
+
</picture>
|
25
|
+
</p>
|
26
|
+
|
27
|
+
<div align="center">
|
28
|
+
|
29
|
+
[](https://github.com/kscalelabs/ksim/blob/main/LICENSE)
|
30
|
+
[](https://discord.gg/k5mSvCkYQh)
|
31
|
+
[](https://humanoids.wiki)
|
32
|
+
<br />
|
33
|
+
[](https://github.com/pre-commit/pre-commit)
|
34
|
+
[](https://black.readthedocs.io/en/stable/)
|
35
|
+
[](https://github.com/charliermarsh/ruff)
|
36
|
+
<br />
|
37
|
+
[](https://github.com/kscalelabs/kscale/actions/workflows/test.yml)
|
38
|
+
[](https://github.com/kscalelabs/kscale/actions/workflows/publish.yml)
|
39
|
+
|
40
|
+
</div>
|
41
|
+
|
42
|
+
# K-Scale Command Line Interface
|
43
|
+
|
44
|
+
This is a command line tool for interacting with various services provided by K-Scale Labs, such as:
|
45
|
+
|
46
|
+
- [K-Scale Store](https://kscale.store/)
|
47
|
+
|
48
|
+
## Installation
|
49
|
+
|
50
|
+
```bash
|
51
|
+
pip install kscale
|
52
|
+
```
|
@@ -0,0 +1,15 @@
|
|
1
|
+
kscale/__init__.py,sha256=QvlVh4JTl3JL7jQAja76yKtT-IvF4631ASjWY1wS6AQ,22
|
2
|
+
kscale/conf.py,sha256=dnO8qii7JeMPMdjfuxnFXURRM4krwzL404RnwDkyL2M,1441
|
3
|
+
kscale/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
|
+
kscale/store/__init__.py,sha256=sXLh7g3KC4QCFxcZGBTpG2scR7hmmBsMjq6LqRptkRg,22
|
5
|
+
kscale/store/cli.py,sha256=sf6F8ZLMenNpxi9dPB6kpaUOmRdfNBcKSnAcoN7EMo0,733
|
6
|
+
kscale/store/pybullet.py,sha256=2Pog9wPSyyhmhTJY6x5KhuUQgenpTPIvp_9wxOy9ZaU,7622
|
7
|
+
kscale/store/urdf.py,sha256=-dp0DEMoDNIqieG9egPv_XaV7Cg0GthMI_QIYvG2Xjk,6185
|
8
|
+
kscale/store/gen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
+
kscale/store/gen/api.py,sha256=7r6KvlZyB-kWKSCixc-YOtaWPy2mn0v11d2tuLlRMSc,7700
|
10
|
+
kscale-0.0.2.dist-info/LICENSE,sha256=HCN2bImAzUOXldAZZI7JZ9PYq6OwMlDAP_PpX1HnuN0,1071
|
11
|
+
kscale-0.0.2.dist-info/METADATA,sha256=fNvMO1rM9-UfnHjp3BZ4t4FmcQ-CRPAmBDAdXRkBu-8,2028
|
12
|
+
kscale-0.0.2.dist-info/WHEEL,sha256=Mdi9PDNwEZptOjTlUcAth7XJDFtKrHYaQMPulZeBCiQ,91
|
13
|
+
kscale-0.0.2.dist-info/entry_points.txt,sha256=Vfj9O643497OONpgwy5UJLJ5a5Q1CfHZSuYKDB9D_GI,49
|
14
|
+
kscale-0.0.2.dist-info/top_level.txt,sha256=C2ynjYwopg6YjgttnI2dJjasyq3EKNmYp-IfQg9Xms4,7
|
15
|
+
kscale-0.0.2.dist-info/RECORD,,
|
kscale/store/auth.py
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
"""Defines utility functions for authenticating the K-Scale Store API."""
|
2
|
-
|
3
|
-
from kscale.conf import Settings
|
4
|
-
|
5
|
-
|
6
|
-
def get_api_key() -> str:
|
7
|
-
try:
|
8
|
-
return Settings.load().store.api_key
|
9
|
-
except AttributeError:
|
10
|
-
raise ValueError(
|
11
|
-
"API key not found! Get one here and set it as the `KSCALE_API_KEY` "
|
12
|
-
"environment variable: https://kscale.store/keys"
|
13
|
-
)
|
kscale-0.0.1.dist-info/METADATA
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.1
|
2
|
-
Name: kscale
|
3
|
-
Version: 0.0.1
|
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
|
-
Provides-Extra: dev
|
12
|
-
Requires-Dist: black ; extra == 'dev'
|
13
|
-
Requires-Dist: darglint ; extra == 'dev'
|
14
|
-
Requires-Dist: mypy ; extra == 'dev'
|
15
|
-
Requires-Dist: pytest ; extra == 'dev'
|
16
|
-
Requires-Dist: ruff ; extra == 'dev'
|
17
|
-
Requires-Dist: datamodel-code-generator ; extra == 'dev'
|
18
|
-
|
19
|
-
# K-Scale Command Line Interface
|
20
|
-
|
21
|
-
This is a command line tool for interacting with various services provided by K-Scale Labs, such as:
|
22
|
-
|
23
|
-
- [K-Scale Store](https://kscale.store/)
|
24
|
-
|
25
|
-
## Installation
|
26
|
-
|
27
|
-
```bash
|
28
|
-
pip install kscale
|
29
|
-
```
|
kscale-0.0.1.dist-info/RECORD
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
kscale/__init__.py,sha256=sXLh7g3KC4QCFxcZGBTpG2scR7hmmBsMjq6LqRptkRg,22
|
2
|
-
kscale/conf.py,sha256=jLGM_bn-QdbZlZyqkhEYo07iIJBsIbuA1sg93YBOqhw,1641
|
3
|
-
kscale/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
|
-
kscale/store/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
-
kscale/store/auth.py,sha256=f84jTwrPXb5OFaY2UGfb9lNqoL1Kqac4N1Q61P2qthk,397
|
6
|
-
kscale/store/urdf.py,sha256=QNwXe677pZm3brUStqu6LLbZrGHCQqpGYQtDOxid4WM,69
|
7
|
-
kscale/store/gen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
|
-
kscale/store/gen/api.py,sha256=7r6KvlZyB-kWKSCixc-YOtaWPy2mn0v11d2tuLlRMSc,7700
|
9
|
-
kscale-0.0.1.dist-info/LICENSE,sha256=HCN2bImAzUOXldAZZI7JZ9PYq6OwMlDAP_PpX1HnuN0,1071
|
10
|
-
kscale-0.0.1.dist-info/METADATA,sha256=xXZUlVlvE5Z0RGl5I-MWamdB2-Pwc3m8WeJfJEBNHkk,756
|
11
|
-
kscale-0.0.1.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91
|
12
|
-
kscale-0.0.1.dist-info/top_level.txt,sha256=C2ynjYwopg6YjgttnI2dJjasyq3EKNmYp-IfQg9Xms4,7
|
13
|
-
kscale-0.0.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|