outleap 0.6.2__py3-none-any.whl → 0.7.1__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.
outleap/api_wrappers.py CHANGED
@@ -97,11 +97,11 @@ class LLWindowAPI(LEAPAPIWrapper):
97
97
  path: UI_PATH_TYPE,
98
98
  ) -> Dict:
99
99
  if keycode is not None:
100
- payload = {"keycode": keycode}
100
+ payload: Dict = {"keycode": keycode}
101
101
  elif keysym is not None:
102
- payload = {"keysym": keysym}
102
+ payload: Dict = {"keysym": keysym}
103
103
  elif char is not None:
104
- payload = {"char": char}
104
+ payload: Dict = {"char": char}
105
105
  else:
106
106
  raise ValueError("Didn't have one of keycode, keysym or char")
107
107
 
@@ -163,9 +163,7 @@ class LLWindowAPI(LEAPAPIWrapper):
163
163
 
164
164
  async def get_paths(self, under: Optional[UI_PATH_TYPE] = None) -> List[UIPath]:
165
165
  """Get all UI paths under the root, or under a path if specified"""
166
- if not under:
167
- under = ""
168
- resp = await self._client.command(self._pump_name, "getPaths", {"under": str(under)})
166
+ resp = await self._client.command(self._pump_name, "getPaths", {"under": str(under or "")})
169
167
  if error := resp.get("error"):
170
168
  raise ValueError(error)
171
169
  return [UIPath(path) for path in resp.get("paths", [])]
@@ -185,9 +183,9 @@ class LLWindowAPI(LEAPAPIWrapper):
185
183
  button: str = None,
186
184
  ) -> Dict:
187
185
  if path is not None:
188
- payload = {"path": str(path)}
186
+ payload: Dict = {"path": str(path)}
189
187
  elif x is not None and y is not None:
190
- payload = {"x": x, "y": y}
188
+ payload: Dict = {"x": x, "y": y}
191
189
  else:
192
190
  raise ValueError("Didn't have one of x + y or path")
193
191
 
@@ -381,7 +379,7 @@ class LLAgentAPI(LEAPAPIWrapper):
381
379
  lookat_type: int = 8,
382
380
  ):
383
381
  """Look at either a specific `obj_uuid` or the closest object to `position`"""
384
- payload = {"type": lookat_type}
382
+ payload: Dict = {"type": lookat_type}
385
383
  if obj_uuid:
386
384
  payload["obj_uuid"] = obj_uuid
387
385
  elif position:
@@ -391,9 +389,124 @@ class LLAgentAPI(LEAPAPIWrapper):
391
389
  self._client.void_command(self._pump_name, "lookAt", payload)
392
390
 
393
391
  def get_auto_pilot(self) -> Awaitable[Dict]:
394
- """Get information about current state of the autopilot system"""
392
+ """Get information about the current state of the autopilot system"""
395
393
  return self._client.command(self._pump_name, "getAutoPilot", {})
396
394
 
