kscale 0.2.2__py3-none-any.whl → 0.3.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.
- kscale/__init__.py +1 -1
- kscale/web/cli/robot_class.py +299 -0
- kscale/web/clients/base.py +4 -3
- kscale/web/clients/robot.py +5 -5
- kscale/web/clients/robot_class.py +14 -9
- kscale/web/gen/api.py +65 -9
- {kscale-0.2.2.dist-info → kscale-0.3.0.dist-info}/METADATA +1 -1
- {kscale-0.2.2.dist-info → kscale-0.3.0.dist-info}/RECORD +12 -12
- {kscale-0.2.2.dist-info → kscale-0.3.0.dist-info}/LICENSE +0 -0
- {kscale-0.2.2.dist-info → kscale-0.3.0.dist-info}/WHEEL +0 -0
- {kscale-0.2.2.dist-info → kscale-0.3.0.dist-info}/entry_points.txt +0 -0
- {kscale-0.2.2.dist-info → kscale-0.3.0.dist-info}/top_level.txt +0 -0
kscale/__init__.py
CHANGED
kscale/web/cli/robot_class.py
CHANGED
@@ -1,12 +1,17 @@
|
|
1
1
|
"""Defines the CLI for getting information about robot classes."""
|
2
2
|
|
3
|
+
import json
|
3
4
|
import logging
|
5
|
+
import math
|
6
|
+
import time
|
7
|
+
from typing import Sequence
|
4
8
|
|
5
9
|
import click
|
6
10
|
from tabulate import tabulate
|
7
11
|
|
8
12
|
from kscale.utils.cli import coro
|
9
13
|
from kscale.web.clients.robot_class import RobotClassClient
|
14
|
+
from kscale.web.gen.api import RobotURDFMetadataInput
|
10
15
|
|
11
16
|
logger = logging.getLogger(__name__)
|
12
17
|
|
@@ -70,6 +75,22 @@ async def update(current_name: str, name: str | None = None, description: str |
|
|
70
75
|
click.echo(f" Description: {click.style(robot_class.description or 'N/A', fg='yellow')}")
|
71
76
|
|
72
77
|
|
78
|
+
@cli.command()
|
79
|
+
@click.argument("name")
|
80
|
+
@click.argument("json_path", type=click.Path(exists=True))
|
81
|
+
@coro
|
82
|
+
async def update_metadata(name: str, json_path: str) -> None:
|
83
|
+
"""Updates the metadata of a robot class."""
|
84
|
+
with open(json_path, "r", encoding="utf-8") as f:
|
85
|
+
raw_metadata = json.load(f)
|
86
|
+
metadata = RobotURDFMetadataInput.model_validate(raw_metadata)
|
87
|
+
async with RobotClassClient() as client:
|
88
|
+
robot_class = await client.update_robot_class(name, new_metadata=metadata)
|
89
|
+
click.echo("Robot class metadata updated:")
|
90
|
+
click.echo(f" ID: {click.style(robot_class.id, fg='blue')}")
|
91
|
+
click.echo(f" Name: {click.style(robot_class.class_name, fg='green')}")
|
92
|
+
|
93
|
+
|
73
94
|
@cli.command()
|
74
95
|
@click.argument("name")
|
75
96
|
@coro
|
@@ -109,5 +130,283 @@ async def download(class_name: str, no_cache: bool) -> None:
|
|
109
130
|
click.echo(f"URDF downloaded: {click.style(urdf_file, fg='green')}")
|
110
131
|
|
111
132
|
|
133
|
+
@urdf.command()
|
134
|
+
@click.argument("class_name")
|
135
|
+
@click.option("--no-cache", is_flag=True, default=False)
|
136
|
+
@click.option("--hide-gui", is_flag=True, default=False)
|
137
|
+
@click.option("--hide-origin", is_flag=True, default=False)
|
138
|
+
@click.option("--see-thru", is_flag=True, default=False)
|
139
|
+
@click.option("--show-collision", is_flag=True, default=False)
|
140
|
+
@click.option("--show-inertia", is_flag=True, default=False)
|
141
|
+
@click.option("--fixed-base", is_flag=True, default=False)
|
142
|
+
@click.option("--no-merge", is_flag=True, default=False)
|
143
|
+
@click.option("--dt", type=float, default=0.01)
|
144
|
+
@click.option("--start-height", type=float, default=0.0)
|
145
|
+
@click.option("--cycle-duration", type=float, default=2.0)
|
146
|
+
@coro
|
147
|
+
async def pybullet(
|
148
|
+
class_name: str,
|
149
|
+
no_cache: bool,
|
150
|
+
hide_gui: bool,
|
151
|
+
hide_origin: bool,
|
152
|
+
see_thru: bool,
|
153
|
+
show_collision: bool,
|
154
|
+
show_inertia: bool,
|
155
|
+
fixed_base: bool,
|
156
|
+
no_merge: bool,
|
157
|
+
dt: float,
|
158
|
+
start_height: float,
|
159
|
+
cycle_duration: float,
|
160
|
+
) -> None:
|
161
|
+
"""Shows the URDF file for a robot class in PyBullet."""
|
162
|
+
try:
|
163
|
+
import pybullet as p
|
164
|
+
except ImportError:
|
165
|
+
click.echo(click.style("PyBullet is not installed; install it with `pip install pybullet`", fg="red"))
|
166
|
+
return
|
167
|
+
async with RobotClassClient() as client:
|
168
|
+
urdf_base = await client.download_and_extract_urdf(class_name, cache=not no_cache)
|
169
|
+
try:
|
170
|
+
urdf_path = next(urdf_base.glob("*.urdf"))
|
171
|
+
except StopIteration:
|
172
|
+
click.echo(click.style(f"No URDF file found in {urdf_base}", fg="red"))
|
173
|
+
return
|
174
|
+
|
175
|
+
# Connect to PyBullet.
|
176
|
+
p.connect(p.GUI)
|
177
|
+
p.setGravity(0, 0, -9.81)
|
178
|
+
p.setRealTimeSimulation(0)
|
179
|
+
|
180
|
+
# Create floor plane
|
181
|
+
floor = p.createCollisionShape(p.GEOM_PLANE)
|
182
|
+
p.createMultiBody(0, floor)
|
183
|
+
|
184
|
+
# Turn off panels.
|
185
|
+
if hide_gui:
|
186
|
+
p.configureDebugVisualizer(p.COV_ENABLE_GUI, 0)
|
187
|
+
p.configureDebugVisualizer(p.COV_ENABLE_SEGMENTATION_MARK_PREVIEW, 0)
|
188
|
+
p.configureDebugVisualizer(p.COV_ENABLE_DEPTH_BUFFER_PREVIEW, 0)
|
189
|
+
p.configureDebugVisualizer(p.COV_ENABLE_RGB_BUFFER_PREVIEW, 0)
|
190
|
+
|
191
|
+
# Enable mouse picking.
|
192
|
+
p.configureDebugVisualizer(p.COV_ENABLE_MOUSE_PICKING, 1)
|
193
|
+
|
194
|
+
# Load the robot URDF.
|
195
|
+
start_position = [0.0, 0.0, start_height]
|
196
|
+
start_orientation = p.getQuaternionFromEuler([0.0, 0.0, 0.0])
|
197
|
+
flags = p.URDF_USE_INERTIA_FROM_FILE
|
198
|
+
if not no_merge:
|
199
|
+
flags |= p.URDF_MERGE_FIXED_LINKS
|
200
|
+
|
201
|
+
robot = p.loadURDF(
|
202
|
+
str(urdf_path.resolve().absolute()),
|
203
|
+
start_position,
|
204
|
+
start_orientation,
|
205
|
+
flags=flags,
|
206
|
+
useFixedBase=fixed_base,
|
207
|
+
)
|
208
|
+
|
209
|
+
# Display collision meshes as separate object.
|
210
|
+
if show_collision:
|
211
|
+
collision_flags = p.URDF_USE_INERTIA_FROM_FILE | p.URDF_USE_SELF_COLLISION_EXCLUDE_ALL_PARENTS
|
212
|
+
collision = p.loadURDF(
|
213
|
+
str(urdf_path.resolve().absolute()),
|
214
|
+
start_position,
|
215
|
+
start_orientation,
|
216
|
+
flags=collision_flags,
|
217
|
+
useFixedBase=0,
|
218
|
+
)
|
219
|
+
|
220
|
+
# Make collision shapes semi-transparent.
|
221
|
+
joint_ids = [i for i in range(p.getNumJoints(collision))] + [-1]
|
222
|
+
for i in joint_ids:
|
223
|
+
p.changeVisualShape(collision, i, rgbaColor=[1, 0, 0, 0.5])
|
224
|
+
|
225
|
+
# Initializes physics parameters.
|
226
|
+
p.changeDynamics(floor, -1, lateralFriction=1, spinningFriction=-1, rollingFriction=-1)
|
227
|
+
p.setPhysicsEngineParameter(fixedTimeStep=dt, maxNumCmdPer1ms=1000)
|
228
|
+
|
229
|
+
# Shows the origin of the robot.
|
230
|
+
if not hide_origin:
|
231
|
+
p.addUserDebugLine([0, 0, 0], [0.1, 0, 0], [1, 0, 0], parentObjectUniqueId=robot, parentLinkIndex=-1)
|
232
|
+
p.addUserDebugLine([0, 0, 0], [0, 0.1, 0], [0, 1, 0], parentObjectUniqueId=robot, parentLinkIndex=-1)
|
233
|
+
p.addUserDebugLine([0, 0, 0], [0, 0, 0.1], [0, 0, 1], parentObjectUniqueId=robot, parentLinkIndex=-1)
|
234
|
+
|
235
|
+
# Make the robot see-through.
|
236
|
+
joint_ids = [i for i in range(p.getNumJoints(robot))] + [-1]
|
237
|
+
if see_thru:
|
238
|
+
shape_data = p.getVisualShapeData(robot)
|
239
|
+
for i in joint_ids:
|
240
|
+
prev_color = shape_data[i][-1]
|
241
|
+
p.changeVisualShape(robot, i, rgbaColor=prev_color[:3] + (0.9,))
|
242
|
+
|
243
|
+
def draw_box(pt: Sequence[Sequence[float]], color: tuple[float, float, float], obj_id: int, link_id: int) -> None:
|
244
|
+
"""Draw a box in PyBullet debug visualization.
|
245
|
+
|
246
|
+
Args:
|
247
|
+
pt: List of 8 points defining box vertices, each point is [x,y,z]
|
248
|
+
color: RGB color tuple for the box lines
|
249
|
+
obj_id: PyBullet object ID to attach box to
|
250
|
+
link_id: Link ID on the object to attach box to
|
251
|
+
"""
|
252
|
+
assert len(pt) == 8
|
253
|
+
assert all(len(p) == 3 for p in pt)
|
254
|
+
|
255
|
+
p.addUserDebugLine(pt[0], pt[1], color, 1, parentObjectUniqueId=obj_id, parentLinkIndex=link_id)
|
256
|
+
p.addUserDebugLine(pt[1], pt[3], color, 1, parentObjectUniqueId=obj_id, parentLinkIndex=link_id)
|
257
|
+
p.addUserDebugLine(pt[3], pt[2], color, 1, parentObjectUniqueId=obj_id, parentLinkIndex=link_id)
|
258
|
+
p.addUserDebugLine(pt[2], pt[0], color, 1, parentObjectUniqueId=obj_id, parentLinkIndex=link_id)
|
259
|
+
|
260
|
+
p.addUserDebugLine(pt[0], pt[4], color, 1, parentObjectUniqueId=obj_id, parentLinkIndex=link_id)
|
261
|
+
p.addUserDebugLine(pt[1], pt[5], color, 1, parentObjectUniqueId=obj_id, parentLinkIndex=link_id)
|
262
|
+
p.addUserDebugLine(pt[2], pt[6], color, 1, parentObjectUniqueId=obj_id, parentLinkIndex=link_id)
|
263
|
+
p.addUserDebugLine(pt[3], pt[7], color, 1, parentObjectUniqueId=obj_id, parentLinkIndex=link_id)
|
264
|
+
|
265
|
+
p.addUserDebugLine(pt[4 + 0], pt[4 + 1], color, 1, parentObjectUniqueId=obj_id, parentLinkIndex=link_id)
|
266
|
+
p.addUserDebugLine(pt[4 + 1], pt[4 + 3], color, 1, parentObjectUniqueId=obj_id, parentLinkIndex=link_id)
|
267
|
+
p.addUserDebugLine(pt[4 + 3], pt[4 + 2], color, 1, parentObjectUniqueId=obj_id, parentLinkIndex=link_id)
|
268
|
+
p.addUserDebugLine(pt[4 + 2], pt[4 + 0], color, 1, parentObjectUniqueId=obj_id, parentLinkIndex=link_id)
|
269
|
+
|
270
|
+
# Shows bounding boxes around each part of the robot representing the inertia frame.
|
271
|
+
if show_inertia:
|
272
|
+
for i in joint_ids:
|
273
|
+
dynamics_info = p.getDynamicsInfo(robot, i)
|
274
|
+
mass = dynamics_info[0]
|
275
|
+
if mass <= 0:
|
276
|
+
continue
|
277
|
+
inertia = dynamics_info[2]
|
278
|
+
|
279
|
+
# Calculate box dimensions.
|
280
|
+
ixx, iyy, izz = inertia[0], inertia[1], inertia[2]
|
281
|
+
box_scale_x = math.sqrt(6 * (iyy + izz - ixx) / mass) / 2
|
282
|
+
box_scale_y = math.sqrt(6 * (ixx + izz - iyy) / mass) / 2
|
283
|
+
box_scale_z = math.sqrt(6 * (ixx + iyy - izz) / mass) / 2
|
284
|
+
half_extents = [box_scale_x, box_scale_y, box_scale_z]
|
285
|
+
|
286
|
+
# Create box vertices in local inertia frame
|
287
|
+
pt = [
|
288
|
+
[half_extents[0], half_extents[1], half_extents[2]],
|
289
|
+
[-half_extents[0], half_extents[1], half_extents[2]],
|
290
|
+
[half_extents[0], -half_extents[1], half_extents[2]],
|
291
|
+
[-half_extents[0], -half_extents[1], half_extents[2]],
|
292
|
+
[half_extents[0], half_extents[1], -half_extents[2]],
|
293
|
+
[-half_extents[0], half_extents[1], -half_extents[2]],
|
294
|
+
[half_extents[0], -half_extents[1], -half_extents[2]],
|
295
|
+
[-half_extents[0], -half_extents[1], -half_extents[2]],
|
296
|
+
]
|
297
|
+
|
298
|
+
draw_box(pt, (1, 0, 0), robot, i)
|
299
|
+
|
300
|
+
# Show joint controller.
|
301
|
+
joints: dict[str, int] = {}
|
302
|
+
controls: dict[str, float] = {}
|
303
|
+
for i in range(p.getNumJoints(robot)):
|
304
|
+
joint_info = p.getJointInfo(robot, i)
|
305
|
+
name = joint_info[1].decode("utf-8")
|
306
|
+
joint_type = joint_info[2]
|
307
|
+
joints[name] = i
|
308
|
+
if joint_type == p.JOINT_PRISMATIC:
|
309
|
+
joint_min, joint_max = joint_info[8:10]
|
310
|
+
controls[name] = p.addUserDebugParameter(name, joint_min, joint_max, 0.0)
|
311
|
+
elif joint_type == p.JOINT_REVOLUTE:
|
312
|
+
joint_min, joint_max = joint_info[8:10]
|
313
|
+
controls[name] = p.addUserDebugParameter(name, joint_min, joint_max, 0.0)
|
314
|
+
|
315
|
+
def reset_joints_to_zero(robot: int, joints: dict[str, int]) -> None:
|
316
|
+
for joint_id in joints.values():
|
317
|
+
joint_info = p.getJointInfo(robot, joint_id)
|
318
|
+
joint_min, joint_max = joint_info[8:10]
|
319
|
+
zero_position = (joint_min + joint_max) / 2
|
320
|
+
p.setJointMotorControl2(robot, joint_id, p.POSITION_CONTROL, zero_position)
|
321
|
+
|
322
|
+
def reset_camera(position: int) -> None:
|
323
|
+
height = start_height if fixed_base else 0
|
324
|
+
camera_positions = {
|
325
|
+
1: (2.0, 0, -30, [0, 0, height]), # Default view
|
326
|
+
2: (2.0, 90, -30, [0, 0, height]), # Side view
|
327
|
+
3: (2.0, 180, -30, [0, 0, height]), # Back view
|
328
|
+
4: (2.0, 270, -30, [0, 0, height]), # Other side view
|
329
|
+
5: (2.0, 0, 0, [0, 0, height]), # Front level view
|
330
|
+
6: (2.0, 0, -80, [0, 0, height]), # Top-down view
|
331
|
+
7: (1.5, 45, -45, [0, 0, height]), # Closer angled view
|
332
|
+
8: (3.0, 30, -30, [0, 0, height]), # Further angled view
|
333
|
+
9: (2.0, 0, 30, [0, 0, height]), # Low angle view
|
334
|
+
}
|
335
|
+
|
336
|
+
if position in camera_positions:
|
337
|
+
distance, yaw, pitch, target = camera_positions[position]
|
338
|
+
p.resetDebugVisualizerCamera(
|
339
|
+
cameraDistance=distance,
|
340
|
+
cameraYaw=yaw,
|
341
|
+
cameraPitch=pitch,
|
342
|
+
cameraTargetPosition=target,
|
343
|
+
)
|
344
|
+
|
345
|
+
# Run the simulation until the user closes the window.
|
346
|
+
last_time = time.time()
|
347
|
+
prev_control_values = {k: 0.0 for k in controls}
|
348
|
+
cycle_joints = False
|
349
|
+
cycle_start_time = 0.0
|
350
|
+
|
351
|
+
while p.isConnected():
|
352
|
+
# Reset the simulation if "r" was pressed.
|
353
|
+
keys = p.getKeyboardEvents()
|
354
|
+
if ord("r") in keys and keys[ord("r")] & p.KEY_WAS_TRIGGERED:
|
355
|
+
p.resetBasePositionAndOrientation(robot, start_position, start_orientation)
|
356
|
+
p.setJointMotorControlArray(
|
357
|
+
robot,
|
358
|
+
range(p.getNumJoints(robot)),
|
359
|
+
p.POSITION_CONTROL,
|
360
|
+
targetPositions=[0] * p.getNumJoints(robot),
|
361
|
+
)
|
362
|
+
|
363
|
+
# Reset joints to zero position if "z" was pressed
|
364
|
+
if ord("z") in keys and keys[ord("z")] & p.KEY_WAS_TRIGGERED:
|
365
|
+
reset_joints_to_zero(robot, joints)
|
366
|
+
cycle_joints = False # Stop joint cycling if it was active
|
367
|
+
|
368
|
+
# Reset camera if number keys 1-9 are pressed
|
369
|
+
for i in range(1, 10):
|
370
|
+
if ord(str(i)) in keys and keys[ord(str(i))] & p.KEY_WAS_TRIGGERED:
|
371
|
+
reset_camera(i)
|
372
|
+
|
373
|
+
# Start/stop joint cycling if "c" was pressed
|
374
|
+
if ord("c") in keys and keys[ord("c")] & p.KEY_WAS_TRIGGERED:
|
375
|
+
cycle_joints = not cycle_joints
|
376
|
+
if cycle_joints:
|
377
|
+
cycle_start_time = time.time()
|
378
|
+
else:
|
379
|
+
# When stopping joint cycling, set joints to their current positions
|
380
|
+
for k, v in controls.items():
|
381
|
+
current_position = p.getJointState(robot, joints[k])[0]
|
382
|
+
p.setJointMotorControl2(robot, joints[k], p.POSITION_CONTROL, current_position)
|
383
|
+
|
384
|
+
# Set joint positions.
|
385
|
+
if cycle_joints:
|
386
|
+
elapsed_time = time.time() - cycle_start_time
|
387
|
+
cycle_progress = (elapsed_time % cycle_duration) / cycle_duration
|
388
|
+
for k, v in controls.items():
|
389
|
+
joint_info = p.getJointInfo(robot, joints[k])
|
390
|
+
joint_min, joint_max = joint_info[8:10]
|
391
|
+
target_position = joint_min + (joint_max - joint_min) * math.sin(cycle_progress * math.pi)
|
392
|
+
p.setJointMotorControl2(robot, joints[k], p.POSITION_CONTROL, target_position)
|
393
|
+
else:
|
394
|
+
for k, v in controls.items():
|
395
|
+
try:
|
396
|
+
target_position = p.readUserDebugParameter(v)
|
397
|
+
if target_position != prev_control_values[k]:
|
398
|
+
prev_control_values[k] = target_position
|
399
|
+
p.setJointMotorControl2(robot, joints[k], p.POSITION_CONTROL, target_position)
|
400
|
+
except p.error:
|
401
|
+
logger.debug("Failed to set joint %s", k)
|
402
|
+
pass
|
403
|
+
|
404
|
+
# Step simulation.
|
405
|
+
p.stepSimulation()
|
406
|
+
cur_time = time.time()
|
407
|
+
time.sleep(max(0, dt - (cur_time - last_time)))
|
408
|
+
last_time = cur_time
|
409
|
+
|
410
|
+
|
112
411
|
if __name__ == "__main__":
|
113
412
|
cli()
|
kscale/web/clients/base.py
CHANGED
@@ -360,9 +360,10 @@ class BaseClient:
|
|
360
360
|
files: dict[str, Any] | None = None,
|
361
361
|
) -> dict[str, Any]:
|
362
362
|
url = urljoin(self.base_url, endpoint)
|
363
|
-
kwargs: dict[str, Any] = {
|
364
|
-
|
365
|
-
|
363
|
+
kwargs: dict[str, Any] = {}
|
364
|
+
if params is not None:
|
365
|
+
kwargs["params"] = params
|
366
|
+
if data is not None:
|
366
367
|
if isinstance(data, BaseModel):
|
367
368
|
kwargs["json"] = data.model_dump(exclude_unset=True)
|
368
369
|
else:
|
kscale/web/clients/robot.py
CHANGED
@@ -19,16 +19,16 @@ class RobotClient(BaseClient):
|
|
19
19
|
class_name: str,
|
20
20
|
description: str | None = None,
|
21
21
|
) -> RobotResponse:
|
22
|
-
|
22
|
+
data = {"class_name": class_name}
|
23
23
|
if description is not None:
|
24
|
-
|
25
|
-
|
24
|
+
data["description"] = description
|
25
|
+
response = await self._request(
|
26
26
|
"PUT",
|
27
27
|
f"/robot/{robot_name}",
|
28
|
-
|
28
|
+
data=data,
|
29
29
|
auth=True,
|
30
30
|
)
|
31
|
-
return RobotResponse.model_validate(
|
31
|
+
return RobotResponse.model_validate(response)
|
32
32
|
|
33
33
|
async def get_robot_by_id(self, robot_id: str) -> RobotResponse:
|
34
34
|
data = await self._request("GET", f"/robot/id/{robot_id}", auth=True)
|
@@ -5,6 +5,7 @@ import json
|
|
5
5
|
import logging
|
6
6
|
import tarfile
|
7
7
|
from pathlib import Path
|
8
|
+
from typing import Any
|
8
9
|
|
9
10
|
import httpx
|
10
11
|
|
@@ -13,6 +14,7 @@ from kscale.web.gen.api import (
|
|
13
14
|
RobotClass,
|
14
15
|
RobotDownloadURDFResponse,
|
15
16
|
RobotUploadURDFResponse,
|
17
|
+
RobotURDFMetadataInput,
|
16
18
|
)
|
17
19
|
from kscale.web.utils import get_robots_dir, should_refresh_file
|
18
20
|
|
@@ -34,13 +36,13 @@ class RobotClassClient(BaseClient):
|
|
34
36
|
return [RobotClass.model_validate(item) for item in data]
|
35
37
|
|
36
38
|
async def create_robot_class(self, class_name: str, description: str | None = None) -> RobotClass:
|
37
|
-
|
39
|
+
data = {}
|
38
40
|
if description is not None:
|
39
|
-
|
41
|
+
data["description"] = description
|
40
42
|
data = await self._request(
|
41
43
|
"PUT",
|
42
44
|
f"/robots/{class_name}",
|
43
|
-
|
45
|
+
data=data,
|
44
46
|
auth=True,
|
45
47
|
)
|
46
48
|
return RobotClass.model_validate(data)
|
@@ -50,18 +52,21 @@ class RobotClassClient(BaseClient):
|
|
50
52
|
class_name: str,
|
51
53
|
new_class_name: str | None = None,
|
52
54
|
new_description: str | None = None,
|
55
|
+
new_metadata: RobotURDFMetadataInput | None = None,
|
53
56
|
) -> RobotClass:
|
54
|
-
|
57
|
+
data: dict[str, Any] = {}
|
55
58
|
if new_class_name is not None:
|
56
|
-
|
59
|
+
data["new_class_name"] = new_class_name
|
57
60
|
if new_description is not None:
|
58
|
-
|
59
|
-
if not
|
61
|
+
data["new_description"] = new_description
|
62
|
+
if new_metadata is not None:
|
63
|
+
data["new_metadata"] = new_metadata.model_dump()
|
64
|
+
if not data:
|
60
65
|
raise ValueError("No parameters to update")
|
61
66
|
data = await self._request(
|
62
67
|
"POST",
|
63
68
|
f"/robots/{class_name}",
|
64
|
-
|
69
|
+
data=data,
|
65
70
|
auth=True,
|
66
71
|
)
|
67
72
|
return RobotClass.model_validate(data)
|
@@ -84,7 +89,7 @@ class RobotClassClient(BaseClient):
|
|
84
89
|
data = await self._request(
|
85
90
|
"PUT",
|
86
91
|
f"/robots/urdf/{class_name}",
|
87
|
-
|
92
|
+
data={"filename": urdf_file.name, "content_type": content_type},
|
88
93
|
auth=True,
|
89
94
|
)
|
90
95
|
response = RobotUploadURDFResponse.model_validate(data)
|
kscale/web/gen/api.py
CHANGED
@@ -2,15 +2,46 @@
|
|
2
2
|
|
3
3
|
# generated by datamodel-codegen:
|
4
4
|
# filename: openapi.json
|
5
|
-
# timestamp: 2025-01-
|
5
|
+
# timestamp: 2025-01-21T07:10:22+00:00
|
6
6
|
|
7
7
|
from __future__ import annotations
|
8
8
|
|
9
|
-
from typing import List, Optional, Union
|
9
|
+
from typing import Dict, List, Optional, Union
|
10
10
|
|
11
11
|
from pydantic import BaseModel, Field
|
12
12
|
|
13
13
|
|
14
|
+
class APIKeyResponse(BaseModel):
|
15
|
+
api_key: str = Field(..., title="Api Key")
|
16
|
+
|
17
|
+
|
18
|
+
class AddRobotClassRequest(BaseModel):
|
19
|
+
description: Optional[str] = Field(None, title="Description")
|
20
|
+
|
21
|
+
|
22
|
+
class AddRobotRequest(BaseModel):
|
23
|
+
description: Optional[str] = Field(None, title="Description")
|
24
|
+
class_name: str = Field(..., title="Class Name")
|
25
|
+
|
26
|
+
|
27
|
+
class JointMetadataInput(BaseModel):
|
28
|
+
id: Optional[int] = Field(None, title="Id")
|
29
|
+
kp: Optional[Union[float, str]] = Field(None, title="Kp")
|
30
|
+
kd: Optional[Union[float, str]] = Field(None, title="Kd")
|
31
|
+
offset: Optional[Union[float, str]] = Field(None, title="Offset")
|
32
|
+
lower_limit: Optional[Union[float, str]] = Field(None, title="Lower Limit")
|
33
|
+
upper_limit: Optional[Union[float, str]] = Field(None, title="Upper Limit")
|
34
|
+
|
35
|
+
|
36
|
+
class JointMetadataOutput(BaseModel):
|
37
|
+
id: Optional[int] = Field(None, title="Id")
|
38
|
+
kp: Optional[str] = Field(None, title="Kp")
|
39
|
+
kd: Optional[str] = Field(None, title="Kd")
|
40
|
+
offset: Optional[str] = Field(None, title="Offset")
|
41
|
+
lower_limit: Optional[str] = Field(None, title="Lower Limit")
|
42
|
+
upper_limit: Optional[str] = Field(None, title="Upper Limit")
|
43
|
+
|
44
|
+
|
14
45
|
class OICDInfo(BaseModel):
|
15
46
|
authority: str = Field(..., title="Authority")
|
16
47
|
client_id: str = Field(..., title="Client Id")
|
@@ -24,13 +55,6 @@ class Robot(BaseModel):
|
|
24
55
|
class_id: str = Field(..., title="Class Id")
|
25
56
|
|
26
57
|
|
27
|
-
class RobotClass(BaseModel):
|
28
|
-
id: str = Field(..., title="Id")
|
29
|
-
class_name: str = Field(..., title="Class Name")
|
30
|
-
description: str = Field(..., title="Description")
|
31
|
-
user_id: str = Field(..., title="User Id")
|
32
|
-
|
33
|
-
|
34
58
|
class RobotDownloadURDFResponse(BaseModel):
|
35
59
|
url: str = Field(..., title="Url")
|
36
60
|
md5_hash: str = Field(..., title="Md5 Hash")
|
@@ -44,12 +68,36 @@ class RobotResponse(BaseModel):
|
|
44
68
|
class_name: str = Field(..., title="Class Name")
|
45
69
|
|
46
70
|
|
71
|
+
class RobotURDFMetadataInput(BaseModel):
|
72
|
+
joint_name_to_metadata: Optional[Dict[str, JointMetadataInput]] = Field(None, title="Joint Name To Metadata")
|
73
|
+
|
74
|
+
|
75
|
+
class RobotURDFMetadataOutput(BaseModel):
|
76
|
+
joint_name_to_metadata: Optional[Dict[str, JointMetadataOutput]] = Field(None, title="Joint Name To Metadata")
|
77
|
+
|
78
|
+
|
79
|
+
class RobotUploadURDFRequest(BaseModel):
|
80
|
+
filename: str = Field(..., title="Filename")
|
81
|
+
content_type: str = Field(..., title="Content Type")
|
82
|
+
|
83
|
+
|
47
84
|
class RobotUploadURDFResponse(BaseModel):
|
48
85
|
url: str = Field(..., title="Url")
|
49
86
|
filename: str = Field(..., title="Filename")
|
50
87
|
content_type: str = Field(..., title="Content Type")
|
51
88
|
|
52
89
|
|
90
|
+
class UpdateRobotClassRequest(BaseModel):
|
91
|
+
new_class_name: Optional[str] = Field(None, title="New Class Name")
|
92
|
+
new_description: Optional[str] = Field(None, title="New Description")
|
93
|
+
new_metadata: Optional[RobotURDFMetadataInput] = None
|
94
|
+
|
95
|
+
|
96
|
+
class UpdateRobotRequest(BaseModel):
|
97
|
+
new_robot_name: Optional[str] = Field(None, title="New Robot Name")
|
98
|
+
new_description: Optional[str] = Field(None, title="New Description")
|
99
|
+
|
100
|
+
|
53
101
|
class UserResponse(BaseModel):
|
54
102
|
user_id: str = Field(..., title="User Id")
|
55
103
|
is_admin: bool = Field(..., title="Is Admin")
|
@@ -71,3 +119,11 @@ class ProfileResponse(BaseModel):
|
|
71
119
|
email: str = Field(..., title="Email")
|
72
120
|
email_verified: bool = Field(..., title="Email Verified")
|
73
121
|
user: UserResponse
|
122
|
+
|
123
|
+
|
124
|
+
class RobotClass(BaseModel):
|
125
|
+
id: str = Field(..., title="Id")
|
126
|
+
class_name: str = Field(..., title="Class Name")
|
127
|
+
description: str = Field(..., title="Description")
|
128
|
+
user_id: str = Field(..., title="User Id")
|
129
|
+
metadata: Optional[RobotURDFMetadataOutput] = None
|
@@ -1,4 +1,4 @@
|
|
1
|
-
kscale/__init__.py,sha256=
|
1
|
+
kscale/__init__.py,sha256=ApqkdyLhaMQ1u0TopxCm4T5hZTDoweO2b2gUg0djui8,200
|
2
2
|
kscale/cli.py,sha256=PMHLKR5UwdbbReVmqHXpJ-K9-mGHv_0I7KQkwxmFcUA,881
|
3
3
|
kscale/conf.py,sha256=dm35XSnzJp93St-ixVtYN4Nvqvb5upPGBrWkSI6Yb-4,1743
|
4
4
|
kscale/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -15,20 +15,20 @@ kscale/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
15
|
kscale/web/utils.py,sha256=Mme-FAQ0_zbjjOQeX8wyq8F4kL4i9fH7ytri16U6qOA,1046
|
16
16
|
kscale/web/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
17
|
kscale/web/cli/robot.py,sha256=rI-A4_0uvJPeA71Apl4Z3mV5fIfWkgmzT9JRmJYxz3A,3307
|
18
|
-
kscale/web/cli/robot_class.py,sha256=
|
18
|
+
kscale/web/cli/robot_class.py,sha256=Za5WZmyySgpY-hhR1BqyF1k1wr9bGbXLAE6uFlTOZkk,16706
|
19
19
|
kscale/web/cli/token.py,sha256=1rFC8MYKtqbNsQa2KIqwW1tqpaMtFaxuNsallwejXTU,787
|
20
20
|
kscale/web/cli/user.py,sha256=aaJJCL1P5lfhK6ZC9OwOHXKA-I3MWqVZ_k7TYnx33CY,1303
|
21
21
|
kscale/web/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
|
-
kscale/web/clients/base.py,sha256=
|
22
|
+
kscale/web/clients/base.py,sha256=A70BF9ZnHEBds0RKrowbeUNsX_WwsgV349csUlM4iMc,15174
|
23
23
|
kscale/web/clients/client.py,sha256=rzW2s8T7bKVuybOSQ65-ghl02rcXBoOxnx_nUDwgEPw,362
|
24
|
-
kscale/web/clients/robot.py,sha256=
|
25
|
-
kscale/web/clients/robot_class.py,sha256=
|
24
|
+
kscale/web/clients/robot.py,sha256=PI8HHkU-4Re9I5rLpp6dGbekRE-rBNVfXZxR_mO2MqE,1485
|
25
|
+
kscale/web/clients/robot_class.py,sha256=_oAEUglLmNXCspnACC40XLclLL3Z2rF88o1p0tk-JUM,6588
|
26
26
|
kscale/web/clients/user.py,sha256=jsa1_s6qXRM-AGBbHlPhd1NierUtynjY9tVAPNr6_Os,568
|
27
27
|
kscale/web/gen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
28
|
-
kscale/web/gen/api.py,sha256=
|
29
|
-
kscale-0.
|
30
|
-
kscale-0.
|
31
|
-
kscale-0.
|
32
|
-
kscale-0.
|
33
|
-
kscale-0.
|
34
|
-
kscale-0.
|
28
|
+
kscale/web/gen/api.py,sha256=7Uyzy4V2Ovh70M5lLSaJhwKkBr5SYyTfrNjr180JQhY,4304
|
29
|
+
kscale-0.3.0.dist-info/LICENSE,sha256=HCN2bImAzUOXldAZZI7JZ9PYq6OwMlDAP_PpX1HnuN0,1071
|
30
|
+
kscale-0.3.0.dist-info/METADATA,sha256=H3CQZAwUTZBfScRQVKAArLadR62aw-rWhFMUBkTby84,2340
|
31
|
+
kscale-0.3.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
32
|
+
kscale-0.3.0.dist-info/entry_points.txt,sha256=N_0pCpPnwGDYVzOeuaSOrbJkS5L3lS9d8CxpJF1f8UI,62
|
33
|
+
kscale-0.3.0.dist-info/top_level.txt,sha256=C2ynjYwopg6YjgttnI2dJjasyq3EKNmYp-IfQg9Xms4,7
|
34
|
+
kscale-0.3.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|