395
+ def get_agent_screen_pos(self, avatar_id: Optional[uuid.UUID] = None) -> Awaitable[Dict]:
396
+ """Get where the specified agent is on the screen, uses current agent's ID if none provided"""
397
+ payload = {}
398
+ if avatar_id:
399
+ payload["avatar_id"] = avatar_id
400
+ return self._client.command(self._pump_name, "getAgentScreenPos", payload)
401
+
402
+ def get_nearby_avatars(self, dist: Optional[float] = None) -> Awaitable[List[Dict]]:
403
+ payload = {}
404
+ if dist is not None:
405
+ payload["dist"] = dist
406
+ fut = self._client.command(self._pump_name, "getNearbyAvatarsList", payload)
407
+ return _data_unwrapper(fut, "result")
408
+
409
+ def get_nearby_objects(self, dist: Optional[float] = None) -> Awaitable[List[Dict]]:
410
+ payload = {}
411
+ if dist is not None:
412
+ payload["dist"] = dist
413
+ fut = self._client.command(self._pump_name, "getNearbyObjectsList", payload)
414
+ return _data_unwrapper(fut, "result")
415
+
416
+ def get_position(self) -> Awaitable[Dict]:
417
+ """Get details about the agent's position"""
418
+ return self._client.command(self._pump_name, "getPosition", {})
419
+
420
+ def request_sit(
421
+ self,
422
+ obj_uuid: Optional[uuid.UUID] = None,
423
+ position: Optional[Sequence[float]] = None,
424
+ ) -> Awaitable:
425
+ """Request to sit on obj_id, or object closest to position. Sits on ground if no args provided"""
426
+ if position and obj_uuid is not None:
427
+ raise ValueError("obj_uuid and position are mutually exclusive")
428
+
429
+ params = {}
430
+ if obj_uuid:
431
+ params["obj_uuid"] = obj_uuid
432
+ if position:
433
+ params["position"] = list(position)
434
+ return self._client.command(self._pump_name, "requestSit", params)
435
+
436
+ def request_stand(self) -> None:
437
+ self._client.void_command(self._pump_name, "requestStand", {})
438
+
439
+ def request_teleport(
440
+ self,
441
+ region_name: Optional[str] = None,
442
+ x: Optional[int] = None,
443
+ y: Optional[int] = None,
444
+ z: Optional[int] = None,
445
+ skip_confirmation: bool = True,
446
+ ) -> None:
447
+ """Request a teleport from the system, (x,y,z) are global if region_name unspecified"""
448
+ have_coords = all(_ is not None for _ in (x, y, z))
449
+ if not region_name and not have_coords:
450
+ raise ValueError("region_name or (x,y,z) is required")
451
+
452
+ params: Dict = {"skip_confirmation": skip_confirmation}
453
+ if region_name:
454
+ params["regionname"] = region_name
455
+ if have_coords:
456
+ params["x"] = x
457
+ params["y"] = y
458
+ params["z"] = z
459
+
460
+ self._client.void_command(self._pump_name, "requestTeleport", params)
461
+
462
+ def get_id(self) -> Awaitable[uuid.UUID]:
463
+ """Get the current agent's ID"""
464
+ return _data_unwrapper(self._client.command(self._pump_name, "getId", {}), "id")
465
+
466
+ def get_groups(self) -> Awaitable[List[Dict]]:
467
+ return _data_unwrapper(self._client.command(self._pump_name, "getGroups", {}), "groups")
468
+
469
+ def play_animation(self, item_id: uuid.UUID, inworld: bool = True) -> Awaitable:
470
+ """Play an animation by item id"""
471
+ return self._client.command(
472
+ self._pump_name, "playAnimation", {"item_id": item_id, "inworld": inworld}
473
+ )
474
+
475
+ def stop_animation(self, item_id: uuid.UUID) -> Awaitable:
476
+ """Stop an animation by item id"""
477
+ return self._client.command(self._pump_name, "stopAnimation", {"item_id": item_id})
478
+
479
+ def get_animation_info(self, item_id: uuid.UUID) -> Awaitable[Dict]:
480
+ """Get information about an animation by item id"""
481
+ return _data_unwrapper(
482
+ self._client.command(self._pump_name, "getAnimationInfo", {"item_id": item_id}), "anim_info"
483
+ )
484
+
485
+ def set_camera_params(self, params: Dict) -> None:
486
+ """Set camera parameters using LSL-like semantics"""
487
+ self._client.void_command(self._pump_name, "setCameraParams", params)
488
+
489
+ def set_follow_cam_active(self, active: bool) -> None:
490
+ self._client.void_command(self._pump_name, "setFollowCamActive", {"active": active})
491
+
492
+ def remove_camera_params(self) -> None:
493
+ self._client.void_command(self._pump_name, "removeCameraParams", {})
494
+
495
+ def request_touch(
496
+ self,
497
+ obj_uuid: Optional[uuid.UUID] = None,
498
+ position: Optional[Sequence[float]] = None,
499
+ face: int = 0,
500
+ ) -> None:
501
+ if not obj_uuid and not position:
502
+ raise ValueError("Must specify either obj_uuid or position")
503
+ params: Dict = {"face": face}
504
+ if obj_uuid:
505
+ params["obj_uuid"] = obj_uuid
506
+ if position:
507
+ params["position"] = list(position)
508
+ self._client.void_command(self._pump_name, "requestTouch", params)
509
+
397
510
 
398
511
  class LLFloaterRegAPI(LEAPAPIWrapper):
399
512
  PUMP_NAME = "LLFloaterReg"
@@ -466,7 +579,7 @@ class LLFloaterRegAPI(LEAPAPIWrapper):
466
579
  )
467
580
 
468
581
 
469
- class LLURLDispatcher(LEAPAPIWrapper):
582
+ class LLURLDispatcherAPI(LEAPAPIWrapper):
470
583
  PUMP_NAME = "LLUrlDispatcher"
471
584
 
472
585
  def dispatch(self, url: str, trusted: bool = True):
@@ -482,7 +595,7 @@ class LLURLDispatcher(LEAPAPIWrapper):
482
595
  self._client.void_command(self._pump_name, "dispatchFromTextEditor", {"url": url})
483
596
 
484
597
 
485
- class LLFloaterAbout(LEAPAPIWrapper):
598
+ class LLFloaterAboutAPI(LEAPAPIWrapper):
486
599
  PUMP_NAME = "LLFloaterAbout"
487
600
 
488
601
  def get_info(self) -> Awaitable[dict]:
@@ -490,7 +603,7 @@ class LLFloaterAbout(LEAPAPIWrapper):
490
603
  return self._client.command(self._pump_name, "getInfo")
491
604
 
492
605
 
493
- class LLGesture(LEAPAPIWrapper):
606
+ class LLGestureAPI(LEAPAPIWrapper):
494
607
  PUMP_NAME = "LLGesture"
495
608
 
496
609
  def get_active_gestures(self) -> Awaitable[list]:
@@ -516,26 +629,25 @@ class LLGesture(LEAPAPIWrapper):
516
629
  self._client.void_command(self._pump_name, "stopGesture", {"id": gesture_id})
517
630
 
518
631
 
519
- class GroupChat(LEAPAPIWrapper):
632
+ class GroupChatAPI(LEAPAPIWrapper):
520
633
  PUMP_NAME = "GroupChat"
521
634
 
522
- def start_im(self, group_id: uuid.UUID) -> Awaitable[uuid.UUID]:
523
- """Start an IM session for the specified group, returning the session ID"""
524
- fut = self._client.command(self._pump_name, "startIM", {"id": group_id})
525
- return _data_unwrapper(fut, "session_id")
635
+ def start_im(self, group_id: uuid.UUID) -> Awaitable:
636
+ """Start an IM session for the specified group"""
637
+ return self._client.command(self._pump_name, "startGroupChat", {"group_id": group_id})
526
638
 
527
639
  def end_im(self, group_id: uuid.UUID):
528
640
  """End an IM session with the specified group"""
529
- self._client.void_command(self._pump_name, "endIM", {"id": group_id})
641
+ return self._client.command(self._pump_name, "leaveGroupChat", {"group_id": group_id})
530
642
 
531
- def send_im(self, group_id: uuid.UUID, session_id: uuid.UUID, text: str):
532
- """Send an IM to the specified group with the specified chatterbox session ID"""
533
- self._client.void_command(
534
- self._pump_name, "sendIM", {"id": group_id, "session_id": session_id, "text": text}
643
+ def send_im(self, group_id: uuid.UUID, message: str) -> Awaitable:
644
+ """Send an IM to the specified group"""
645
+ return self._client.command(
646
+ self._pump_name, "sendGroupIM", {"group_id": group_id, "message": message}
535
647
  )
536
648
 
537
649
 
538
- class LLFloaterIMNearbyChat(LEAPAPIWrapper):
650
+ class LLChatBarAPI(LEAPAPIWrapper):
539
651
  PUMP_NAME = "LLChatBar"
540
652
 
541
653
  def send_chat(self, message: str, channel: int = 0, chat_type: str = "normal"):
@@ -551,7 +663,7 @@ class LLFloaterIMNearbyChat(LEAPAPIWrapper):
551
663
  )
552
664
 
553
665
 
554
- class LLAppViewer(LEAPAPIWrapper):
666
+ class LLAppViewerAPI(LEAPAPIWrapper):
555
667
  PUMP_NAME = "LLAppViewer"
556
668
 
557
669
  def request_quit(self):
@@ -561,40 +673,65 @@ class LLAppViewer(LEAPAPIWrapper):
561
673
  self._client.void_command(self._pump_name, "forceQuit")
562
674
 
563
675
 
564
- class LLPuppetryAPI(LEAPAPIWrapper):
565
- PUMP_NAME = "puppetry"
566
- OP_KEY: str = "command"
676
+ class LLTeleportHandlerAPI(LEAPAPIWrapper):
677
+ PUMP_NAME = "LLTeleportHandler"
567
678
 
568
- def get_camera(self) -> Awaitable[int]:
569
- """Request camera number: returns ["camera_id"]"""
570
- fut = self._client.command(self._pump_name, "get_camera", {}, op_key=self.OP_KEY)
571
- return _data_unwrapper(fut, "camera_id")
679
+ def teleport(
680
+ self,
681
+ region_name: Optional[str] = None,
682
+ x: Optional[int] = None,
683
+ y: Optional[int] = None,
684
+ z: Optional[int] = None,
685
+ ) -> None:
686
+ have_coords = all(_ is not None for _ in (x, y, z))
687
+ if not region_name and not have_coords:
688
+ raise ValueError("region_name or (x,y,z) is required")
572
689
 
573
- def set_camera(self, camera_id: int) -> None:
574
- """Request camera number: returns ["camera_id"]"""
575
- payload = {"camera_id": camera_id}
576
- self._client.void_command(self._pump_name, "set_camera", payload, op_key=self.OP_KEY)
690
+ params = {}
691
+ if region_name:
692
+ params["regionname"] = region_name
693
+ if have_coords:
694
+ params["x"] = x
695
+ params["y"] = y
696
+ params["z"] = z
577
697
 
578
- def send_skeleton(self) -> None:
579
- """
580
- Request skeleton data
698
+ self._client.void_command(self._pump_name, "teleport", params)
581
699
 
582
- Response will be sent over the "puppetry.command" listener as a "set_skeleton"
583
- """
584
- self._client.void_command(self._pump_name, "send_skeleton", {}, op_key=self.OP_KEY)
585
700
 
586
- def move(self, joint_data: Dict[str, Dict]) -> None:
587
- """
588
- Send puppet movement data
589
-
590
- Expected data format:
591
- {'joint_name':{'param_name':[r1.23,r4.56,r7.89]}, ...}
592
- Where:
593
- joint_name = e.g. mWristLeft
594
- param_name = rot | pos | scale | eff
595
- param value = array of 3 floats [x,y,z]
596
- """
597
- self._client.void_command(self._pump_name, "move", joint_data, op_key=self.OP_KEY)
701
+ class LLAppearanceAPI(LEAPAPIWrapper):
702
+ PUMP_NAME = "LLAppearance"
703
+
704
+ def wear_outfit(
705
+ self,
706
+ folder_id: Optional[uuid.UUID] = None,
707
+ folder_name: Optional[str] = None,
708
+ append: bool = False,
709
+ ) -> Awaitable:
710
+ params: Dict = {"append": append}
711
+ if folder_id:
712
+ params["folder_id"] = folder_id
713
+ if folder_name:
714
+ params["folder_name"] = folder_name
715
+ return self._client.command(self._pump_name, "wearOutfit", params)
716
+
717
+ def wear_items(self, item_ids: Sequence[uuid.UUID], replace: bool = False) -> None:
718
+ self._client.void_command(
719
+ self._pump_name, "wearItems", {"items_id": list(item_ids), "replace": replace}
720
+ )
721
+
722
+ def detach_items(self, item_ids: Sequence[uuid.UUID], replace: bool = False) -> None:
723
+ self._client.void_command(
724
+ self._pump_name, "detachItems", {"items_id": list(item_ids), "replace": replace}
725
+ )
726
+
727
+ def get_outfits_list(self) -> Awaitable[Dict[str, str]]:
728
+ return _data_unwrapper(self._client.command(self._pump_name, "getOutfitsList", {}), "outfits")
729
+
730
+ def get_outfit_items(self, outfit_id: uuid.UUID) -> Awaitable[Dict[str, dict]]:
731
+ return _data_unwrapper(
732
+ self._client.command(self._pump_name, "getOutfitItems", {"outfit_id": outfit_id}),
733
+ "items",
734
+ )
598
735
 
599
736
 
600
737
  __all__ = [
@@ -606,12 +743,13 @@ __all__ = [
606
743
  "LLViewerWindowAPI",
607
744
  "LLCommandDispatcherAPI",
608
745
  "LLFloaterRegAPI",
609
- "LLURLDispatcher",
610
- "LLFloaterAbout",
611
- "LLGesture",
612
- "GroupChat",
613
- "LLFloaterIMNearbyChat",
614
- "LLAppViewer",
615
- "LLPuppetryAPI",
746
+ "LLURLDispatcherAPI",
747
+ "LLFloaterAboutAPI",
748
+ "LLGestureAPI",
749
+ "GroupChatAPI",
750
+ "LLChatBarAPI",
751
+ "LLAppViewerAPI",
752
+ "LLTeleportHandlerAPI",
753
+ "LLAppearanceAPI",
616
754
  "LEAPAPIWrapper",
617
755
  ]
outleap/client.py CHANGED
@@ -100,6 +100,9 @@ class LEAPClient:
100
100
  self.handle_message(await self._protocol.read_message())
101
101
  except asyncio.IncompleteReadError:
102
102
  pass
103
+ except ConnectionResetError:
104
+ # This can happen on Windows rather than `IncompleteReadError`
105
+ pass
103
106
  finally:
104
107
  self.disconnect()
105
108
 
outleap/version.py CHANGED
@@ -5,7 +5,7 @@ from collections import namedtuple
5
5
  #: A namedtuple of the version info for the current release.
6
6
  _version_info = namedtuple("_version_info", "major minor micro status")
7
7
 
8
- parts = "0.6.2".split(".", 3)
8
+ parts = "0.7.1".split(".", 3)
9
9
  version_info = _version_info(
10
10
  int(parts[0]),
11
11
  int(parts[1]),
@@ -16,4 +16,4 @@ version_info = _version_info(
16
16
  # Remove everything but the 'version_info' from this module.
17
17
  del namedtuple, _version_info, parts
18
18
 
19
- __version__ = "0.6.2"
19
+ __version__ = "0.7.1"
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: outleap
3
- Version: 0.6.2
3
+ Version: 0.7.1
4
4
  Summary: Tools for using asyncio to control a Second Life viewer over the LEAP protocol
5
5
  Author-email: Salad Dais <83434023+SaladDais@users.noreply.github.com>
6
6
  License: MIT License
@@ -20,12 +20,13 @@ Classifier: Programming Language :: Python :: 3.12
20
20
  Requires-Python: >=3.8
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: COPYING
23
- Requires-Dist: llsd ==1.0.0
23
+ Requires-Dist: llsd==1.0.0
24
24
  Requires-Dist: setuptools
25
25
  Provides-Extra: tools
26
- Requires-Dist: ptpython <4,>=3 ; extra == 'tools'
27
- Requires-Dist: qasync ; extra == 'tools'
28
- Requires-Dist: pyside6-essentials ; extra == 'tools'
26
+ Requires-Dist: ptpython<4,>=3; extra == "tools"
27
+ Requires-Dist: qasync; extra == "tools"
28
+ Requires-Dist: pyside6-essentials; extra == "tools"
29
+ Dynamic: license-file
29
30
 
30
31
  # outleap
31
32
 
@@ -1,20 +1,20 @@
1
1
  outleap/__init__.py,sha256=h0VTP95rggZ-iFH1Ke4rh85fiU3BTvfj_jkLW27Ln1k,689
2
- outleap/api_wrappers.py,sha256=bohxbm_5vFsQ9Dr_1UQHD-jsOoNd9cXad0RrOcDfER0,21308
2
+ outleap/api_wrappers.py,sha256=SZWBLTA6R0zv-nsZ4pmlm92lWsELoq_5t36WhFoRdx8,26785
3
3
  outleap/bridge.py,sha256=Q5tku2JP-K0wT9zVA8OyJpFEpumgbUgHaC1YeRQR8vE,1016
4
- outleap/client.py,sha256=I-hVicIEBKcfZBRuG3hQUO-N-pbmfLECQfpLl7323IQ,14404
4
+ outleap/client.py,sha256=4RFTOSiL2cCwpJ64_sux_DbFh_LjnwjVIlByutGg-ao,14545
5
5
  outleap/protocol.py,sha256=HeCnlzszGp6Yxgq__34KYYuii6tpxnfUi5La9SFxw0k,3725
6
6
  outleap/qt_helpers.py,sha256=qpdEx7y60T3nAJ4GFDvpcefKEgpgBBnt8dQ-c2IB-Zc,7766
7
7
  outleap/ui_elems.py,sha256=ndi3FDp_fu1rVD3rNpFp6hs8eU53U4D_dCtuwFmums0,11577
8
8
  outleap/utils.py,sha256=Ad1-mAU0ButNp8n4KeXQxxNa7AtFRuZWD5L2RVS5vLM,5863
9
- outleap/version.py,sha256=987ruM7FKIuGGyZCOfIaKjb_aMixEGnBSUmP9Iy9s6Y,517
9
+ outleap/version.py,sha256=peWte9DMk6zS-DsPk9CFvGk2AQQ8bihUD-NMrs4elGc,517
10
10
  outleap/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  outleap/scripts/agent.py,sha256=YOXtkjqZY7ju9fLhBHRzdhsp9TdhPnDzsXiQo5-Dhfs,1984
12
12
  outleap/scripts/inspector.py,sha256=h_2So4ohXlE4qOcYzfFRFiKdMItXmPFnVh50KwfoXmM,10950
13
13
  outleap/scripts/repl.py,sha256=z20hpXY5_O68U2WX1Il5tRGRyafsKHa-FNeF9CoJpZk,1721
14
14
  outleap/scripts/ui/inspector.ui,sha256=CY-Tczsrpmc2groE6_7s_fIc7de6Oy2TuU2a4ZGzl7o,7269
15
- outleap-0.6.2.dist-info/COPYING,sha256=OPIPYHZUdL5ylRYaujIVg05CSvX4-AHW15qBGYz_fyY,1093
16
- outleap-0.6.2.dist-info/METADATA,sha256=doGy6abirwG6AIRMSv02kJLsIYba_dCOLn3f-FjjU_g,4802
17
- outleap-0.6.2.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
18
- outleap-0.6.2.dist-info/entry_points.txt,sha256=gi7UECBDlCTzn0jUU8xPvyMm_8lOQMZUJuNOe266I48,174
19
- outleap-0.6.2.dist-info/top_level.txt,sha256=dIQJmMy2IulmIzvBWuz8dup0dm9MV0b4p_hCtBRewfE,8
20
- outleap-0.6.2.dist-info/RECORD,,
15
+ outleap-0.7.1.dist-info/licenses/COPYING,sha256=OPIPYHZUdL5ylRYaujIVg05CSvX4-AHW15qBGYz_fyY,1093
16
+ outleap-0.7.1.dist-info/METADATA,sha256=Zp9FZl0H8SDUIUmXIHXfv3dfChA4fH4XMS8AXyWNNRw,4819
17
+ outleap-0.7.1.dist-info/WHEEL,sha256=ck4Vq1_RXyvS4Jt6SI0Vz6fyVs4GWg7AINwpsaGEgPE,91
18
+ outleap-0.7.1.dist-info/entry_points.txt,sha256=gi7UECBDlCTzn0jUU8xPvyMm_8lOQMZUJuNOe266I48,174
19
+ outleap-0.7.1.dist-info/top_level.txt,sha256=dIQJmMy2IulmIzvBWuz8dup0dm9MV0b4p_hCtBRewfE,8
20
+ outleap-0.7.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.42.0)
2
+ Generator: setuptools (80.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5