diff --git a/.idea/grpc_client.sync-conflict-20260318-013404-55JW2V4.iml b/.idea/grpc_client.sync-conflict-20260318-013404-55JW2V4.iml
new file mode 100644
index 0000000..1675d09
--- /dev/null
+++ b/.idea/grpc_client.sync-conflict-20260318-013404-55JW2V4.iml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.sync-conflict-20260318-013404-55JW2V4.xml b/.idea/misc.sync-conflict-20260318-013404-55JW2V4.xml
new file mode 100644
index 0000000..accb38f
--- /dev/null
+++ b/.idea/misc.sync-conflict-20260318-013404-55JW2V4.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.sync-conflict-20260318-013404-55JW2V4.xml b/.idea/workspace.sync-conflict-20260318-013404-55JW2V4.xml
new file mode 100644
index 0000000..d286775
--- /dev/null
+++ b/.idea/workspace.sync-conflict-20260318-013404-55JW2V4.xml
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1773704484327
+
+
+ 1773704484327
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.sync-conflict-20260326-143838-55JW2V4.xml b/.idea/workspace.sync-conflict-20260326-143838-55JW2V4.xml
new file mode 100644
index 0000000..9d758e2
--- /dev/null
+++ b/.idea/workspace.sync-conflict-20260326-143838-55JW2V4.xml
@@ -0,0 +1,247 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ "customColor": "",
+ "associatedIndex": 3
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ C:\Users\lgv\AppData\Roaming\Subversion
+
+
+
+
+ 1773737578904
+
+
+ 1773737578904
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.sync-conflict-20260402-100016-55JW2V4.xml b/.idea/workspace.sync-conflict-20260402-100016-55JW2V4.xml
new file mode 100644
index 0000000..78431af
--- /dev/null
+++ b/.idea/workspace.sync-conflict-20260402-100016-55JW2V4.xml
@@ -0,0 +1,272 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ "customColor": "",
+ "associatedIndex": 3
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ C:\Users\lgv\AppData\Roaming\Subversion
+
+
+
+
+ 1773737578904
+
+
+ 1773737578904
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 002e6fd..bfae643 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,13 @@
## Setup
```bash
-pip install -r requirements.txt
+python -m pip install -r requirements.txt
+```
+
+Use the same Python interpreter you run the clients with. For example:
+
+```bash
+C:\Users\lgv\miniconda3\envs\grpc_client\python.exe -m pip install -r requirements.txt
```
## Generate gRPC code
@@ -29,9 +35,9 @@ python -m clients.get_pose_client
python -m clients.torque_on_client
python -m clients.torque_off_client
python -m clients.torque_key_client
-python -m clients.press_client
python -m clients.save_joint_to_csv
python -m clients.http_server
+python -m clients.hlc_client
```
## UI (PyQt)
diff --git a/clients/__pycache__/__init__.cpython-39.pyc b/clients/__pycache__/__init__.cpython-39.pyc
index e6ee043..775872a 100644
Binary files a/clients/__pycache__/__init__.cpython-39.pyc and b/clients/__pycache__/__init__.cpython-39.pyc differ
diff --git a/clients/__pycache__/_path_setup.cpython-39.pyc b/clients/__pycache__/_path_setup.cpython-39.pyc
index 889b4a6..29ba91f 100644
Binary files a/clients/__pycache__/_path_setup.cpython-39.pyc and b/clients/__pycache__/_path_setup.cpython-39.pyc differ
diff --git a/clients/__pycache__/base_client.cpython-39.pyc b/clients/__pycache__/base_client.cpython-39.pyc
index a76c039..3dc1267 100644
Binary files a/clients/__pycache__/base_client.cpython-39.pyc and b/clients/__pycache__/base_client.cpython-39.pyc differ
diff --git a/clients/__pycache__/camera_client.cpython-39.pyc b/clients/__pycache__/camera_client.cpython-39.pyc
new file mode 100644
index 0000000..5d8ac4d
Binary files /dev/null and b/clients/__pycache__/camera_client.cpython-39.pyc differ
diff --git a/clients/__pycache__/get_rgb_image_stream_client.cpython-39.pyc b/clients/__pycache__/get_rgb_image_stream_client.cpython-39.pyc
new file mode 100644
index 0000000..a01f28d
Binary files /dev/null and b/clients/__pycache__/get_rgb_image_stream_client.cpython-39.pyc differ
diff --git a/clients/__pycache__/hand_client.cpython-39.pyc b/clients/__pycache__/hand_client.cpython-39.pyc
index 5fa591a..22c543f 100644
Binary files a/clients/__pycache__/hand_client.cpython-39.pyc and b/clients/__pycache__/hand_client.cpython-39.pyc differ
diff --git a/clients/__pycache__/hlc_client.cpython-39.pyc b/clients/__pycache__/hlc_client.cpython-39.pyc
new file mode 100644
index 0000000..047f665
Binary files /dev/null and b/clients/__pycache__/hlc_client.cpython-39.pyc differ
diff --git a/clients/__pycache__/movej_client.cpython-39.pyc b/clients/__pycache__/movej_client.cpython-39.pyc
index 4aa8f39..4ef95f7 100644
Binary files a/clients/__pycache__/movej_client.cpython-39.pyc and b/clients/__pycache__/movej_client.cpython-39.pyc differ
diff --git a/clients/__pycache__/torque_off_client.cpython-39.pyc b/clients/__pycache__/torque_off_client.cpython-39.pyc
index 0b80783..16f213d 100644
Binary files a/clients/__pycache__/torque_off_client.cpython-39.pyc and b/clients/__pycache__/torque_off_client.cpython-39.pyc differ
diff --git a/clients/__pycache__/torque_on_client.cpython-39.pyc b/clients/__pycache__/torque_on_client.cpython-39.pyc
index 956d347..3700380 100644
Binary files a/clients/__pycache__/torque_on_client.cpython-39.pyc and b/clients/__pycache__/torque_on_client.cpython-39.pyc differ
diff --git a/clients/base_client.py b/clients/base_client.py
index 995770b..eb4b629 100644
--- a/clients/base_client.py
+++ b/clients/base_client.py
@@ -17,9 +17,14 @@ class RobotClientBase:
self.retries = retries
self.channel = None
self.stub = None
+ self.stub_hand = None
print(f"Attempting to connect to gRPC server: {self.address}")
self._connect_with_retry()
+ def _init_stubs(self, channel):
+ self.stub = humanoid_robot_service_pb2_grpc.HumanoidRobotServiceStub(channel)
+ self.stub_hand = dexhand_service_pb2_grpc.DexHandServiceStub(channel)
+
def _connect_with_retry(self):
attempt = 0
while attempt <= self.retries:
@@ -27,8 +32,7 @@ class RobotClientBase:
try:
self.channel = grpc.insecure_channel(self.address)
grpc.channel_ready_future(self.channel).result(timeout=self.timeout)
- self.stub = humanoid_robot_service_pb2_grpc.HumanoidRobotServiceStub(self.channel)
- self.stub_hand = dexhand_service_pb2_grpc.DexHandServiceStub(self.channel)
+ self._init_stubs(self.channel)
print(f"Successfully connected to gRPC server: {self.address}")
return
except grpc.FutureTimeoutError:
diff --git a/clients/camera_client.py b/clients/camera_client.py
new file mode 100644
index 0000000..53cdb2f
--- /dev/null
+++ b/clients/camera_client.py
@@ -0,0 +1,67 @@
+import time
+from typing import Callable, Optional
+
+from google.protobuf.timestamp_pb2 import Timestamp
+
+from clients._path_setup import ensure_paths
+
+ensure_paths()
+
+from clients.base_client import RobotClientBase
+from cmvr.api import camera_command_pb2 as pb
+from cmvr.api import camera_service_pb2_grpc as rpc
+from cmvr.api import common_pb2
+
+
+class CameraClient(RobotClientBase):
+ """Client for CameraService RGB image RPCs."""
+
+ def _init_stubs(self, channel):
+ self.stub = rpc.CameraServiceStub(channel)
+
+ @staticmethod
+ def _make_header(device_id: str):
+ header = common_pb2.CommandHeader.Request()
+ header.device_id = device_id
+ timestamp = Timestamp()
+ timestamp.GetCurrentTime()
+ header.timestamp.CopyFrom(timestamp)
+ return header
+
+ def start_camera(self, device_id: str):
+ request = pb.StartCameraCommand.Request(header=self._make_header(device_id))
+ return self.stub.StartCamera(request, timeout=self.timeout)
+
+ def stop_camera(self, device_id: str):
+ request = pb.StopCameraCommand.Request(header=self._make_header(device_id))
+ return self.stub.StopCamera(request, timeout=self.timeout)
+
+ def get_rgb_image(self, device_id: str):
+ request = pb.GetRGBImageCommand.Request(header=self._make_header(device_id))
+ return self.stub.GetRGBImage(request, timeout=self.timeout)
+
+ def get_rgb_image_stream(
+ self,
+ device_id: str,
+ stop_requested: Optional[Callable[[], bool]] = None,
+ request_interval: float = 0.001,
+ ):
+ should_stop = stop_requested or (lambda: False)
+
+ def request_gen():
+ while not should_stop():
+ yield pb.GetRGBImageStreamCommand.Request(
+ header=self._make_header(device_id),
+ eof=False,
+ )
+ time.sleep(request_interval)
+
+ yield pb.GetRGBImageStreamCommand.Request(
+ header=self._make_header(device_id),
+ eof=True,
+ )
+
+ return self.stub.GetRGBImageStream(request_gen())
+
+
+GetRGBImageStreamClient = CameraClient
diff --git a/clients/gui_camera_client..py b/clients/gui_camera_client..py
deleted file mode 100644
index 6ef05b4..0000000
--- a/clients/gui_camera_client..py
+++ /dev/null
@@ -1,727 +0,0 @@
-# camera_player_qt.py
-import sys
-import cv2
-import av
-import numpy as np
-import grpc
-import threading
-import time
-from datetime import datetime
-from pathlib import Path
-from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
- QHBoxLayout, QLabel, QPushButton, QLineEdit,
- QFileDialog, QMessageBox, QStatusBar, QGroupBox,
- QGridLayout, QComboBox, QSpinBox, QCheckBox, QFrame)
-from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer
-from PyQt5.QtGui import QImage, QPixmap
-
-# 导入生成的protobuf模块
-from cmvr.api import camera_service_pb2_grpc
-from cmvr.api import camera_command_pb2
-from cmvr.api.common_pb2 import CommandHeader
-from google.protobuf.timestamp_pb2 import Timestamp
-
-
-class StreamThread(QThread):
- """流接收线程"""
- frame_received = pyqtSignal(object, int, int) # frame, width, height
- status_update = pyqtSignal(dict)
- error_occurred = pyqtSignal(str)
- keyframe_received = pyqtSignal()
-
- # 支持的编码格式
- CODECS = {
- 'H.265 (HEVC)': 'hevc',
- 'H.264 (AVC)': 'h264'
- }
-
- def __init__(self, device_id, server_addr, codec='hevc', wait_for_keyframe=True):
- super().__init__()
- self.device_id = device_id
- self.server_addr = server_addr
- self.codec_name = codec
- self.wait_for_keyframe = wait_for_keyframe
- self.running = False
- self.streaming = False
-
- # gRPC
- self.channel = None
- self.stub = None
-
- # 解码器
- self.codec = None
- self._init_decoder()
-
- # 统计
- self.frame_count = 0
- self.fps_counter = 0
- self.fps_time = time.time()
- self.fps = 0
- self.keyframe_count = 0
-
- def _init_decoder(self):
- """初始化解码器"""
- try:
- # 根据选择的编码创建解码器
- self.codec = av.CodecContext.create(self.codec_name, "r")
- self.codec.thread_count = 1
-
- # 设置低延迟模式
- if hasattr(self.codec, 'flags'):
- try:
- self.codec.flags |= av.codec.context.AV_CODEC_FLAG_LOW_DELAY
- except:
- pass
-
- print(f"✓ {self.codec_name.upper()}解码器初始化成功")
- except Exception as e:
- print(f"✗ 解码器初始化失败: {e}")
- self.codec = None
-
- def _create_header(self):
- """创建请求头"""
- timestamp = Timestamp()
- timestamp.GetCurrentTime()
- return CommandHeader.Request(
- device_id=self.device_id,
- timestamp=timestamp
- )
-
- def connect(self):
- """连接服务器"""
- try:
- self.channel = grpc.insecure_channel(self.server_addr)
- grpc.channel_ready_future(self.channel).result(timeout=3)
- self.stub = camera_service_pb2_grpc.CameraServiceStub(self.channel)
- return True
- except Exception as e:
- self.error_occurred.emit(f"连接失败: {e}")
- return False
-
- def start_camera(self):
- """启动摄像头"""
- try:
- request = camera_command_pb2.StartCameraCommand.Request(
- header=self._create_header()
- )
- response = self.stub.StartCamera(request, timeout=3)
- return response.header.success
- except Exception as e:
- self.error_occurred.emit(f"启动摄像头失败: {e}")
- return False
-
- def stop_camera(self):
- """停止摄像头"""
- try:
- request = camera_command_pb2.StopCameraCommand.Request(
- header=self._create_header()
- )
- self.stub.StopCamera(request, timeout=3)
- except:
- pass
-
- def run(self):
- """线程主函数"""
- if not self.connect():
- return
-
- if not self.start_camera():
- return
-
- self.running = True
- self.streaming = True
- self.frame_count = 0
- self.fps_counter = 0
- self.fps_time = time.time()
- keyframe_received = False
-
- def request_gen():
- while self.streaming:
- yield camera_command_pb2.GetRGBImageStreamCommand.Request(
- header=self._create_header(),
- eof=False
- )
- time.sleep(0.001)
- yield camera_command_pb2.GetRGBImageStreamCommand.Request(
- header=self._create_header(),
- eof=True
- )
-
- try:
- responses = self.stub.GetRGBImageStream(request_gen())
-
- for response in responses:
- if not self.streaming:
- break
-
- if response.header.success:
- frame_data = response.color_frame.data
- is_keyframe = response.color_frame.is_key_frame
-
- # 等待首个关键帧
- if self.wait_for_keyframe and not keyframe_received and not is_keyframe:
- continue
-
- if is_keyframe and not keyframe_received:
- keyframe_received = True
- self.keyframe_count += 1
- self.keyframe_received.emit()
-
- # 解码
- if self.codec and frame_data:
- packet = av.Packet(frame_data)
- frames = self.codec.decode(packet)
-
- if frames:
- # 转换为RGB (已经是RGB格式)
- img = frames[0].to_ndarray(format='rgb24')
-
- # 更新统计
- self.fps_counter += 1
- self.frame_count += 1
-
- # 计算FPS
- now = time.time()
- if now - self.fps_time >= 1.0:
- self.fps = self.fps_counter
- self.fps_counter = 0
- self.fps_time = now
-
- # 发送状态更新
- self.status_update.emit({
- 'fps': self.fps,
- 'frames': self.frame_count,
- 'keyframes': self.keyframe_count
- })
-
- # 发送帧到主线程
- h, w = img.shape[:2]
- self.frame_received.emit(img, w, h)
-
- else:
- print(f"流错误: {response.header.error_message}")
-
- except Exception as e:
- self.error_occurred.emit(f"流错误: {e}")
- finally:
- self.stop_camera()
- if self.channel:
- self.channel.close()
- self.running = False
- self.streaming = False
-
- def stop(self):
- """停止线程"""
- self.streaming = False
- self.wait(2000) # 等待最多2秒
-
-
-class CameraPlayerQt(QMainWindow):
- """基于Qt的摄像头拉流播放器"""
-
- def __init__(self):
- super().__init__()
- self.setWindowTitle("摄像头拉流播放器 (Qt版)")
- self.setMinimumSize(1000, 800)
-
- # 状态变量
- self.connected = False
- self.streaming = False
- self.recording = False
- self.stream_thread = None
- self.video_writer = None
-
- # 录制目录
- self.record_path = Path("./recordings")
- self.record_path.mkdir(exist_ok=True)
-
- # 创建UI
- self._create_ui()
-
- # 状态栏
- self.status_bar = QStatusBar()
- self.setStatusBar(self.status_bar)
- self.status_bar.showMessage("就绪")
-
- # 定时器用于更新显示
- self.display_timer = QTimer()
- self.display_timer.timeout.connect(self._update_display)
- self.display_timer.start(30) # 约33fps
-
- # 当前帧缓存
- self.current_frame = None
- self.frame_width = 0
- self.frame_height = 0
-
- def _create_ui(self):
- """创建UI"""
- central_widget = QWidget()
- self.setCentralWidget(central_widget)
- main_layout = QVBoxLayout(central_widget)
-
- # ========== 连接设置区域 ==========
- conn_group = QGroupBox("连接设置")
- conn_layout = QGridLayout()
-
- # 第一行:设备ID和编码格式
- conn_layout.addWidget(QLabel("设备ID:"), 0, 0)
- self.device_id = QLineEdit("right_hand_cam")
- conn_layout.addWidget(self.device_id, 0, 1)
-
- conn_layout.addWidget(QLabel("编码格式:"), 0, 2)
- self.codec_combo = QComboBox()
- self.codec_combo.addItems(['H.265 (HEVC)', 'H.264 (AVC)'])
- self.codec_combo.setCurrentIndex(0)
- self.codec_combo.setMinimumWidth(120)
- conn_layout.addWidget(self.codec_combo, 0, 3)
-
- # 等待关键帧选项
- self.wait_keyframe = QCheckBox("等待关键帧")
- self.wait_keyframe.setChecked(True)
- conn_layout.addWidget(self.wait_keyframe, 0, 4)
-
- # 第二行:服务器地址
- conn_layout.addWidget(QLabel("服务器IP:"), 1, 0)
- self.server_ip = QLineEdit("192.168.0.222")
- conn_layout.addWidget(self.server_ip, 1, 1)
-
- conn_layout.addWidget(QLabel("端口:"), 1, 2)
- self.server_port = QLineEdit("50052")
- self.server_port.setMaximumWidth(80)
- conn_layout.addWidget(self.server_port, 1, 3)
-
- # 超时设置
- conn_layout.addWidget(QLabel("超时(秒):"), 1, 4)
- self.timeout_spin = QSpinBox()
- self.timeout_spin.setRange(1, 10)
- self.timeout_spin.setValue(3)
- self.timeout_spin.setMaximumWidth(60)
- conn_layout.addWidget(self.timeout_spin, 1, 5)
-
- # 第三行:录制目录
- conn_layout.addWidget(QLabel("录制目录:"), 2, 0)
- self.record_dir = QLineEdit(str(self.record_path))
- conn_layout.addWidget(self.record_dir, 2, 1, 1, 4)
-
- self.browse_btn = QPushButton("浏览")
- self.browse_btn.clicked.connect(self._browse_dir)
- conn_layout.addWidget(self.browse_btn, 2, 5)
-
- # 第四行:控制按钮
- btn_layout = QHBoxLayout()
-
- self.connect_btn = QPushButton("连接")
- self.connect_btn.clicked.connect(self._connect)
- self.connect_btn.setMinimumWidth(80)
- btn_layout.addWidget(self.connect_btn)
-
- self.preview_btn = QPushButton("预览")
- self.preview_btn.clicked.connect(self._toggle_preview)
- self.preview_btn.setEnabled(False)
- self.preview_btn.setMinimumWidth(80)
- btn_layout.addWidget(self.preview_btn)
-
- self.record_btn = QPushButton("录制")
- self.record_btn.clicked.connect(self._toggle_recording)
- self.record_btn.setEnabled(False)
- self.record_btn.setMinimumWidth(80)
- btn_layout.addWidget(self.record_btn)
-
- self.disconnect_btn = QPushButton("断开")
- self.disconnect_btn.clicked.connect(self._disconnect)
- self.disconnect_btn.setEnabled(False)
- self.disconnect_btn.setMinimumWidth(80)
- btn_layout.addWidget(self.disconnect_btn)
-
- btn_layout.addStretch()
-
- conn_layout.addLayout(btn_layout, 3, 0, 1, 6)
-
- conn_group.setLayout(conn_layout)
- main_layout.addWidget(conn_group)
-
- # ========== 状态显示区域 ==========
- status_group = QGroupBox("状态信息")
- status_layout = QHBoxLayout()
-
- self.conn_status = QLabel("● 未连接")
- self.conn_status.setStyleSheet("color: red;")
- status_layout.addWidget(self.conn_status)
-
- status_layout.addWidget(QLabel("|"))
-
- self.codec_status = QLabel("编码: H.265")
- status_layout.addWidget(self.codec_status)
-
- status_layout.addWidget(QLabel("|"))
-
- self.fps_status = QLabel("FPS: 0")
- status_layout.addWidget(self.fps_status)
-
- status_layout.addWidget(QLabel("|"))
-
- self.frame_status = QLabel("帧数: 0")
- status_layout.addWidget(self.frame_status)
-
- status_layout.addWidget(QLabel("|"))
-
- self.resolution_status = QLabel("分辨率: -")
- status_layout.addWidget(self.resolution_status)
-
- status_layout.addWidget(QLabel("|"))
-
- self.keyframe_status = QLabel("关键帧: 等待")
- self.keyframe_status.setStyleSheet("color: orange;")
- status_layout.addWidget(self.keyframe_status)
-
- status_layout.addWidget(QLabel("|"))
-
- self.record_status = QLabel("录制: 未开始")
- self.record_status.setStyleSheet("color: gray;")
- status_layout.addWidget(self.record_status)
-
- status_layout.addStretch()
- status_group.setLayout(status_layout)
- main_layout.addWidget(status_group)
-
- # ========== 视频预览区域 ==========
- video_group = QGroupBox("视频预览")
- video_layout = QVBoxLayout()
-
- self.video_label = QLabel()
- self.video_label.setAlignment(Qt.AlignCenter)
- self.video_label.setMinimumSize(640, 480)
- self.video_label.setStyleSheet("background-color: black; border: 1px solid gray;")
- video_layout.addWidget(self.video_label)
-
- video_group.setLayout(video_layout)
- main_layout.addWidget(video_group, 1) # 1表示拉伸因子
-
- # ========== 操作说明 ==========
- info_frame = QFrame()
- info_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken)
- info_layout = QHBoxLayout(info_frame)
-
- info_label = QLabel("操作说明: ①选择编码格式 ②连接 ③预览 ④录制 (按'停止预览'结束)")
- info_label.setStyleSheet("color: gray; padding: 5px;")
- info_layout.addWidget(info_label)
-
- main_layout.addWidget(info_frame)
-
- def _browse_dir(self):
- """浏览目录"""
- directory = QFileDialog.getExistingDirectory(self, "选择录制目录",
- self.record_dir.text())
- if directory:
- self.record_dir.setText(directory)
- self.record_path = Path(directory)
- self.record_path.mkdir(exist_ok=True)
-
- def _get_server_addr(self):
- """获取服务器地址"""
- return f"{self.server_ip.text()}:{self.server_port.text()}"
-
- def _get_codec(self):
- """获取编码格式"""
- codec_map = {
- 'H.265 (HEVC)': 'hevc',
- 'H.264 (AVC)': 'h264'
- }
- return codec_map[self.codec_combo.currentText()]
-
- def _connect(self):
- """连接服务器"""
- codec = self._get_codec()
- self.status_bar.showMessage(f"正在连接服务器 (编码: {codec.upper()})...")
-
- # 更新编码状态显示
- self.codec_status.setText(f"编码: {self.codec_combo.currentText()}")
-
- # 创建流线程
- self.stream_thread = StreamThread(
- self.device_id.text(),
- self._get_server_addr(),
- codec=codec,
- wait_for_keyframe=self.wait_keyframe.isChecked()
- )
-
- # 连接信号
- self.stream_thread.frame_received.connect(self._on_frame_received)
- self.stream_thread.status_update.connect(self._on_status_update)
- self.stream_thread.error_occurred.connect(self._on_error)
- self.stream_thread.keyframe_received.connect(self._on_keyframe_received)
-
- # 连接测试
- if self.stream_thread.connect():
- self.connected = True
- self.conn_status.setText("● 已连接")
- self.conn_status.setStyleSheet("color: green;")
- self.connect_btn.setEnabled(False)
- self.preview_btn.setEnabled(True)
- self.disconnect_btn.setEnabled(True)
- self.codec_combo.setEnabled(False)
- self.wait_keyframe.setEnabled(False)
- self.status_bar.showMessage("服务器连接成功", 3000)
- else:
- self.stream_thread = None
-
- def _disconnect(self):
- """断开连接"""
- # 停止预览
- if self.streaming:
- self._stop_preview()
-
- # 停止录制
- if self.recording:
- self._stop_recording()
-
- # 清理线程
- if self.stream_thread:
- self.stream_thread.stop()
- self.stream_thread = None
-
- self.connected = False
- self.conn_status.setText("● 未连接")
- self.conn_status.setStyleSheet("color: red;")
- self.connect_btn.setEnabled(True)
- self.preview_btn.setEnabled(False)
- self.preview_btn.setText("预览")
- self.record_btn.setEnabled(False)
- self.record_btn.setText("录制")
- self.disconnect_btn.setEnabled(False)
- self.codec_combo.setEnabled(True)
- self.wait_keyframe.setEnabled(True)
-
- # 清空画面
- self.video_label.clear()
- self.current_frame = None
-
- # 重置状态
- self.keyframe_status.setText("关键帧: 等待")
- self.keyframe_status.setStyleSheet("color: orange;")
- self.fps_status.setText("FPS: 0")
- self.frame_status.setText("帧数: 0")
- self.resolution_status.setText("分辨率: -")
-
- self.status_bar.showMessage("已断开连接", 3000)
-
- def _toggle_preview(self):
- """切换预览"""
- if not self.streaming:
- self._start_preview()
- else:
- self._stop_preview()
-
- def _start_preview(self):
- """开始预览"""
- if not self.stream_thread:
- return
-
- self.streaming = True
- self.stream_thread.streaming = True
- self.stream_thread.start()
-
- self.preview_btn.setText("停止")
- self.record_btn.setEnabled(True)
-
- if self.wait_keyframe.isChecked():
- self.keyframe_status.setText("关键帧: 等待中...")
- self.keyframe_status.setStyleSheet("color: orange;")
- else:
- self.keyframe_status.setText("关键帧: 不等待")
- self.keyframe_status.setStyleSheet("color: gray;")
-
- self.status_bar.showMessage("预览已启动...")
-
- def _stop_preview(self):
- """停止预览"""
- if self.stream_thread:
- self.stream_thread.streaming = False
- self.stream_thread.wait(2000)
-
- self.streaming = False
- self.preview_btn.setText("预览")
- self.record_btn.setEnabled(False)
-
- # 停止录制
- if self.recording:
- self._stop_recording()
-
- self.keyframe_status.setText("关键帧: 等待")
- self.keyframe_status.setStyleSheet("color: orange;")
-
- # 清空画面
- self.video_label.clear()
- self.current_frame = None
-
- self.status_bar.showMessage("预览已停止", 3000)
-
- def _toggle_recording(self):
- """切换录制"""
- if not self.recording:
- self._start_recording()
- else:
- self._stop_recording()
-
- def _start_recording(self):
- """开始录制"""
- if not self.streaming:
- QMessageBox.warning(self, "警告", "请先启动预览")
- return
-
- # 更新录制目录
- self.record_path = Path(self.record_dir.text())
- self.record_path.mkdir(parents=True, exist_ok=True)
-
- # 生成文件名
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
- codec_name = self.codec_combo.currentText().replace(' ', '_')
- filename = self.record_path / f"rec_{self.device_id.text()}_{codec_name}_{timestamp}.mp4"
-
- # 获取画面尺寸
- if self.current_frame is not None:
- height, width = self.current_frame.shape[:2]
- else:
- width, height = 640, 480
-
- # 创建VideoWriter
- fourcc = cv2.VideoWriter_fourcc(*'mp4v')
- self.video_writer = cv2.VideoWriter(str(filename), fourcc, 30.0, (width, height))
-
- if self.video_writer.isOpened():
- self.recording = True
- self.record_btn.setText("停止")
- self.record_status.setText("● 录制中")
- self.record_status.setStyleSheet("color: red;")
- self.status_bar.showMessage(f"开始录制: {filename.name}", 3000)
- else:
- QMessageBox.critical(self, "错误", "无法创建视频文件")
-
- def _stop_recording(self):
- """停止录制"""
- if self.video_writer:
- self.video_writer.release()
- self.video_writer = None
-
- self.recording = False
- self.record_btn.setText("录制")
- self.record_status.setText("录制: 未开始")
- self.record_status.setStyleSheet("color: gray;")
- self.status_bar.showMessage("录制已停止", 3000)
-
- def _on_frame_received(self, frame, width, height):
- """收到新帧"""
- self.current_frame = frame
- self.frame_width = width
- self.frame_height = height
-
- # 录制
- if self.recording and self.video_writer:
- # 转换为BGR用于录制
- frame_bgr = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
- self.video_writer.write(frame_bgr)
-
- def _update_display(self):
- """更新显示"""
- if self.current_frame is not None:
- # 直接使用QImage,避免转换
- bytes_per_line = 3 * self.frame_width
- qimage = QImage(self.current_frame.data, self.frame_width,
- self.frame_height, bytes_per_line, QImage.Format_RGB888)
-
- # 缩放以适应标签大小
- label_size = self.video_label.size()
- pixmap = QPixmap.fromImage(qimage).scaled(
- label_size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
-
- self.video_label.setPixmap(pixmap)
-
- # 更新分辨率显示
- self.resolution_status.setText(f"分辨率: {self.frame_width}x{self.frame_height}")
-
- def _on_status_update(self, stats):
- """状态更新"""
- self.fps_status.setText(f"FPS: {stats['fps']}")
- self.frame_status.setText(f"帧数: {stats['frames']}")
-
- def _on_keyframe_received(self):
- """收到关键帧"""
- if self.wait_keyframe.isChecked():
- self.keyframe_status.setText("关键帧: 已接收")
- self.keyframe_status.setStyleSheet("color: green;")
-
- def _on_error(self, msg):
- """错误处理"""
- QMessageBox.critical(self, "错误", msg)
- self.status_bar.showMessage(f"错误: {msg}")
-
- def closeEvent(self, event):
- """窗口关闭事件"""
- # 停止预览
- if self.streaming:
- self._stop_preview()
-
- # 停止录制
- if self.recording:
- self._stop_recording()
-
- # 断开连接
- if self.connected:
- self._disconnect()
-
- event.accept()
-
-
-def main():
- app = QApplication(sys.argv)
-
- # 设置应用样式
- app.setStyle('Fusion')
-
- # 设置全局样式表
- app.setStyleSheet("""
- QGroupBox {
- font-weight: bold;
- border: 1px solid gray;
- border-radius: 5px;
- margin-top: 10px;
- padding-top: 10px;
- }
- QGroupBox::title {
- subcontrol-origin: margin;
- left: 10px;
- padding: 0 5px 0 5px;
- }
- QPushButton {
- padding: 5px 10px;
- }
- QLineEdit {
- padding: 3px;
- }
- QComboBox {
- padding: 3px;
- }
- """)
-
- window = CameraPlayerQt()
- window.show()
-
- print("\n" + "=" * 60)
- print("摄像头拉流播放器 (Qt版) 启动")
- print("=" * 60)
- print("支持编码:")
- print(" • H.265 (HEVC) - 高压缩率")
- print(" • H.264 (AVC) - 兼容性好")
- print("\n操作步骤:")
- print("1. 选择编码格式")
- print("2. 输入设备ID、服务器IP和端口")
- print("3. 点击【连接】按钮")
- print("4. 点击【预览】按钮开始观看")
- print("5. 点击【录制】按钮开始/停止录制")
- print("=" * 60 + "\n")
-
- sys.exit(app.exec_())
-
-
-if __name__ == "__main__":
- main()
\ No newline at end of file
diff --git a/clients/hand_client.py b/clients/hand_client.py
index 75e0951..729203f 100644
--- a/clients/hand_client.py
+++ b/clients/hand_client.py
@@ -104,7 +104,7 @@ if __name__ == "__main__":
# ]
# client.set_angles(hand_open, device_id="hand2")
- client.set_angles(hand_open, device_id="hand2")
+ client.set_angles(hand_touch, device_id="hand2")
# client.set_angles(handshake, device_id="hand1")
# Close client
diff --git a/clients/hlc_client.py b/clients/hlc_client.py
new file mode 100644
index 0000000..65f6a51
--- /dev/null
+++ b/clients/hlc_client.py
@@ -0,0 +1,66 @@
+from google.protobuf.timestamp_pb2 import Timestamp
+
+from clients._path_setup import ensure_paths
+
+ensure_paths()
+
+from clients.base_client import RobotClientBase
+from cmvr.api import common_pb2
+from cmvr.api import hlc_command_pb2 as pb
+from cmvr.api import hlc_service_pb2_grpc as rpc
+
+
+class HlcClient(RobotClientBase):
+ """Client for calling cmvr.api.HlcService."""
+
+ def _init_stubs(self, channel):
+ self.stub = rpc.HlcServiceStub(channel)
+
+ @staticmethod
+ def _make_header(device_id: str):
+ header = common_pb2.CommandHeader.Request()
+ header.device_id = device_id
+ timestamp = Timestamp()
+ timestamp.GetCurrentTime()
+ header.timestamp.CopyFrom(timestamp)
+ return header
+
+ def touch(
+ self,
+ u: int,
+ v: int,
+ max_force: float,
+ device_id="hc01",
+ timeout=None,
+ ):
+ request = pb.Touch.Request()
+ request.header.CopyFrom(self._make_header(device_id))
+ request.u = u
+ request.v = v
+ request.max_force = max_force
+
+ rpc_timeout = self.timeout if timeout is None else timeout
+ response = self.stub.touch(request, timeout=rpc_timeout)
+ return response
+
+
+def main():
+ client = HlcClient()
+ try:
+ response = client.touch(
+ u=12,
+ v=34,
+ max_force=10.0,
+ device_id="hc01",
+ )
+ print("touch RPC call completed")
+ print(f"success: {response.header.success}")
+ print(f"error_message: {response.header.error_message}")
+ print(f"timestamp: {response.header.timestamp.seconds}")
+ return response
+ finally:
+ client.close()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/clients/joint_states.csv b/clients/joint_states.csv
deleted file mode 100644
index 600a5be..0000000
--- a/clients/joint_states.csv
+++ /dev/null
@@ -1,51 +0,0 @@
-R_SHOULDER_P,R_SHOULDER_R,R_SHOULDER_Y,R_ELBOW_R,R_WRIST_P,R_WRIST_Y,R_WRIST_R
-42.3871312049,94.4262521308,-69.8990748368,-35.2088867644,-16.7676098745,-5.0042133827,1.9505775177
-38.4358423623,74.7161029078,-69.9082421615,-35.1792075506,-16.7656045222,16.3947289414,-3.6198327581
-74.883406584,73.4828685496,-69.8867562442,-5.0052447067,-15.4131949426,15.065925223,14.4940560476
-61.7429060315,50.3894035464,-69.9068670628,-20.3281415008,-15.3997877302,35.9769366887,1.1899187489
-59.4242031304,67.0953058663,-69.8938609209,-21.9081872124,-15.423221704,19.7719713691,4.0812929663
-90.7017399835,66.3198647864,-69.8943765829,7.3872594442,-15.469745877,18.6678816978,20.4932679373
-43.3590395128,62.9293297379,-73.6622043394,-35.8108362239,-15.1957001636,20.2325721406,0
-40.5923409116,88.5308028978,-73.6077160531,-31.2608064855,-11.9376329573,-4.7227192179,-12.7598273933
-23.8609419698,84.6395600321,-73.65269324,-33.7536312605,-15.2801541426,-3.2523949241,-33.0631025258
-17.1967552631,63.8605007466,-76.6419286488,-33.6005942334,-26.570975045,1.7080444831,-40.4801557753
-112.3846401909,69.7356099778,-80.6124115775,39.6581332267,-8.4428768859,-0.4859828018,9.1025231955
--5.7770761525,76.506481426,-80.5607307844,-61.0971825964,0.409550232,5.9197426435,-25.0002685454
--8.8597036819,77.9207131517,-80.5670906159,-33.145436561,0.2532473454,5.9258159961,-61.5692425238
--36.6110290806,76.1685509185,-80.3678158947,-72.2165745265,11.1447166646,-0.0391903132,-52.2577616205
--35.3651323551,36.5073237197,-79.0065827651,-73.8037822106,52.7478124227,12.1613156805,-32.3100640957
--5.3251270437,44.1961881472,-81.8030751716,-71.2213468364,52.7241492657,10.7124200082,-8.0924558984
-2.8886240199,76.6163747311,-84.1251585237,-69.7672372481,74.4535163503,7.928589969,-1.7550270223
-10.4090961515,84.9616196088,-84.1484779059,-60.456730373,-18.5233817419,-21.8610327859,0
-54.5989244672,82.6571833568,-84.1363312007,-15.5816445344,-18.5294550945,-10.4057729963,-7.731091417
-81.1793533158,80.2358637145,-84.1327788623,11.5328382751,-18.4099360985,-10.3396536667,-3.3667000042
-38.2612048264,74.9317069261,-84.1652082735,-34.3789573981,-32.3599687196,0,0
-45.9865093697,73.6787628197,-42.7689502796,-25.1068959911,21.0002209945,17.5849341693,12.4229855056
-22.7002440684,64.4069878916,-62.7950857265,-31.455039178,7.9418825899,33.6965073683,-22.0264457013
--7.8064353671,69.4039247102,-64.5624886372,-46.9387079294,5.7706590252,20.9282574954,-42.2467565451
--2.1254442368,74.2813998286,-71.1825002979,-13.416838097,-4.5865844458,22.1780503339,-61.8393921242
-5.4559906041,77.0098821448,-71.1713849167,-48.8421310206,0.1305770815,11.0492046002,-28.0768227221
-8.1414437899,69.7912441798,-109.3110080989,-62.2787361616,26.0631052554,-26.4386790901,-18.1101072843
-8.1236820983,39.0906758264,-91.0631617607,-54.527763109,26.2233042549,12.5954458019,-19.292806765
-11.0263435842,28.5577634954,-91.6200194418,-56.0312616592,24.2097586755,5.6390506197,0
-36.628332406,62.8089513052,-98.8949218623,-43.4181687572,24.2570276936,2.887306217,-1.8566697351
-24.7188316764,87.9824822878,-99.1789943371,-41.2523309958,-8.5307113159,-24.3634832519,-10.7646737591
-72.4000929083,79.991210736,-99.1548155182,0.9364995161,-8.3708560911,-10.9260186742,-2.1646918458
-23.3924916765,86.2120999966,-73.4446522646,-46.4581109308,6.0181194969,8.9699407617,-2.1167925741
-30.0970719078,53.0039818529,-97.8485290411,-46.4597152127,5.9310872079,8.2670934344,-8.9634663386
-29.1964395496,32.4169207245,-113.7501132082,-48.2284359262,0.5494665255,3.410932346,-18.0847825497
-47.142795496,53.1601701478,-116.6299136781,-33.5294328753,-21.6354593019,-0.8102196181,-14.3094490461
-52.5930565222,86.0146587404,-67.4882403222,-27.2043225917,-5.4967279034,-0.6699022541,-2.348095636
-5.689127131,79.3010830718,-87.5470343635,-64.8126993063,-6.6456228742,-13.3742673328,-10.9887575528
--36.490879831,85.2079914607,-76.8147327198,-59.0912000599,3.7261737249,-13.9641273829,-40.7579257144
--43.7821688445,66.2628554858,-75.3971778389,-55.7114939138,2.5531572309,-13.9655024816,-48.4137304772
--18.0355654751,62.0254315203,-77.0001418623,-56.2921293434,-15.9961794991,-23.3165747686,-20.1152876799
--25.9848392206,67.5677668642,-69.5084322121,-71.3935206538,20.9554156949,11.0399799797,-15.5277865016
--34.3248128865,59.3506353495,-64.9300983585,-78.7725868015,26.2126472399,7.4840320158,-12.2919500578
--26.2237053253,93.5622317757,-72.0755123174,-77.1128999564,26.2140796344,12.1362201291,-17.8720433204
-43.6226573943,57.1218295265,-87.7357093655,-23.230974874,23.3228773044,17.0774654501,-0.0433729051
-20.543000674,69.8960954563,-85.5268997694,-25.9198658066,77.8772256551,22.5696096911,-8.9078321367
--0.940166446,22.5041779109,-94.648101389,-58.6892701666,70.7468231905,20.9154232408,-8.9639247048
-0.1180293058,33.0162345782,-92.8632805614,-67.671758704,61.8440903782,13.0295759233,0
-41.6477291703,62.5570217627,-96.0662292278,-22.0864916783,52.5155926283,11.0115039773,-3.3080864217
--9.3205909323,58.9835412902,-86.0922372259,-65.5880830905,52.532036517,11.5678459964,-14.0276111066
diff --git a/clients/press_client.py b/clients/press_client.py
deleted file mode 100644
index 87bf02e..0000000
--- a/clients/press_client.py
+++ /dev/null
@@ -1,54 +0,0 @@
-from clients._path_setup import ensure_paths
-
-ensure_paths()
-
-from google.protobuf import timestamp_pb2
-
-from clients.base_client import RobotClientBase
-from cmvr.api import hlc_command_pb2 as pb
-
-
-class TouchClient(RobotClientBase):
- """Client to send Touch commands"""
-
- def send(self, u: int, v: int, max_force: int, device_id="hc01"):
- """
- u, v: target coordinates
- max_force: maximum touch force
- device_id: device ID
- """
- # Construct request
- req = pb.Touch.Request()
- req.header.device_id = device_id
- ts = timestamp_pb2.Timestamp()
- ts.GetCurrentTime()
- req.header.timestamp.CopyFrom(ts)
-
- req.u = u
- req.v = v
- req.max_force = max_force
-
- # Call RPC
- try:
- resp = self.stub.touch(req, timeout=self.timeout)
- success = getattr(resp.header, "success", None)
- error_msg = getattr(resp.header, "error_message", "")
- timestamp_sec = getattr(resp.header.timestamp, "seconds", 0)
-
- print("Touch RPC call succeeded")
- print(f"Success: {success}")
- print(f"Error message: {error_msg}")
- print(f"Timestamp: {timestamp_sec}")
- except Exception as e:
- print("Touch RPC call failed:", e)
-
-
-if __name__ == "__main__":
- # Initialize client
- client = TouchClient()
-
- # Send Touch command
- client.send(u=12, v=13, max_force=1300, device_id="hc01")
-
- # Close client
- client.close()
diff --git a/generated/cmvr/__pycache__/__init__.cpython-39.pyc b/generated/cmvr/__pycache__/__init__.cpython-39.pyc
index b1b4d0b..d3c793c 100644
Binary files a/generated/cmvr/__pycache__/__init__.cpython-39.pyc and b/generated/cmvr/__pycache__/__init__.cpython-39.pyc differ
diff --git a/generated/cmvr/api/__pycache__/__init__.cpython-39.pyc b/generated/cmvr/api/__pycache__/__init__.cpython-39.pyc
index a78f275..3b26f31 100644
Binary files a/generated/cmvr/api/__pycache__/__init__.cpython-39.pyc and b/generated/cmvr/api/__pycache__/__init__.cpython-39.pyc differ
diff --git a/generated/cmvr/api/__pycache__/camera_command_pb2.cpython-39.pyc b/generated/cmvr/api/__pycache__/camera_command_pb2.cpython-39.pyc
new file mode 100644
index 0000000..a7e8760
Binary files /dev/null and b/generated/cmvr/api/__pycache__/camera_command_pb2.cpython-39.pyc differ
diff --git a/generated/cmvr/api/__pycache__/camera_service_pb2_grpc.cpython-39.pyc b/generated/cmvr/api/__pycache__/camera_service_pb2_grpc.cpython-39.pyc
new file mode 100644
index 0000000..dfa98bf
Binary files /dev/null and b/generated/cmvr/api/__pycache__/camera_service_pb2_grpc.cpython-39.pyc differ
diff --git a/generated/cmvr/api/__pycache__/common_pb2.cpython-39.pyc b/generated/cmvr/api/__pycache__/common_pb2.cpython-39.pyc
index 33fbda6..ce4d605 100644
Binary files a/generated/cmvr/api/__pycache__/common_pb2.cpython-39.pyc and b/generated/cmvr/api/__pycache__/common_pb2.cpython-39.pyc differ
diff --git a/generated/cmvr/api/__pycache__/dexhand_command_pb2.cpython-39.pyc b/generated/cmvr/api/__pycache__/dexhand_command_pb2.cpython-39.pyc
index b70627f..857346a 100644
Binary files a/generated/cmvr/api/__pycache__/dexhand_command_pb2.cpython-39.pyc and b/generated/cmvr/api/__pycache__/dexhand_command_pb2.cpython-39.pyc differ
diff --git a/generated/cmvr/api/__pycache__/dexhand_service_pb2_grpc.cpython-39.pyc b/generated/cmvr/api/__pycache__/dexhand_service_pb2_grpc.cpython-39.pyc
index 3d840e8..3b4d603 100644
Binary files a/generated/cmvr/api/__pycache__/dexhand_service_pb2_grpc.cpython-39.pyc and b/generated/cmvr/api/__pycache__/dexhand_service_pb2_grpc.cpython-39.pyc differ
diff --git a/generated/cmvr/api/__pycache__/hlc_command_pb2.cpython-39.pyc b/generated/cmvr/api/__pycache__/hlc_command_pb2.cpython-39.pyc
index 0f420ba..8cfe40a 100644
Binary files a/generated/cmvr/api/__pycache__/hlc_command_pb2.cpython-39.pyc and b/generated/cmvr/api/__pycache__/hlc_command_pb2.cpython-39.pyc differ
diff --git a/generated/cmvr/api/__pycache__/hlc_service_pb2_grpc.cpython-39.pyc b/generated/cmvr/api/__pycache__/hlc_service_pb2_grpc.cpython-39.pyc
new file mode 100644
index 0000000..b825184
Binary files /dev/null and b/generated/cmvr/api/__pycache__/hlc_service_pb2_grpc.cpython-39.pyc differ
diff --git a/generated/cmvr/api/__pycache__/humanoid_robot_command_pb2.cpython-39.pyc b/generated/cmvr/api/__pycache__/humanoid_robot_command_pb2.cpython-39.pyc
index 1856ba5..dd8c021 100644
Binary files a/generated/cmvr/api/__pycache__/humanoid_robot_command_pb2.cpython-39.pyc and b/generated/cmvr/api/__pycache__/humanoid_robot_command_pb2.cpython-39.pyc differ
diff --git a/generated/cmvr/api/__pycache__/humanoid_robot_service_pb2_grpc.cpython-39.pyc b/generated/cmvr/api/__pycache__/humanoid_robot_service_pb2_grpc.cpython-39.pyc
index 1caa4b6..f05c297 100644
Binary files a/generated/cmvr/api/__pycache__/humanoid_robot_service_pb2_grpc.cpython-39.pyc and b/generated/cmvr/api/__pycache__/humanoid_robot_service_pb2_grpc.cpython-39.pyc differ
diff --git a/generated/cmvr/api/biohead_command_pb2.py b/generated/cmvr/api/biohead_command_pb2.py
index 0c44d0b..2b1f7f3 100644
--- a/generated/cmvr/api/biohead_command_pb2.py
+++ b/generated/cmvr/api/biohead_command_pb2.py
@@ -25,7 +25,7 @@ _sym_db = _symbol_database.Default()
from cmvr.api import common_pb2 as cmvr_dot_api_dot_common__pb2
-DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1e\x63mvr/api/biohead_command.proto\x12\x08\x63mvr.api\x1a\x15\x63mvr/api/common.proto\"\xdd\x08\n\x10\x46\x61\x63ialExpression\x12\x33\n\x07\x65yebrow\x18\x03 \x01(\x0b\x32\".cmvr.api.FacialExpression.Eyebrow\x12\x31\n\x06\x65yelid\x18\x04 \x01(\x0b\x32!.cmvr.api.FacialExpression.Eyelid\x12\x33\n\x07\x65yeball\x18\x05 \x01(\x0b\x32\".cmvr.api.FacialExpression.Eyeball\x12-\n\x04nose\x18\x06 \x01(\x0b\x32\x1f.cmvr.api.FacialExpression.Nose\x12/\n\x05mouth\x18\x07 \x01(\x0b\x32 .cmvr.api.FacialExpression.Mouth\x12+\n\x03jaw\x18\x08 \x01(\x0b\x32\x1e.cmvr.api.FacialExpression.Jaw\x1ai\n\x07\x45yebrow\x12\x16\n\x0eleft_outside_y\x18\x01 \x01(\x02\x12\x15\n\rleft_inside_y\x18\x02 \x01(\x02\x12\x17\n\x0fright_outside_y\x18\x03 \x01(\x02\x12\x16\n\x0eright_inside_y\x18\x04 \x01(\x02\x1a\x62\n\x06\x45yelid\x12\x14\n\x0cleft_upper_y\x18\x01 \x01(\x02\x12\x14\n\x0cleft_lower_y\x18\x02 \x01(\x02\x12\x15\n\rright_upper_y\x18\x03 \x01(\x02\x12\x15\n\rright_lower_y\x18\x04 \x01(\x02\x1aK\n\x07\x45yeball\x12\x0e\n\x06left_x\x18\x01 \x01(\x02\x12\x0e\n\x06left_y\x18\x02 \x01(\x02\x12\x0f\n\x07right_x\x18\x03 \x01(\x02\x12\x0f\n\x07right_y\x18\x04 \x01(\x02\x1a\'\n\x04Nose\x12\x0e\n\x06left_y\x18\x01 \x01(\x02\x12\x0f\n\x07right_y\x18\x02 \x01(\x02\x1a\xbc\x03\n\x05Mouth\x12\x13\n\x0bupper_lip_y\x18\x01 \x01(\x02\x12\x13\n\x0bupper_lip_z\x18\x02 \x01(\x02\x12\x13\n\x0blower_lip_y\x18\x03 \x01(\x02\x12\x13\n\x0blower_lip_z\x18\x04 \x01(\x02\x12:\n\x08left_lip\x18\x05 \x01(\x0b\x32(.cmvr.api.FacialExpression.Mouth.LeftLip\x12<\n\tright_lip\x18\x06 \x01(\x0b\x32).cmvr.api.FacialExpression.Mouth.RightLip\x1aq\n\x07LeftLip\x12\x0f\n\x07upper_x\x18\x01 \x01(\x02\x12\x0f\n\x07upper_y\x18\x02 \x01(\x02\x12\x10\n\x08\x63orner_x\x18\x03 \x01(\x02\x12\x10\n\x08\x63orner_y\x18\x04 \x01(\x02\x12\x0f\n\x07lower_x\x18\x05 \x01(\x02\x12\x0f\n\x07lower_y\x18\x06 \x01(\x02\x1ar\n\x08RightLip\x12\x0f\n\x07upper_x\x18\x01 \x01(\x02\x12\x0f\n\x07upper_y\x18\x02 \x01(\x02\x12\x10\n\x08\x63orner_x\x18\x03 \x01(\x02\x12\x10\n\x08\x63orner_y\x18\x04 \x01(\x02\x12\x0f\n\x07lower_x\x18\x05 \x01(\x02\x12\x0f\n\x07lower_y\x18\x06 \x01(\x02\x1a\x1b\n\x03Jaw\x12\t\n\x01x\x18\x01 \x01(\x02\x12\t\n\x01y\x18\x02 \x01(\x02\"\xf0\x01\n\x13SetFacialExpression\x1aj\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12.\n\nexpression\x18\x02 \x01(\x0b\x32\x1a.cmvr.api.FacialExpression\x1am\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12\x14\n\x0c\x65xecution_id\x18\x02 \x01(\t\x12\x19\n\x11\x65xecution_time_ms\x18\x03 \x01(\x02\"\xf8\x01\n\x16StreamFacialExpression\x1aq\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12(\n\x04\x65xpr\x18\x02 \x01(\x0b\x32\x1a.cmvr.api.FacialExpression\x12\x0b\n\x03\x65of\x18\x03 \x01(\x08\x1ak\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12-\n\texpr_diff\x18\x02 \x01(\x0b\x32\x1a.cmvr.api.FacialExpression\"\x84\x02\n\tGetStatus\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a\xba\x01\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12\x11\n\tis_moving\x18\x02 \x01(\x08\x12\x17\n\x0flast_request_id\x18\x03 \x01(\t\x12\x19\n\x11\x63urrent_positions\x18\x04 \x03(\x02\x12\x18\n\x10\x63\x61mera_recording\x18\x05 \x01(\x08\x12\x1b\n\x13\x61\x63tive_recording_id\x18\x06 \x01(\t\"\xa4\x01\n\rEmergencyStop\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1aW\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12\x19\n\x11stopped_processes\x18\x02 \x01(\tb\x06proto3')
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1e\x63mvr/api/biohead_command.proto\x12\x08\x63mvr.api\x1a\x15\x63mvr/api/common.proto\"\xdd\x08\n\x10\x46\x61\x63ialExpression\x12\x33\n\x07\x65yebrow\x18\x03 \x01(\x0b\x32\".cmvr.api.FacialExpression.Eyebrow\x12\x31\n\x06\x65yelid\x18\x04 \x01(\x0b\x32!.cmvr.api.FacialExpression.Eyelid\x12\x33\n\x07\x65yeball\x18\x05 \x01(\x0b\x32\".cmvr.api.FacialExpression.Eyeball\x12-\n\x04nose\x18\x06 \x01(\x0b\x32\x1f.cmvr.api.FacialExpression.Nose\x12/\n\x05mouth\x18\x07 \x01(\x0b\x32 .cmvr.api.FacialExpression.Mouth\x12+\n\x03jaw\x18\x08 \x01(\x0b\x32\x1e.cmvr.api.FacialExpression.Jaw\x1ai\n\x07\x45yebrow\x12\x16\n\x0eleft_outside_y\x18\x01 \x01(\x02\x12\x15\n\rleft_inside_y\x18\x02 \x01(\x02\x12\x17\n\x0fright_outside_y\x18\x03 \x01(\x02\x12\x16\n\x0eright_inside_y\x18\x04 \x01(\x02\x1a\x62\n\x06\x45yelid\x12\x14\n\x0cleft_upper_y\x18\x01 \x01(\x02\x12\x14\n\x0cleft_lower_y\x18\x02 \x01(\x02\x12\x15\n\rright_upper_y\x18\x03 \x01(\x02\x12\x15\n\rright_lower_y\x18\x04 \x01(\x02\x1aK\n\x07\x45yeball\x12\x0e\n\x06left_x\x18\x01 \x01(\x02\x12\x0e\n\x06left_y\x18\x02 \x01(\x02\x12\x0f\n\x07right_x\x18\x03 \x01(\x02\x12\x0f\n\x07right_y\x18\x04 \x01(\x02\x1a\'\n\x04Nose\x12\x0e\n\x06left_y\x18\x01 \x01(\x02\x12\x0f\n\x07right_y\x18\x02 \x01(\x02\x1a\xbc\x03\n\x05Mouth\x12\x13\n\x0bupper_lip_y\x18\x01 \x01(\x02\x12\x13\n\x0bupper_lip_z\x18\x02 \x01(\x02\x12\x13\n\x0blower_lip_y\x18\x03 \x01(\x02\x12\x13\n\x0blower_lip_z\x18\x04 \x01(\x02\x12:\n\x08left_lip\x18\x05 \x01(\x0b\x32(.cmvr.api.FacialExpression.Mouth.LeftLip\x12<\n\tright_lip\x18\x06 \x01(\x0b\x32).cmvr.api.FacialExpression.Mouth.RightLip\x1aq\n\x07LeftLip\x12\x0f\n\x07upper_x\x18\x01 \x01(\x02\x12\x0f\n\x07upper_y\x18\x02 \x01(\x02\x12\x10\n\x08\x63orner_x\x18\x03 \x01(\x02\x12\x10\n\x08\x63orner_y\x18\x04 \x01(\x02\x12\x0f\n\x07lower_x\x18\x05 \x01(\x02\x12\x0f\n\x07lower_y\x18\x06 \x01(\x02\x1ar\n\x08RightLip\x12\x0f\n\x07upper_x\x18\x01 \x01(\x02\x12\x0f\n\x07upper_y\x18\x02 \x01(\x02\x12\x10\n\x08\x63orner_x\x18\x03 \x01(\x02\x12\x10\n\x08\x63orner_y\x18\x04 \x01(\x02\x12\x0f\n\x07lower_x\x18\x05 \x01(\x02\x12\x0f\n\x07lower_y\x18\x06 \x01(\x02\x1a\x1b\n\x03Jaw\x12\t\n\x01x\x18\x01 \x01(\x02\x12\t\n\x01y\x18\x02 \x01(\x02\"\xf0\x01\n\x13SetFacialExpression\x1aj\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12.\n\nexpression\x18\x02 \x01(\x0b\x32\x1a.cmvr.api.FacialExpression\x1am\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12\x14\n\x0c\x65xecution_id\x18\x02 \x01(\t\x12\x19\n\x11\x65xecution_time_ms\x18\x03 \x01(\x02\"\xf8\x01\n\x16StreamFacialExpression\x1aq\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12(\n\x04\x65xpr\x18\x02 \x01(\x0b\x32\x1a.cmvr.api.FacialExpression\x12\x0b\n\x03\x65of\x18\x03 \x01(\x08\x1ak\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12-\n\texpr_diff\x18\x02 \x01(\x0b\x32\x1a.cmvr.api.FacialExpression\"\x84\x02\n\tGetStatus\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a\xba\x01\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12\x11\n\tis_moving\x18\x02 \x01(\x08\x12\x17\n\x0flast_request_id\x18\x03 \x01(\t\x12\x19\n\x11\x63urrent_positions\x18\x04 \x03(\x02\x12\x18\n\x10\x63\x61mera_recording\x18\x05 \x01(\x08\x12\x1b\n\x13\x61\x63tive_recording_id\x18\x06 \x01(\t\"\xa4\x01\n\rEmergencyStop\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1aW\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12\x19\n\x11stopped_processes\x18\x02 \x01(\t\"\x86\x01\n\nSpeakStart\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x85\x01\n\tSpeakStop\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x81\x01\n\x05Happy\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x84\x01\n\x08Surprise\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x8b\x01\n\x0f\x45xpressionTired\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x8b\x01\n\x0f\x45xpressionAngry\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x8d\x01\n\x11\x45xpressionSadness\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x8a\x01\n\x0e\x45xpressionYawn\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedbackb\x06proto3')
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -74,4 +74,52 @@ if not _descriptor._USE_C_DESCRIPTORS:
_globals['_EMERGENCYSTOP_REQUEST']._serialized_end=1269
_globals['_EMERGENCYSTOP_FEEDBACK']._serialized_start=2022
_globals['_EMERGENCYSTOP_FEEDBACK']._serialized_end=2109
+ _globals['_SPEAKSTART']._serialized_start=2112
+ _globals['_SPEAKSTART']._serialized_end=2246
+ _globals['_SPEAKSTART_REQUEST']._serialized_start=1211
+ _globals['_SPEAKSTART_REQUEST']._serialized_end=1269
+ _globals['_SPEAKSTART_FEEDBACK']._serialized_start=1319
+ _globals['_SPEAKSTART_FEEDBACK']._serialized_end=1379
+ _globals['_SPEAKSTOP']._serialized_start=2249
+ _globals['_SPEAKSTOP']._serialized_end=2382
+ _globals['_SPEAKSTOP_REQUEST']._serialized_start=1211
+ _globals['_SPEAKSTOP_REQUEST']._serialized_end=1269
+ _globals['_SPEAKSTOP_FEEDBACK']._serialized_start=1319
+ _globals['_SPEAKSTOP_FEEDBACK']._serialized_end=1379
+ _globals['_HAPPY']._serialized_start=2385
+ _globals['_HAPPY']._serialized_end=2514
+ _globals['_HAPPY_REQUEST']._serialized_start=1211
+ _globals['_HAPPY_REQUEST']._serialized_end=1269
+ _globals['_HAPPY_FEEDBACK']._serialized_start=1319
+ _globals['_HAPPY_FEEDBACK']._serialized_end=1379
+ _globals['_SURPRISE']._serialized_start=2517
+ _globals['_SURPRISE']._serialized_end=2649
+ _globals['_SURPRISE_REQUEST']._serialized_start=1211
+ _globals['_SURPRISE_REQUEST']._serialized_end=1269
+ _globals['_SURPRISE_FEEDBACK']._serialized_start=1319
+ _globals['_SURPRISE_FEEDBACK']._serialized_end=1379
+ _globals['_EXPRESSIONTIRED']._serialized_start=2652
+ _globals['_EXPRESSIONTIRED']._serialized_end=2791
+ _globals['_EXPRESSIONTIRED_REQUEST']._serialized_start=1211
+ _globals['_EXPRESSIONTIRED_REQUEST']._serialized_end=1269
+ _globals['_EXPRESSIONTIRED_FEEDBACK']._serialized_start=1319
+ _globals['_EXPRESSIONTIRED_FEEDBACK']._serialized_end=1379
+ _globals['_EXPRESSIONANGRY']._serialized_start=2794
+ _globals['_EXPRESSIONANGRY']._serialized_end=2933
+ _globals['_EXPRESSIONANGRY_REQUEST']._serialized_start=1211
+ _globals['_EXPRESSIONANGRY_REQUEST']._serialized_end=1269
+ _globals['_EXPRESSIONANGRY_FEEDBACK']._serialized_start=1319
+ _globals['_EXPRESSIONANGRY_FEEDBACK']._serialized_end=1379
+ _globals['_EXPRESSIONSADNESS']._serialized_start=2936
+ _globals['_EXPRESSIONSADNESS']._serialized_end=3077
+ _globals['_EXPRESSIONSADNESS_REQUEST']._serialized_start=1211
+ _globals['_EXPRESSIONSADNESS_REQUEST']._serialized_end=1269
+ _globals['_EXPRESSIONSADNESS_FEEDBACK']._serialized_start=1319
+ _globals['_EXPRESSIONSADNESS_FEEDBACK']._serialized_end=1379
+ _globals['_EXPRESSIONYAWN']._serialized_start=3080
+ _globals['_EXPRESSIONYAWN']._serialized_end=3218
+ _globals['_EXPRESSIONYAWN_REQUEST']._serialized_start=1211
+ _globals['_EXPRESSIONYAWN_REQUEST']._serialized_end=1269
+ _globals['_EXPRESSIONYAWN_FEEDBACK']._serialized_start=1319
+ _globals['_EXPRESSIONYAWN_FEEDBACK']._serialized_end=1379
# @@protoc_insertion_point(module_scope)
diff --git a/generated/cmvr/api/biohead_service_pb2.py b/generated/cmvr/api/biohead_service_pb2.py
index 01f389d..e78f428 100644
--- a/generated/cmvr/api/biohead_service_pb2.py
+++ b/generated/cmvr/api/biohead_service_pb2.py
@@ -25,7 +25,7 @@ _sym_db = _symbol_database.Default()
from cmvr.api import biohead_command_pb2 as cmvr_dot_api_dot_biohead__command__pb2
-DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1e\x63mvr/api/biohead_service.proto\x12\x08\x63mvr.api\x1a\x1e\x63mvr/api/biohead_command.proto2\x85\x03\n\x0e\x42ioHeadService\x12`\n\rSetExpression\x12%.cmvr.api.SetFacialExpression.Request\x1a&.cmvr.api.SetFacialExpression.Feedback\"\x00\x12k\n\x10StreamExpression\x12(.cmvr.api.StreamFacialExpression.Request\x1a).cmvr.api.StreamFacialExpression.Feedback(\x01\x30\x01\x12N\n\x0fGetSystemStatus\x12\x1b.cmvr.api.GetStatus.Request\x1a\x1c.cmvr.api.GetStatus.Feedback\"\x00\x12T\n\rEmergencyStop\x12\x1f.cmvr.api.EmergencyStop.Request\x1a .cmvr.api.EmergencyStop.Feedback\"\x00\x62\x06proto3')
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1e\x63mvr/api/biohead_service.proto\x12\x08\x63mvr.api\x1a\x1e\x63mvr/api/biohead_command.proto2\x94\x08\n\x0e\x42ioHeadService\x12`\n\rSetExpression\x12%.cmvr.api.SetFacialExpression.Request\x1a&.cmvr.api.SetFacialExpression.Feedback\"\x00\x12k\n\x10StreamExpression\x12(.cmvr.api.StreamFacialExpression.Request\x1a).cmvr.api.StreamFacialExpression.Feedback(\x01\x30\x01\x12N\n\x0fGetSystemStatus\x12\x1b.cmvr.api.GetStatus.Request\x1a\x1c.cmvr.api.GetStatus.Feedback\"\x00\x12T\n\rEmergencyStop\x12\x1f.cmvr.api.EmergencyStop.Request\x1a .cmvr.api.EmergencyStop.Feedback\"\x00\x12K\n\nSpeakStart\x12\x1c.cmvr.api.SpeakStart.Request\x1a\x1d.cmvr.api.SpeakStart.Feedback\"\x00\x12H\n\tSpeakStop\x12\x1b.cmvr.api.SpeakStop.Request\x1a\x1c.cmvr.api.SpeakStop.Feedback\"\x00\x12<\n\x05Happy\x12\x17.cmvr.api.Happy.Request\x1a\x18.cmvr.api.Happy.Feedback\"\x00\x12\x45\n\x08Surprise\x12\x1a.cmvr.api.Surprise.Request\x1a\x1b.cmvr.api.Surprise.Feedback\"\x00\x12Z\n\x0f\x45xpressionTired\x12!.cmvr.api.ExpressionTired.Request\x1a\".cmvr.api.ExpressionTired.Feedback\"\x00\x12Z\n\x0f\x45xpressionAngry\x12!.cmvr.api.ExpressionAngry.Request\x1a\".cmvr.api.ExpressionAngry.Feedback\"\x00\x12`\n\x11\x45xpressionSadness\x12#.cmvr.api.ExpressionSadness.Request\x1a$.cmvr.api.ExpressionSadness.Feedback\"\x00\x12W\n\x0e\x45xpressionYawn\x12 .cmvr.api.ExpressionYawn.Request\x1a!.cmvr.api.ExpressionYawn.Feedback\"\x00\x62\x06proto3')
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -33,5 +33,5 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.api.biohead_service_pb
if not _descriptor._USE_C_DESCRIPTORS:
DESCRIPTOR._loaded_options = None
_globals['_BIOHEADSERVICE']._serialized_start=77
- _globals['_BIOHEADSERVICE']._serialized_end=466
+ _globals['_BIOHEADSERVICE']._serialized_end=1121
# @@protoc_insertion_point(module_scope)
diff --git a/generated/cmvr/api/biohead_service_pb2_grpc.py b/generated/cmvr/api/biohead_service_pb2_grpc.py
index 4313022..0ddb589 100644
--- a/generated/cmvr/api/biohead_service_pb2_grpc.py
+++ b/generated/cmvr/api/biohead_service_pb2_grpc.py
@@ -55,6 +55,46 @@ class BioHeadServiceStub(object):
request_serializer=cmvr_dot_api_dot_biohead__command__pb2.EmergencyStop.Request.SerializeToString,
response_deserializer=cmvr_dot_api_dot_biohead__command__pb2.EmergencyStop.Feedback.FromString,
_registered_method=True)
+ self.SpeakStart = channel.unary_unary(
+ '/cmvr.api.BioHeadService/SpeakStart',
+ request_serializer=cmvr_dot_api_dot_biohead__command__pb2.SpeakStart.Request.SerializeToString,
+ response_deserializer=cmvr_dot_api_dot_biohead__command__pb2.SpeakStart.Feedback.FromString,
+ _registered_method=True)
+ self.SpeakStop = channel.unary_unary(
+ '/cmvr.api.BioHeadService/SpeakStop',
+ request_serializer=cmvr_dot_api_dot_biohead__command__pb2.SpeakStop.Request.SerializeToString,
+ response_deserializer=cmvr_dot_api_dot_biohead__command__pb2.SpeakStop.Feedback.FromString,
+ _registered_method=True)
+ self.Happy = channel.unary_unary(
+ '/cmvr.api.BioHeadService/Happy',
+ request_serializer=cmvr_dot_api_dot_biohead__command__pb2.Happy.Request.SerializeToString,
+ response_deserializer=cmvr_dot_api_dot_biohead__command__pb2.Happy.Feedback.FromString,
+ _registered_method=True)
+ self.Surprise = channel.unary_unary(
+ '/cmvr.api.BioHeadService/Surprise',
+ request_serializer=cmvr_dot_api_dot_biohead__command__pb2.Surprise.Request.SerializeToString,
+ response_deserializer=cmvr_dot_api_dot_biohead__command__pb2.Surprise.Feedback.FromString,
+ _registered_method=True)
+ self.ExpressionTired = channel.unary_unary(
+ '/cmvr.api.BioHeadService/ExpressionTired',
+ request_serializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionTired.Request.SerializeToString,
+ response_deserializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionTired.Feedback.FromString,
+ _registered_method=True)
+ self.ExpressionAngry = channel.unary_unary(
+ '/cmvr.api.BioHeadService/ExpressionAngry',
+ request_serializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionAngry.Request.SerializeToString,
+ response_deserializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionAngry.Feedback.FromString,
+ _registered_method=True)
+ self.ExpressionSadness = channel.unary_unary(
+ '/cmvr.api.BioHeadService/ExpressionSadness',
+ request_serializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionSadness.Request.SerializeToString,
+ response_deserializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionSadness.Feedback.FromString,
+ _registered_method=True)
+ self.ExpressionYawn = channel.unary_unary(
+ '/cmvr.api.BioHeadService/ExpressionYawn',
+ request_serializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionYawn.Request.SerializeToString,
+ response_deserializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionYawn.Feedback.FromString,
+ _registered_method=True)
class BioHeadServiceServicer(object):
@@ -91,6 +131,56 @@ class BioHeadServiceServicer(object):
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
+ def SpeakStart(self, request, context):
+ """开始说话
+ """
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+ context.set_details('Method not implemented!')
+ raise NotImplementedError('Method not implemented!')
+
+ def SpeakStop(self, request, context):
+ """停止说话
+ """
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+ context.set_details('Method not implemented!')
+ raise NotImplementedError('Method not implemented!')
+
+ def Happy(self, request, context):
+ """Missing associated documentation comment in .proto file."""
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+ context.set_details('Method not implemented!')
+ raise NotImplementedError('Method not implemented!')
+
+ def Surprise(self, request, context):
+ """Missing associated documentation comment in .proto file."""
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+ context.set_details('Method not implemented!')
+ raise NotImplementedError('Method not implemented!')
+
+ def ExpressionTired(self, request, context):
+ """Missing associated documentation comment in .proto file."""
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+ context.set_details('Method not implemented!')
+ raise NotImplementedError('Method not implemented!')
+
+ def ExpressionAngry(self, request, context):
+ """Missing associated documentation comment in .proto file."""
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+ context.set_details('Method not implemented!')
+ raise NotImplementedError('Method not implemented!')
+
+ def ExpressionSadness(self, request, context):
+ """Missing associated documentation comment in .proto file."""
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+ context.set_details('Method not implemented!')
+ raise NotImplementedError('Method not implemented!')
+
+ def ExpressionYawn(self, request, context):
+ """Missing associated documentation comment in .proto file."""
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+ context.set_details('Method not implemented!')
+ raise NotImplementedError('Method not implemented!')
+
def add_BioHeadServiceServicer_to_server(servicer, server):
rpc_method_handlers = {
@@ -114,6 +204,46 @@ def add_BioHeadServiceServicer_to_server(servicer, server):
request_deserializer=cmvr_dot_api_dot_biohead__command__pb2.EmergencyStop.Request.FromString,
response_serializer=cmvr_dot_api_dot_biohead__command__pb2.EmergencyStop.Feedback.SerializeToString,
),
+ 'SpeakStart': grpc.unary_unary_rpc_method_handler(
+ servicer.SpeakStart,
+ request_deserializer=cmvr_dot_api_dot_biohead__command__pb2.SpeakStart.Request.FromString,
+ response_serializer=cmvr_dot_api_dot_biohead__command__pb2.SpeakStart.Feedback.SerializeToString,
+ ),
+ 'SpeakStop': grpc.unary_unary_rpc_method_handler(
+ servicer.SpeakStop,
+ request_deserializer=cmvr_dot_api_dot_biohead__command__pb2.SpeakStop.Request.FromString,
+ response_serializer=cmvr_dot_api_dot_biohead__command__pb2.SpeakStop.Feedback.SerializeToString,
+ ),
+ 'Happy': grpc.unary_unary_rpc_method_handler(
+ servicer.Happy,
+ request_deserializer=cmvr_dot_api_dot_biohead__command__pb2.Happy.Request.FromString,
+ response_serializer=cmvr_dot_api_dot_biohead__command__pb2.Happy.Feedback.SerializeToString,
+ ),
+ 'Surprise': grpc.unary_unary_rpc_method_handler(
+ servicer.Surprise,
+ request_deserializer=cmvr_dot_api_dot_biohead__command__pb2.Surprise.Request.FromString,
+ response_serializer=cmvr_dot_api_dot_biohead__command__pb2.Surprise.Feedback.SerializeToString,
+ ),
+ 'ExpressionTired': grpc.unary_unary_rpc_method_handler(
+ servicer.ExpressionTired,
+ request_deserializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionTired.Request.FromString,
+ response_serializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionTired.Feedback.SerializeToString,
+ ),
+ 'ExpressionAngry': grpc.unary_unary_rpc_method_handler(
+ servicer.ExpressionAngry,
+ request_deserializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionAngry.Request.FromString,
+ response_serializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionAngry.Feedback.SerializeToString,
+ ),
+ 'ExpressionSadness': grpc.unary_unary_rpc_method_handler(
+ servicer.ExpressionSadness,
+ request_deserializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionSadness.Request.FromString,
+ response_serializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionSadness.Feedback.SerializeToString,
+ ),
+ 'ExpressionYawn': grpc.unary_unary_rpc_method_handler(
+ servicer.ExpressionYawn,
+ request_deserializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionYawn.Request.FromString,
+ response_serializer=cmvr_dot_api_dot_biohead__command__pb2.ExpressionYawn.Feedback.SerializeToString,
+ ),
}
generic_handler = grpc.method_handlers_generic_handler(
'cmvr.api.BioHeadService', rpc_method_handlers)
@@ -233,3 +363,219 @@ class BioHeadService(object):
timeout,
metadata,
_registered_method=True)
+
+ @staticmethod
+ def SpeakStart(request,
+ target,
+ options=(),
+ channel_credentials=None,
+ call_credentials=None,
+ insecure=False,
+ compression=None,
+ wait_for_ready=None,
+ timeout=None,
+ metadata=None):
+ return grpc.experimental.unary_unary(
+ request,
+ target,
+ '/cmvr.api.BioHeadService/SpeakStart',
+ cmvr_dot_api_dot_biohead__command__pb2.SpeakStart.Request.SerializeToString,
+ cmvr_dot_api_dot_biohead__command__pb2.SpeakStart.Feedback.FromString,
+ options,
+ channel_credentials,
+ insecure,
+ call_credentials,
+ compression,
+ wait_for_ready,
+ timeout,
+ metadata,
+ _registered_method=True)
+
+ @staticmethod
+ def SpeakStop(request,
+ target,
+ options=(),
+ channel_credentials=None,
+ call_credentials=None,
+ insecure=False,
+ compression=None,
+ wait_for_ready=None,
+ timeout=None,
+ metadata=None):
+ return grpc.experimental.unary_unary(
+ request,
+ target,
+ '/cmvr.api.BioHeadService/SpeakStop',
+ cmvr_dot_api_dot_biohead__command__pb2.SpeakStop.Request.SerializeToString,
+ cmvr_dot_api_dot_biohead__command__pb2.SpeakStop.Feedback.FromString,
+ options,
+ channel_credentials,
+ insecure,
+ call_credentials,
+ compression,
+ wait_for_ready,
+ timeout,
+ metadata,
+ _registered_method=True)
+
+ @staticmethod
+ def Happy(request,
+ target,
+ options=(),
+ channel_credentials=None,
+ call_credentials=None,
+ insecure=False,
+ compression=None,
+ wait_for_ready=None,
+ timeout=None,
+ metadata=None):
+ return grpc.experimental.unary_unary(
+ request,
+ target,
+ '/cmvr.api.BioHeadService/Happy',
+ cmvr_dot_api_dot_biohead__command__pb2.Happy.Request.SerializeToString,
+ cmvr_dot_api_dot_biohead__command__pb2.Happy.Feedback.FromString,
+ options,
+ channel_credentials,
+ insecure,
+ call_credentials,
+ compression,
+ wait_for_ready,
+ timeout,
+ metadata,
+ _registered_method=True)
+
+ @staticmethod
+ def Surprise(request,
+ target,
+ options=(),
+ channel_credentials=None,
+ call_credentials=None,
+ insecure=False,
+ compression=None,
+ wait_for_ready=None,
+ timeout=None,
+ metadata=None):
+ return grpc.experimental.unary_unary(
+ request,
+ target,
+ '/cmvr.api.BioHeadService/Surprise',
+ cmvr_dot_api_dot_biohead__command__pb2.Surprise.Request.SerializeToString,
+ cmvr_dot_api_dot_biohead__command__pb2.Surprise.Feedback.FromString,
+ options,
+ channel_credentials,
+ insecure,
+ call_credentials,
+ compression,
+ wait_for_ready,
+ timeout,
+ metadata,
+ _registered_method=True)
+
+ @staticmethod
+ def ExpressionTired(request,
+ target,
+ options=(),
+ channel_credentials=None,
+ call_credentials=None,
+ insecure=False,
+ compression=None,
+ wait_for_ready=None,
+ timeout=None,
+ metadata=None):
+ return grpc.experimental.unary_unary(
+ request,
+ target,
+ '/cmvr.api.BioHeadService/ExpressionTired',
+ cmvr_dot_api_dot_biohead__command__pb2.ExpressionTired.Request.SerializeToString,
+ cmvr_dot_api_dot_biohead__command__pb2.ExpressionTired.Feedback.FromString,
+ options,
+ channel_credentials,
+ insecure,
+ call_credentials,
+ compression,
+ wait_for_ready,
+ timeout,
+ metadata,
+ _registered_method=True)
+
+ @staticmethod
+ def ExpressionAngry(request,
+ target,
+ options=(),
+ channel_credentials=None,
+ call_credentials=None,
+ insecure=False,
+ compression=None,
+ wait_for_ready=None,
+ timeout=None,
+ metadata=None):
+ return grpc.experimental.unary_unary(
+ request,
+ target,
+ '/cmvr.api.BioHeadService/ExpressionAngry',
+ cmvr_dot_api_dot_biohead__command__pb2.ExpressionAngry.Request.SerializeToString,
+ cmvr_dot_api_dot_biohead__command__pb2.ExpressionAngry.Feedback.FromString,
+ options,
+ channel_credentials,
+ insecure,
+ call_credentials,
+ compression,
+ wait_for_ready,
+ timeout,
+ metadata,
+ _registered_method=True)
+
+ @staticmethod
+ def ExpressionSadness(request,
+ target,
+ options=(),
+ channel_credentials=None,
+ call_credentials=None,
+ insecure=False,
+ compression=None,
+ wait_for_ready=None,
+ timeout=None,
+ metadata=None):
+ return grpc.experimental.unary_unary(
+ request,
+ target,
+ '/cmvr.api.BioHeadService/ExpressionSadness',
+ cmvr_dot_api_dot_biohead__command__pb2.ExpressionSadness.Request.SerializeToString,
+ cmvr_dot_api_dot_biohead__command__pb2.ExpressionSadness.Feedback.FromString,
+ options,
+ channel_credentials,
+ insecure,
+ call_credentials,
+ compression,
+ wait_for_ready,
+ timeout,
+ metadata,
+ _registered_method=True)
+
+ @staticmethod
+ def ExpressionYawn(request,
+ target,
+ options=(),
+ channel_credentials=None,
+ call_credentials=None,
+ insecure=False,
+ compression=None,
+ wait_for_ready=None,
+ timeout=None,
+ metadata=None):
+ return grpc.experimental.unary_unary(
+ request,
+ target,
+ '/cmvr.api.BioHeadService/ExpressionYawn',
+ cmvr_dot_api_dot_biohead__command__pb2.ExpressionYawn.Request.SerializeToString,
+ cmvr_dot_api_dot_biohead__command__pb2.ExpressionYawn.Feedback.FromString,
+ options,
+ channel_credentials,
+ insecure,
+ call_credentials,
+ compression,
+ wait_for_ready,
+ timeout,
+ metadata,
+ _registered_method=True)
diff --git a/generated/cmvr/api/humanoid_robot_command_pb2.py b/generated/cmvr/api/humanoid_robot_command_pb2.py
index b16f942..c8c95a5 100644
--- a/generated/cmvr/api/humanoid_robot_command_pb2.py
+++ b/generated/cmvr/api/humanoid_robot_command_pb2.py
@@ -25,67 +25,81 @@ _sym_db = _symbol_database.Default()
from cmvr.api import common_pb2 as cmvr_dot_api_dot_common__pb2
-DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n%cmvr/api/humanoid_robot_command.proto\x12\x08\x63mvr.api\x1a\x15\x63mvr/api/common.proto\"8\n\x08JointCmd\x12\x12\n\njoint_name\x18\x01 \x01(\t\x12\x0b\n\x03rad\x18\x02 \x01(\x01\x12\x0b\n\x03vel\x18\x03 \x01(\x01\"M\n\x06Pose3D\x12\t\n\x01x\x18\x01 \x01(\x01\x12\t\n\x01y\x18\x02 \x01(\x01\x12\t\n\x01z\x18\x03 \x01(\x01\x12\n\n\x02rx\x18\x04 \x01(\x01\x12\n\n\x02ry\x18\x05 \x01(\x01\x12\n\n\x02rz\x18\x06 \x01(\x01\"\xbd\x01\n\x05MoveJ\x1av\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12 \n\x04\x63mds\x18\x02 \x03(\x0b\x32\x12.cmvr.api.JointCmd\x12\x0b\n\x03vel\x18\x03 \x01(\x01\x12\x0b\n\x03\x61\x63\x63\x18\x04 \x01(\x01\x1a<\n\x08Response\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\xc7\x01\n\x07GetPose\x1a^\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\x11\n\tbase_link\x18\x02 \x01(\t\x12\x0f\n\x07\x65\x65_link\x18\x03 \x01(\t\x1a\\\n\x08Response\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12\x1e\n\x04pose\x18\x02 \x01(\x0b\x32\x10.cmvr.api.Pose3D\"\xd3\x01\n\x05MoveL\x1a\x8b\x01\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\x0f\n\x07\x65\x65_link\x18\x02 \x01(\t\x12$\n\ntargetPose\x18\x03 \x01(\x0b\x32\x10.cmvr.api.Pose3D\x12\x0b\n\x03vel\x18\x04 \x01(\x01\x12\x0b\n\x03\x61\x63\x63\x18\x05 \x01(\x01\x1a<\n\x08Response\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\xe2\x01\n\x06SpeedJ\x1a\x99\x01\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\x12\n\njoint_name\x18\x02 \x01(\t\x12\x0b\n\x03vel\x18\x03 \x01(\x01\x12\x0b\n\x03\x61\x63\x63\x18\x04 \x01(\x01\x12/\n\x03\x64ir\x18\x05 \x01(\x0e\x32\".cmvr.api.RobotJointIndexDirection\x1a<\n\x08Response\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x87\x02\n\x06SpeedL\x1a\xbe\x01\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\x0f\n\x07\x65\x65_link\x18\x02 \x01(\t\x12\x0b\n\x03vel\x18\x03 \x01(\x01\x12\x0b\n\x03\x61\x63\x63\x18\x04 \x01(\x01\x12/\n\x03\x64ir\x18\x05 \x01(\x0e\x32\".cmvr.api.RobotJointIndexDirection\x12&\n\x04\x63\x61rt\x18\x06 \x01(\x0e\x32\x18.cmvr.api.RobotCartesian\x1a<\n\x08Response\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"a\n\nJointState\x12\x0c\n\x04name\x18\x01 \x03(\t\x12\x10\n\x08position\x18\x02 \x03(\x01\x12\x10\n\x08velocity\x18\x03 \x03(\x01\x12\x0e\n\x06\x65\x66\x66ort\x18\x04 \x03(\x01\x12\x11\n\ttimestamp\x18\x05 \x01(\x01\"f\n\rJointResponse\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12#\n\x05state\x18\x02 \x03(\x0b\x32\x14.cmvr.api.JointState\"?\n\x0cJointRequest\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\"\x9e\x01\n\x0e\x43\x61librateZeroQ\x1aN\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\x12\n\njoint_name\x18\x02 \x01(\t\x1a<\n\x08Response\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\xb1\x01\n\x06ServoJ\x1ai\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12 \n\x04\x63mds\x18\x02 \x03(\x0b\x32\x12.cmvr.api.JointCmd\x12\x0b\n\x03vel\x18\x03 \x01(\x01\x1a<\n\x08Response\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback*=\n\x0eRobotCartesian\x12\x05\n\x01X\x10\x00\x12\x05\n\x01Y\x10\x01\x12\x05\n\x01Z\x10\x02\x12\x06\n\x02RX\x10\x03\x12\x06\n\x02RY\x10\x04\x12\x06\n\x02RZ\x10\x05*\xbf\x01\n\x18RobotJointIndexDirection\x12\x0b\n\x07\x46ORWARD\x10\x00\x12\x0c\n\x08\x42\x41\x43KWARD\x10\x01\x12\x0e\n\nX_POSITIVE\x10\x02\x12\x0e\n\nX_NEGATIVE\x10\x03\x12\x0e\n\nY_POSITIVE\x10\x04\x12\x0e\n\nY_NEGATIVE\x10\x05\x12\x0e\n\nZ_POSITIVE\x10\x06\x12\x0e\n\nZ_NEGATIVE\x10\x07\x12\x0c\n\x08ROTATE_X\x10\x08\x12\x0c\n\x08ROTATE_Y\x10\t\x12\x0c\n\x08ROTATE_Z\x10\nb\x06proto3')
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n%cmvr/api/humanoid_robot_command.proto\x12\x08\x63mvr.api\x1a\x15\x63mvr/api/common.proto\"8\n\x08JointCmd\x12\x12\n\njoint_name\x18\x01 \x01(\t\x12\x0b\n\x03rad\x18\x02 \x01(\x01\x12\x0b\n\x03vel\x18\x03 \x01(\x01\"M\n\x06Pose3D\x12\t\n\x01x\x18\x01 \x01(\x01\x12\t\n\x01y\x18\x02 \x01(\x01\x12\t\n\x01z\x18\x03 \x01(\x01\x12\n\n\x02rx\x18\x04 \x01(\x01\x12\n\n\x02ry\x18\x05 \x01(\x01\x12\n\n\x02rz\x18\x06 \x01(\x01\"\xe4\x01\n\x12TransformMatrix4x4\x12\x0b\n\x03m00\x18\x01 \x01(\x01\x12\x0b\n\x03m01\x18\x02 \x01(\x01\x12\x0b\n\x03m02\x18\x03 \x01(\x01\x12\x0b\n\x03m03\x18\x04 \x01(\x01\x12\x0b\n\x03m10\x18\x05 \x01(\x01\x12\x0b\n\x03m11\x18\x06 \x01(\x01\x12\x0b\n\x03m12\x18\x07 \x01(\x01\x12\x0b\n\x03m13\x18\x08 \x01(\x01\x12\x0b\n\x03m20\x18\t \x01(\x01\x12\x0b\n\x03m21\x18\n \x01(\x01\x12\x0b\n\x03m22\x18\x0b \x01(\x01\x12\x0b\n\x03m23\x18\x0c \x01(\x01\x12\x0b\n\x03m30\x18\r \x01(\x01\x12\x0b\n\x03m31\x18\x0e \x01(\x01\x12\x0b\n\x03m32\x18\x0f \x01(\x01\x12\x0b\n\x03m33\x18\x10 \x01(\x01\"\xbd\x01\n\x05MoveJ\x1av\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12 \n\x04\x63mds\x18\x02 \x03(\x0b\x32\x12.cmvr.api.JointCmd\x12\x0b\n\x03vel\x18\x03 \x01(\x01\x12\x0b\n\x03\x61\x63\x63\x18\x04 \x01(\x01\x1a<\n\x08Response\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\xc7\x01\n\x07GetPose\x1a^\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\x11\n\tbase_link\x18\x02 \x01(\t\x12\x0f\n\x07\x65\x65_link\x18\x03 \x01(\t\x1a\\\n\x08Response\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12\x1e\n\x04pose\x18\x02 \x01(\x0b\x32\x10.cmvr.api.Pose3D\"\xd3\x01\n\x05MoveL\x1a\x8b\x01\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\x0f\n\x07\x65\x65_link\x18\x02 \x01(\t\x12$\n\ntargetPose\x18\x03 \x01(\x0b\x32\x10.cmvr.api.Pose3D\x12\x0b\n\x03vel\x18\x04 \x01(\x01\x12\x0b\n\x03\x61\x63\x63\x18\x05 \x01(\x01\x1a<\n\x08Response\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\xe2\x01\n\x06SpeedJ\x1a\x99\x01\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\x12\n\njoint_name\x18\x02 \x01(\t\x12\x0b\n\x03vel\x18\x03 \x01(\x01\x12\x0b\n\x03\x61\x63\x63\x18\x04 \x01(\x01\x12/\n\x03\x64ir\x18\x05 \x01(\x0e\x32\".cmvr.api.RobotJointIndexDirection\x1a<\n\x08Response\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x87\x02\n\x06SpeedL\x1a\xbe\x01\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\x0f\n\x07\x65\x65_link\x18\x02 \x01(\t\x12\x0b\n\x03vel\x18\x03 \x01(\x01\x12\x0b\n\x03\x61\x63\x63\x18\x04 \x01(\x01\x12/\n\x03\x64ir\x18\x05 \x01(\x0e\x32\".cmvr.api.RobotJointIndexDirection\x12&\n\x04\x63\x61rt\x18\x06 \x01(\x0e\x32\x18.cmvr.api.RobotCartesian\x1a<\n\x08Response\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"a\n\nJointState\x12\x0c\n\x04name\x18\x01 \x03(\t\x12\x10\n\x08position\x18\x02 \x03(\x01\x12\x10\n\x08velocity\x18\x03 \x03(\x01\x12\x0e\n\x06\x65\x66\x66ort\x18\x04 \x03(\x01\x12\x11\n\ttimestamp\x18\x05 \x01(\x01\"f\n\rJointResponse\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12#\n\x05state\x18\x02 \x03(\x0b\x32\x14.cmvr.api.JointState\"?\n\x0cJointRequest\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\"\x9e\x01\n\x0e\x43\x61librateZeroQ\x1aN\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\x12\n\njoint_name\x18\x02 \x01(\t\x1a<\n\x08Response\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\xb1\x01\n\x06ServoJ\x1ai\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12 \n\x04\x63mds\x18\x02 \x03(\x0b\x32\x12.cmvr.api.JointCmd\x12\x0b\n\x03vel\x18\x03 \x01(\x01\x1a<\n\x08Response\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\xdb\x01\n\rGetPoseMatrix\x1a^\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\x11\n\tbase_link\x18\x02 \x01(\t\x12\x0f\n\x07\x65\x65_link\x18\x03 \x01(\t\x1aj\n\x08Response\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12,\n\x06matrix\x18\x02 \x01(\x0b\x32\x1c.cmvr.api.TransformMatrix4x4\"\x8b\x02\n\x18\x43omputeForwardKinematics\x1a\x82\x01\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12\x11\n\tbase_link\x18\x02 \x01(\t\x12\x0f\n\x07\x65\x65_link\x18\x03 \x01(\t\x12\"\n\x06joints\x18\x04 \x03(\x0b\x32\x12.cmvr.api.JointCmd\x1aj\n\x08Response\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12,\n\x06matrix\x18\x02 \x01(\x0b\x32\x1c.cmvr.api.TransformMatrix4x4*=\n\x0eRobotCartesian\x12\x05\n\x01X\x10\x00\x12\x05\n\x01Y\x10\x01\x12\x05\n\x01Z\x10\x02\x12\x06\n\x02RX\x10\x03\x12\x06\n\x02RY\x10\x04\x12\x06\n\x02RZ\x10\x05*\xbf\x01\n\x18RobotJointIndexDirection\x12\x0b\n\x07\x46ORWARD\x10\x00\x12\x0c\n\x08\x42\x41\x43KWARD\x10\x01\x12\x0e\n\nX_POSITIVE\x10\x02\x12\x0e\n\nX_NEGATIVE\x10\x03\x12\x0e\n\nY_POSITIVE\x10\x04\x12\x0e\n\nY_NEGATIVE\x10\x05\x12\x0e\n\nZ_POSITIVE\x10\x06\x12\x0e\n\nZ_NEGATIVE\x10\x07\x12\x0c\n\x08ROTATE_X\x10\x08\x12\x0c\n\x08ROTATE_Y\x10\t\x12\x0c\n\x08ROTATE_Z\x10\nb\x06proto3')
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.api.humanoid_robot_command_pb2', _globals)
if not _descriptor._USE_C_DESCRIPTORS:
DESCRIPTOR._loaded_options = None
- _globals['_ROBOTCARTESIAN']._serialized_start=1923
- _globals['_ROBOTCARTESIAN']._serialized_end=1984
- _globals['_ROBOTJOINTINDEXDIRECTION']._serialized_start=1987
- _globals['_ROBOTJOINTINDEXDIRECTION']._serialized_end=2178
+ _globals['_ROBOTCARTESIAN']._serialized_start=2646
+ _globals['_ROBOTCARTESIAN']._serialized_end=2707
+ _globals['_ROBOTJOINTINDEXDIRECTION']._serialized_start=2710
+ _globals['_ROBOTJOINTINDEXDIRECTION']._serialized_end=2901
_globals['_JOINTCMD']._serialized_start=74
_globals['_JOINTCMD']._serialized_end=130
_globals['_POSE3D']._serialized_start=132
_globals['_POSE3D']._serialized_end=209
- _globals['_MOVEJ']._serialized_start=212
- _globals['_MOVEJ']._serialized_end=401
- _globals['_MOVEJ_REQUEST']._serialized_start=221
- _globals['_MOVEJ_REQUEST']._serialized_end=339
- _globals['_MOVEJ_RESPONSE']._serialized_start=341
- _globals['_MOVEJ_RESPONSE']._serialized_end=401
- _globals['_GETPOSE']._serialized_start=404
- _globals['_GETPOSE']._serialized_end=603
- _globals['_GETPOSE_REQUEST']._serialized_start=415
- _globals['_GETPOSE_REQUEST']._serialized_end=509
- _globals['_GETPOSE_RESPONSE']._serialized_start=511
- _globals['_GETPOSE_RESPONSE']._serialized_end=603
- _globals['_MOVEL']._serialized_start=606
- _globals['_MOVEL']._serialized_end=817
- _globals['_MOVEL_REQUEST']._serialized_start=616
- _globals['_MOVEL_REQUEST']._serialized_end=755
- _globals['_MOVEL_RESPONSE']._serialized_start=341
- _globals['_MOVEL_RESPONSE']._serialized_end=401
- _globals['_SPEEDJ']._serialized_start=820
- _globals['_SPEEDJ']._serialized_end=1046
- _globals['_SPEEDJ_REQUEST']._serialized_start=831
- _globals['_SPEEDJ_REQUEST']._serialized_end=984
- _globals['_SPEEDJ_RESPONSE']._serialized_start=341
- _globals['_SPEEDJ_RESPONSE']._serialized_end=401
- _globals['_SPEEDL']._serialized_start=1049
- _globals['_SPEEDL']._serialized_end=1312
- _globals['_SPEEDL_REQUEST']._serialized_start=1060
- _globals['_SPEEDL_REQUEST']._serialized_end=1250
- _globals['_SPEEDL_RESPONSE']._serialized_start=341
- _globals['_SPEEDL_RESPONSE']._serialized_end=401
- _globals['_JOINTSTATE']._serialized_start=1314
- _globals['_JOINTSTATE']._serialized_end=1411
- _globals['_JOINTRESPONSE']._serialized_start=1413
- _globals['_JOINTRESPONSE']._serialized_end=1515
- _globals['_JOINTREQUEST']._serialized_start=1517
- _globals['_JOINTREQUEST']._serialized_end=1580
- _globals['_CALIBRATEZEROQ']._serialized_start=1583
- _globals['_CALIBRATEZEROQ']._serialized_end=1741
- _globals['_CALIBRATEZEROQ_REQUEST']._serialized_start=831
- _globals['_CALIBRATEZEROQ_REQUEST']._serialized_end=909
- _globals['_CALIBRATEZEROQ_RESPONSE']._serialized_start=341
- _globals['_CALIBRATEZEROQ_RESPONSE']._serialized_end=401
- _globals['_SERVOJ']._serialized_start=1744
- _globals['_SERVOJ']._serialized_end=1921
- _globals['_SERVOJ_REQUEST']._serialized_start=221
- _globals['_SERVOJ_REQUEST']._serialized_end=326
- _globals['_SERVOJ_RESPONSE']._serialized_start=341
- _globals['_SERVOJ_RESPONSE']._serialized_end=401
+ _globals['_TRANSFORMMATRIX4X4']._serialized_start=212
+ _globals['_TRANSFORMMATRIX4X4']._serialized_end=440
+ _globals['_MOVEJ']._serialized_start=443
+ _globals['_MOVEJ']._serialized_end=632
+ _globals['_MOVEJ_REQUEST']._serialized_start=452
+ _globals['_MOVEJ_REQUEST']._serialized_end=570
+ _globals['_MOVEJ_RESPONSE']._serialized_start=572
+ _globals['_MOVEJ_RESPONSE']._serialized_end=632
+ _globals['_GETPOSE']._serialized_start=635
+ _globals['_GETPOSE']._serialized_end=834
+ _globals['_GETPOSE_REQUEST']._serialized_start=646
+ _globals['_GETPOSE_REQUEST']._serialized_end=740
+ _globals['_GETPOSE_RESPONSE']._serialized_start=742
+ _globals['_GETPOSE_RESPONSE']._serialized_end=834
+ _globals['_MOVEL']._serialized_start=837
+ _globals['_MOVEL']._serialized_end=1048
+ _globals['_MOVEL_REQUEST']._serialized_start=847
+ _globals['_MOVEL_REQUEST']._serialized_end=986
+ _globals['_MOVEL_RESPONSE']._serialized_start=572
+ _globals['_MOVEL_RESPONSE']._serialized_end=632
+ _globals['_SPEEDJ']._serialized_start=1051
+ _globals['_SPEEDJ']._serialized_end=1277
+ _globals['_SPEEDJ_REQUEST']._serialized_start=1062
+ _globals['_SPEEDJ_REQUEST']._serialized_end=1215
+ _globals['_SPEEDJ_RESPONSE']._serialized_start=572
+ _globals['_SPEEDJ_RESPONSE']._serialized_end=632
+ _globals['_SPEEDL']._serialized_start=1280
+ _globals['_SPEEDL']._serialized_end=1543
+ _globals['_SPEEDL_REQUEST']._serialized_start=1291
+ _globals['_SPEEDL_REQUEST']._serialized_end=1481
+ _globals['_SPEEDL_RESPONSE']._serialized_start=572
+ _globals['_SPEEDL_RESPONSE']._serialized_end=632
+ _globals['_JOINTSTATE']._serialized_start=1545
+ _globals['_JOINTSTATE']._serialized_end=1642
+ _globals['_JOINTRESPONSE']._serialized_start=1644
+ _globals['_JOINTRESPONSE']._serialized_end=1746
+ _globals['_JOINTREQUEST']._serialized_start=1748
+ _globals['_JOINTREQUEST']._serialized_end=1811
+ _globals['_CALIBRATEZEROQ']._serialized_start=1814
+ _globals['_CALIBRATEZEROQ']._serialized_end=1972
+ _globals['_CALIBRATEZEROQ_REQUEST']._serialized_start=1062
+ _globals['_CALIBRATEZEROQ_REQUEST']._serialized_end=1140
+ _globals['_CALIBRATEZEROQ_RESPONSE']._serialized_start=572
+ _globals['_CALIBRATEZEROQ_RESPONSE']._serialized_end=632
+ _globals['_SERVOJ']._serialized_start=1975
+ _globals['_SERVOJ']._serialized_end=2152
+ _globals['_SERVOJ_REQUEST']._serialized_start=452
+ _globals['_SERVOJ_REQUEST']._serialized_end=557
+ _globals['_SERVOJ_RESPONSE']._serialized_start=572
+ _globals['_SERVOJ_RESPONSE']._serialized_end=632
+ _globals['_GETPOSEMATRIX']._serialized_start=2155
+ _globals['_GETPOSEMATRIX']._serialized_end=2374
+ _globals['_GETPOSEMATRIX_REQUEST']._serialized_start=646
+ _globals['_GETPOSEMATRIX_REQUEST']._serialized_end=740
+ _globals['_GETPOSEMATRIX_RESPONSE']._serialized_start=2268
+ _globals['_GETPOSEMATRIX_RESPONSE']._serialized_end=2374
+ _globals['_COMPUTEFORWARDKINEMATICS']._serialized_start=2377
+ _globals['_COMPUTEFORWARDKINEMATICS']._serialized_end=2644
+ _globals['_COMPUTEFORWARDKINEMATICS_REQUEST']._serialized_start=2406
+ _globals['_COMPUTEFORWARDKINEMATICS_REQUEST']._serialized_end=2536
+ _globals['_COMPUTEFORWARDKINEMATICS_RESPONSE']._serialized_start=2268
+ _globals['_COMPUTEFORWARDKINEMATICS_RESPONSE']._serialized_end=2374
# @@protoc_insertion_point(module_scope)
diff --git a/generated/cmvr/api/humanoid_robot_service_pb2.py b/generated/cmvr/api/humanoid_robot_service_pb2.py
index c466cd2..822a742 100644
--- a/generated/cmvr/api/humanoid_robot_service_pb2.py
+++ b/generated/cmvr/api/humanoid_robot_service_pb2.py
@@ -26,7 +26,7 @@ from cmvr.api import common_pb2 as cmvr_dot_api_dot_common__pb2
from cmvr.api import humanoid_robot_command_pb2 as cmvr_dot_api_dot_humanoid__robot__command__pb2
-DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n%cmvr/api/humanoid_robot_service.proto\x12\x08\x63mvr.api\x1a\x15\x63mvr/api/common.proto\x1a%cmvr/api/humanoid_robot_command.proto2\xc5\x05\n\x14HumanoidRobotService\x12N\n\ttorqueOff\x12\x1f.cmvr.api.CommandHeader.Request\x1a .cmvr.api.CommandHeader.Feedback\x12M\n\x08torqueOn\x12\x1f.cmvr.api.CommandHeader.Request\x1a .cmvr.api.CommandHeader.Feedback\x12:\n\x05moveJ\x12\x17.cmvr.api.MoveJ.Request\x1a\x18.cmvr.api.MoveJ.Response\x12:\n\x05moveL\x12\x17.cmvr.api.MoveL.Request\x1a\x18.cmvr.api.MoveL.Response\x12=\n\x06speedJ\x12\x18.cmvr.api.SpeedJ.Request\x1a\x19.cmvr.api.SpeedJ.Response\x12=\n\x06speedL\x12\x18.cmvr.api.SpeedL.Request\x1a\x19.cmvr.api.SpeedL.Response\x12@\n\rgetJointState\x12\x16.cmvr.api.JointRequest\x1a\x17.cmvr.api.JointResponse\x12@\n\x07getPose\x12\x19.cmvr.api.GetPose.Request\x1a\x1a.cmvr.api.GetPose.Response\x12U\n\x0e\x63\x61librateZeroQ\x12 .cmvr.api.CalibrateZeroQ.Request\x1a!.cmvr.api.CalibrateZeroQ.Response\x12=\n\x06servoJ\x12\x18.cmvr.api.ServoJ.Request\x1a\x19.cmvr.api.ServoJ.Responseb\x06proto3')
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n%cmvr/api/humanoid_robot_service.proto\x12\x08\x63mvr.api\x1a\x15\x63mvr/api/common.proto\x1a%cmvr/api/humanoid_robot_command.proto2\x8e\x07\n\x14HumanoidRobotService\x12N\n\ttorqueOff\x12\x1f.cmvr.api.CommandHeader.Request\x1a .cmvr.api.CommandHeader.Feedback\x12M\n\x08torqueOn\x12\x1f.cmvr.api.CommandHeader.Request\x1a .cmvr.api.CommandHeader.Feedback\x12:\n\x05moveJ\x12\x17.cmvr.api.MoveJ.Request\x1a\x18.cmvr.api.MoveJ.Response\x12:\n\x05moveL\x12\x17.cmvr.api.MoveL.Request\x1a\x18.cmvr.api.MoveL.Response\x12=\n\x06speedJ\x12\x18.cmvr.api.SpeedJ.Request\x1a\x19.cmvr.api.SpeedJ.Response\x12=\n\x06speedL\x12\x18.cmvr.api.SpeedL.Request\x1a\x19.cmvr.api.SpeedL.Response\x12@\n\rgetJointState\x12\x16.cmvr.api.JointRequest\x1a\x17.cmvr.api.JointResponse\x12@\n\x07getPose\x12\x19.cmvr.api.GetPose.Request\x1a\x1a.cmvr.api.GetPose.Response\x12U\n\x0e\x63\x61librateZeroQ\x12 .cmvr.api.CalibrateZeroQ.Request\x1a!.cmvr.api.CalibrateZeroQ.Response\x12=\n\x06servoJ\x12\x18.cmvr.api.ServoJ.Request\x1a\x19.cmvr.api.ServoJ.Response\x12R\n\rgetPoseMatrix\x12\x1f.cmvr.api.GetPoseMatrix.Request\x1a .cmvr.api.GetPoseMatrix.Response\x12s\n\x18\x63omputeForwardKinematics\x12*.cmvr.api.ComputeForwardKinematics.Request\x1a+.cmvr.api.ComputeForwardKinematics.Responseb\x06proto3')
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -34,5 +34,5 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.api.humanoid_robot_ser
if not _descriptor._USE_C_DESCRIPTORS:
DESCRIPTOR._loaded_options = None
_globals['_HUMANOIDROBOTSERVICE']._serialized_start=114
- _globals['_HUMANOIDROBOTSERVICE']._serialized_end=823
+ _globals['_HUMANOIDROBOTSERVICE']._serialized_end=1024
# @@protoc_insertion_point(module_scope)
diff --git a/generated/cmvr/api/humanoid_robot_service_pb2_grpc.py b/generated/cmvr/api/humanoid_robot_service_pb2_grpc.py
index cd15fc7..6536b92 100644
--- a/generated/cmvr/api/humanoid_robot_service_pb2_grpc.py
+++ b/generated/cmvr/api/humanoid_robot_service_pb2_grpc.py
@@ -85,6 +85,16 @@ class HumanoidRobotServiceStub(object):
request_serializer=cmvr_dot_api_dot_humanoid__robot__command__pb2.ServoJ.Request.SerializeToString,
response_deserializer=cmvr_dot_api_dot_humanoid__robot__command__pb2.ServoJ.Response.FromString,
_registered_method=True)
+ self.getPoseMatrix = channel.unary_unary(
+ '/cmvr.api.HumanoidRobotService/getPoseMatrix',
+ request_serializer=cmvr_dot_api_dot_humanoid__robot__command__pb2.GetPoseMatrix.Request.SerializeToString,
+ response_deserializer=cmvr_dot_api_dot_humanoid__robot__command__pb2.GetPoseMatrix.Response.FromString,
+ _registered_method=True)
+ self.computeForwardKinematics = channel.unary_unary(
+ '/cmvr.api.HumanoidRobotService/computeForwardKinematics',
+ request_serializer=cmvr_dot_api_dot_humanoid__robot__command__pb2.ComputeForwardKinematics.Request.SerializeToString,
+ response_deserializer=cmvr_dot_api_dot_humanoid__robot__command__pb2.ComputeForwardKinematics.Response.FromString,
+ _registered_method=True)
class HumanoidRobotServiceServicer(object):
@@ -150,6 +160,18 @@ class HumanoidRobotServiceServicer(object):
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
+ def getPoseMatrix(self, request, context):
+ """Missing associated documentation comment in .proto file."""
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+ context.set_details('Method not implemented!')
+ raise NotImplementedError('Method not implemented!')
+
+ def computeForwardKinematics(self, request, context):
+ """Missing associated documentation comment in .proto file."""
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+ context.set_details('Method not implemented!')
+ raise NotImplementedError('Method not implemented!')
+
def add_HumanoidRobotServiceServicer_to_server(servicer, server):
rpc_method_handlers = {
@@ -203,6 +225,16 @@ def add_HumanoidRobotServiceServicer_to_server(servicer, server):
request_deserializer=cmvr_dot_api_dot_humanoid__robot__command__pb2.ServoJ.Request.FromString,
response_serializer=cmvr_dot_api_dot_humanoid__robot__command__pb2.ServoJ.Response.SerializeToString,
),
+ 'getPoseMatrix': grpc.unary_unary_rpc_method_handler(
+ servicer.getPoseMatrix,
+ request_deserializer=cmvr_dot_api_dot_humanoid__robot__command__pb2.GetPoseMatrix.Request.FromString,
+ response_serializer=cmvr_dot_api_dot_humanoid__robot__command__pb2.GetPoseMatrix.Response.SerializeToString,
+ ),
+ 'computeForwardKinematics': grpc.unary_unary_rpc_method_handler(
+ servicer.computeForwardKinematics,
+ request_deserializer=cmvr_dot_api_dot_humanoid__robot__command__pb2.ComputeForwardKinematics.Request.FromString,
+ response_serializer=cmvr_dot_api_dot_humanoid__robot__command__pb2.ComputeForwardKinematics.Response.SerializeToString,
+ ),
}
generic_handler = grpc.method_handlers_generic_handler(
'cmvr.api.HumanoidRobotService', rpc_method_handlers)
@@ -483,3 +515,57 @@ class HumanoidRobotService(object):
timeout,
metadata,
_registered_method=True)
+
+ @staticmethod
+ def getPoseMatrix(request,
+ target,
+ options=(),
+ channel_credentials=None,
+ call_credentials=None,
+ insecure=False,
+ compression=None,
+ wait_for_ready=None,
+ timeout=None,
+ metadata=None):
+ return grpc.experimental.unary_unary(
+ request,
+ target,
+ '/cmvr.api.HumanoidRobotService/getPoseMatrix',
+ cmvr_dot_api_dot_humanoid__robot__command__pb2.GetPoseMatrix.Request.SerializeToString,
+ cmvr_dot_api_dot_humanoid__robot__command__pb2.GetPoseMatrix.Response.FromString,
+ options,
+ channel_credentials,
+ insecure,
+ call_credentials,
+ compression,
+ wait_for_ready,
+ timeout,
+ metadata,
+ _registered_method=True)
+
+ @staticmethod
+ def computeForwardKinematics(request,
+ target,
+ options=(),
+ channel_credentials=None,
+ call_credentials=None,
+ insecure=False,
+ compression=None,
+ wait_for_ready=None,
+ timeout=None,
+ metadata=None):
+ return grpc.experimental.unary_unary(
+ request,
+ target,
+ '/cmvr.api.HumanoidRobotService/computeForwardKinematics',
+ cmvr_dot_api_dot_humanoid__robot__command__pb2.ComputeForwardKinematics.Request.SerializeToString,
+ cmvr_dot_api_dot_humanoid__robot__command__pb2.ComputeForwardKinematics.Response.FromString,
+ options,
+ channel_credentials,
+ insecure,
+ call_credentials,
+ compression,
+ wait_for_ready,
+ timeout,
+ metadata,
+ _registered_method=True)
diff --git a/generated/cmvr/api/system_command_pb2.py b/generated/cmvr/api/system_command_pb2.py
index e76b9d5..c60017f 100644
--- a/generated/cmvr/api/system_command_pb2.py
+++ b/generated/cmvr/api/system_command_pb2.py
@@ -25,15 +25,15 @@ _sym_db = _symbol_database.Default()
from cmvr.api import common_pb2 as cmvr_dot_api_dot_common__pb2
-DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1d\x63mvr/api/system_command.proto\x12\x08\x63mvr.api\x1a\x15\x63mvr/api/common.proto\"J\n\nDeviceList\x12\x11\n\tdevice_id\x18\x01 \x01(\t\x12)\n\x0b\x64\x65vice_type\x18\x02 \x01(\x0e\x32\x14.cmvr.api.DeviceType\"\xd5\x01\n\x14GetSystemInfoCommand\x1a\t\n\x07Request\x1a\xb1\x01\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12\x13\n\x0bsystem_name\x18\x02 \x01(\t\x12\x0f\n\x07version\x18\x03 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x04 \x01(\t\x12\n\n\x02os\x18\x05 \x01(\t\x12\x16\n\x0ekernel_version\x18\x06 \x01(\t\x12\x14\n\x0c\x61rchitecture\x18\x07 \x01(\t\"\xf8\x01\n\x16GetSystemStatusCommand\x1a\t\n\x07Request\x1a\xd2\x01\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12\x11\n\tcpu_usage\x18\x02 \x01(\x02\x12\x14\n\x0cmem_total_mb\x18\x03 \x01(\x02\x12\x13\n\x0bmem_used_mb\x18\x04 \x01(\x02\x12\x15\n\rdisk_total_gb\x18\x05 \x01(\x02\x12\x14\n\x0c\x64isk_used_gb\x18\x06 \x01(\x02\x12)\n\x0b\x64\x65vice_list\x18\x07 \x03(\x0b\x32\x14.cmvr.api.DeviceList\"\xb6\x01\n\x13UpdateParamsCommand\x1a\x61\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12%\n\x06params\x18\x02 \x03(\x0b\x32\x15.cmvr.api.ConfigParam\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback*}\n\nDeviceType\x12\x07\n\x03\x41GV\x10\x00\x12\x0b\n\x07\x42\x61ttery\x10\x01\x12\n\n\x06\x43\x61mera\x10\x02\x12\x0b\n\x07\x44\x65xHand\x10\x03\x12\x0b\n\x07Gripper\x10\x04\x12\x0e\n\nMicrophone\x10\x05\x12\t\n\x05Robot\x10\x06\x12\x0b\n\x07Speaker\x10\x07\x12\x0b\n\x07Unknown\x10\x14\x62\x06proto3')
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1d\x63mvr/api/system_command.proto\x12\x08\x63mvr.api\x1a\x15\x63mvr/api/common.proto\"J\n\nDeviceList\x12\x11\n\tdevice_id\x18\x01 \x01(\t\x12)\n\x0b\x64\x65vice_type\x18\x02 \x01(\x0e\x32\x14.cmvr.api.DeviceType\"\xd5\x01\n\x14GetSystemInfoCommand\x1a\t\n\x07Request\x1a\xb1\x01\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12\x13\n\x0bsystem_name\x18\x02 \x01(\t\x12\x0f\n\x07version\x18\x03 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x04 \x01(\t\x12\n\n\x02os\x18\x05 \x01(\t\x12\x16\n\x0ekernel_version\x18\x06 \x01(\t\x12\x14\n\x0c\x61rchitecture\x18\x07 \x01(\t\"\xf8\x01\n\x16GetSystemStatusCommand\x1a\t\n\x07Request\x1a\xd2\x01\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\x12\x11\n\tcpu_usage\x18\x02 \x01(\x02\x12\x14\n\x0cmem_total_mb\x18\x03 \x01(\x02\x12\x13\n\x0bmem_used_mb\x18\x04 \x01(\x02\x12\x15\n\rdisk_total_gb\x18\x05 \x01(\x02\x12\x14\n\x0c\x64isk_used_gb\x18\x06 \x01(\x02\x12)\n\x0b\x64\x65vice_list\x18\x07 \x03(\x0b\x32\x14.cmvr.api.DeviceList\"\xb6\x01\n\x13UpdateParamsCommand\x1a\x61\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x12%\n\x06params\x18\x02 \x03(\x0b\x32\x15.cmvr.api.ConfigParam\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback\"\x8a\x01\n\x0eStopAllCommand\x1a:\n\x07Request\x12/\n\x06header\x18\x01 \x01(\x0b\x32\x1f.cmvr.api.CommandHeader.Request\x1a<\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x30\n\x06header\x18\x01 \x01(\x0b\x32 .cmvr.api.CommandHeader.Feedback*}\n\nDeviceType\x12\x07\n\x03\x41GV\x10\x00\x12\x0b\n\x07\x42\x61ttery\x10\x01\x12\n\n\x06\x43\x61mera\x10\x02\x12\x0b\n\x07\x44\x65xHand\x10\x03\x12\x0b\n\x07Gripper\x10\x04\x12\x0e\n\nMicrophone\x10\x05\x12\t\n\x05Robot\x10\x06\x12\x0b\n\x07Speaker\x10\x07\x12\x0b\n\x07Unknown\x10\x14\x62\x06proto3')
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.api.system_command_pb2', _globals)
if not _descriptor._USE_C_DESCRIPTORS:
DESCRIPTOR._loaded_options = None
- _globals['_DEVICETYPE']._serialized_start=794
- _globals['_DEVICETYPE']._serialized_end=919
+ _globals['_DEVICETYPE']._serialized_start=935
+ _globals['_DEVICETYPE']._serialized_end=1060
_globals['_DEVICELIST']._serialized_start=66
_globals['_DEVICELIST']._serialized_end=140
_globals['_GETSYSTEMINFOCOMMAND']._serialized_start=143
@@ -54,4 +54,10 @@ if not _descriptor._USE_C_DESCRIPTORS:
_globals['_UPDATEPARAMSCOMMAND_REQUEST']._serialized_end=730
_globals['_UPDATEPARAMSCOMMAND_FEEDBACK']._serialized_start=179
_globals['_UPDATEPARAMSCOMMAND_FEEDBACK']._serialized_end=239
+ _globals['_STOPALLCOMMAND']._serialized_start=795
+ _globals['_STOPALLCOMMAND']._serialized_end=933
+ _globals['_STOPALLCOMMAND_REQUEST']._serialized_start=633
+ _globals['_STOPALLCOMMAND_REQUEST']._serialized_end=691
+ _globals['_STOPALLCOMMAND_FEEDBACK']._serialized_start=179
+ _globals['_STOPALLCOMMAND_FEEDBACK']._serialized_end=239
# @@protoc_insertion_point(module_scope)
diff --git a/generated/cmvr/api/system_service_pb2.py b/generated/cmvr/api/system_service_pb2.py
index af84fab..4dea91f 100644
--- a/generated/cmvr/api/system_service_pb2.py
+++ b/generated/cmvr/api/system_service_pb2.py
@@ -25,7 +25,7 @@ _sym_db = _symbol_database.Default()
from cmvr.api import system_command_pb2 as cmvr_dot_api_dot_system__command__pb2
-DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1d\x63mvr/api/system_service.proto\x12\x08\x63mvr.api\x1a\x1d\x63mvr/api/system_command.proto2\xbe\x02\n\rSystemService\x12\x62\n\rGetSystemInfo\x12&.cmvr.api.GetSystemInfoCommand.Request\x1a\'.cmvr.api.GetSystemInfoCommand.Feedback\"\x00\x12h\n\x0fGetSystemStatus\x12(.cmvr.api.GetSystemStatusCommand.Request\x1a).cmvr.api.GetSystemStatusCommand.Feedback\"\x00\x12_\n\x0cUpdateParams\x12%.cmvr.api.UpdateParamsCommand.Request\x1a&.cmvr.api.UpdateParamsCommand.Feedback\"\x00\x62\x06proto3')
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1d\x63mvr/api/system_service.proto\x12\x08\x63mvr.api\x1a\x1d\x63mvr/api/system_command.proto2\x90\x03\n\rSystemService\x12\x62\n\rGetSystemInfo\x12&.cmvr.api.GetSystemInfoCommand.Request\x1a\'.cmvr.api.GetSystemInfoCommand.Feedback\"\x00\x12h\n\x0fGetSystemStatus\x12(.cmvr.api.GetSystemStatusCommand.Request\x1a).cmvr.api.GetSystemStatusCommand.Feedback\"\x00\x12_\n\x0cUpdateParams\x12%.cmvr.api.UpdateParamsCommand.Request\x1a&.cmvr.api.UpdateParamsCommand.Feedback\"\x00\x12P\n\x07StopAll\x12 .cmvr.api.StopAllCommand.Request\x1a!.cmvr.api.StopAllCommand.Feedback\"\x00\x62\x06proto3')
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -33,5 +33,5 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.api.system_service_pb2
if not _descriptor._USE_C_DESCRIPTORS:
DESCRIPTOR._loaded_options = None
_globals['_SYSTEMSERVICE']._serialized_start=75
- _globals['_SYSTEMSERVICE']._serialized_end=393
+ _globals['_SYSTEMSERVICE']._serialized_end=475
# @@protoc_insertion_point(module_scope)
diff --git a/generated/cmvr/api/system_service_pb2_grpc.py b/generated/cmvr/api/system_service_pb2_grpc.py
index 215234d..7262550 100644
--- a/generated/cmvr/api/system_service_pb2_grpc.py
+++ b/generated/cmvr/api/system_service_pb2_grpc.py
@@ -49,6 +49,11 @@ class SystemServiceStub(object):
request_serializer=cmvr_dot_api_dot_system__command__pb2.UpdateParamsCommand.Request.SerializeToString,
response_deserializer=cmvr_dot_api_dot_system__command__pb2.UpdateParamsCommand.Feedback.FromString,
_registered_method=True)
+ self.StopAll = channel.unary_unary(
+ '/cmvr.api.SystemService/StopAll',
+ request_serializer=cmvr_dot_api_dot_system__command__pb2.StopAllCommand.Request.SerializeToString,
+ response_deserializer=cmvr_dot_api_dot_system__command__pb2.StopAllCommand.Feedback.FromString,
+ _registered_method=True)
class SystemServiceServicer(object):
@@ -72,6 +77,12 @@ class SystemServiceServicer(object):
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
+ def StopAll(self, request, context):
+ """Missing associated documentation comment in .proto file."""
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+ context.set_details('Method not implemented!')
+ raise NotImplementedError('Method not implemented!')
+
def add_SystemServiceServicer_to_server(servicer, server):
rpc_method_handlers = {
@@ -90,6 +101,11 @@ def add_SystemServiceServicer_to_server(servicer, server):
request_deserializer=cmvr_dot_api_dot_system__command__pb2.UpdateParamsCommand.Request.FromString,
response_serializer=cmvr_dot_api_dot_system__command__pb2.UpdateParamsCommand.Feedback.SerializeToString,
),
+ 'StopAll': grpc.unary_unary_rpc_method_handler(
+ servicer.StopAll,
+ request_deserializer=cmvr_dot_api_dot_system__command__pb2.StopAllCommand.Request.FromString,
+ response_serializer=cmvr_dot_api_dot_system__command__pb2.StopAllCommand.Feedback.SerializeToString,
+ ),
}
generic_handler = grpc.method_handlers_generic_handler(
'cmvr.api.SystemService', rpc_method_handlers)
@@ -181,3 +197,30 @@ class SystemService(object):
timeout,
metadata,
_registered_method=True)
+
+ @staticmethod
+ def StopAll(request,
+ target,
+ options=(),
+ channel_credentials=None,
+ call_credentials=None,
+ insecure=False,
+ compression=None,
+ wait_for_ready=None,
+ timeout=None,
+ metadata=None):
+ return grpc.experimental.unary_unary(
+ request,
+ target,
+ '/cmvr.api.SystemService/StopAll',
+ cmvr_dot_api_dot_system__command__pb2.StopAllCommand.Request.SerializeToString,
+ cmvr_dot_api_dot_system__command__pb2.StopAllCommand.Feedback.FromString,
+ options,
+ channel_credentials,
+ insecure,
+ call_credentials,
+ compression,
+ wait_for_ready,
+ timeout,
+ metadata,
+ _registered_method=True)
diff --git a/generated/cmvr/config/__init__.py b/generated/cmvr/config/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/generated/cmvr/config/camera_config/__init__.py b/generated/cmvr/config/camera_config/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/generated/cmvr/config/camera_config/camera_config_pb2.py b/generated/cmvr/config/camera_config/camera_config_pb2.py
new file mode 100644
index 0000000..d43d179
--- /dev/null
+++ b/generated/cmvr/config/camera_config/camera_config_pb2.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# NO CHECKED-IN PROTOBUF GENCODE
+# source: cmvr/config/camera_config/camera_config.proto
+# Protobuf Python Version: 6.31.1
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import runtime_version as _runtime_version
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf.internal import builder as _builder
+_runtime_version.ValidateProtobufRuntimeVersion(
+ _runtime_version.Domain.PUBLIC,
+ 6,
+ 31,
+ 1,
+ '',
+ 'cmvr/config/camera_config/camera_config.proto'
+)
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n-cmvr/config/camera_config/camera_config.proto\x12\x0b\x63mvr.config\"\xdc\x02\n\x15RealSenseCameraConfig\x12\n\n\x02id\x18\x01 \x01(\t\x12\x14\n\x0cserialNumber\x18\x02 \x01(\t\x12\r\n\x05width\x18\x03 \x01(\x05\x12\x0e\n\x06height\x18\x04 \x01(\x05\x12\x0b\n\x03\x66ps\x18\x05 \x01(\x05\x12\r\n\x05\x63odec\x18\x06 \x01(\t\x12,\n\x0b\x63\x61mera_mode\x18\x07 \x01(\x0e\x32\x17.cmvr.config.CameraMode\x12,\n\x0bstream_mode\x18\x08 \x01(\x0e\x32\x17.cmvr.config.StreamMode\x12*\n\nalign_mode\x18\t \x01(\x0e\x32\x16.cmvr.config.AlignMode\x12\x13\n\x0b\x62uffer_size\x18\n \x01(\x05\x12\x0c\n\x04sync\x18\x0b \x01(\x08\x12\x0e\n\x06\x65nable\x18\x0c \x01(\x08\x12\x14\n\x0c\x65ncode_width\x18\r \x01(\x05\x12\x15\n\rencode_height\x18\x0e \x01(\x05\"\x93\x02\n\x0fUVCCameraConfig\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0b\n\x03usb\x18\x02 \x01(\t\x12\r\n\x05width\x18\x03 \x01(\x05\x12\x0e\n\x06height\x18\x04 \x01(\x05\x12\x0b\n\x03\x66ps\x18\x05 \x01(\x05\x12\r\n\x05\x63odec\x18\x06 \x01(\t\x12,\n\x0b\x63\x61mera_mode\x18\x07 \x01(\x0e\x32\x17.cmvr.config.CameraMode\x12,\n\x0bstream_mode\x18\x08 \x01(\x0e\x32\x17.cmvr.config.StreamMode\x12\x13\n\x0b\x62uffer_size\x18\t \x01(\x05\x12\x0e\n\x06\x65nable\x18\n \x01(\x08\x12\x14\n\x0c\x65ncode_width\x18\x0b \x01(\x05\x12\x15\n\rencode_height\x18\x0c \x01(\x05\"\x80\x01\n\x0c\x43\x61meraConfig\x12\x31\n\x0buvc_cameras\x18\x01 \x03(\x0b\x32\x1c.cmvr.config.UVCCameraConfig\x12=\n\x11realsense_cameras\x18\x02 \x03(\x0b\x32\".cmvr.config.RealSenseCameraConfig*:\n\nCameraMode\x12\x15\n\x11\x43\x41MERA_MODE_PHOTO\x10\x00\x12\x15\n\x11\x43\x41MERA_MODE_VIDEO\x10\x01*N\n\nStreamMode\x12\x13\n\x0fSTREAM_MODE_RGB\x10\x00\x12\x14\n\x10STREAM_MODE_RGBD\x10\x01\x12\x15\n\x11STREAM_MODE_DEPTH\x10\x02*7\n\tAlignMode\x12\x14\n\x10\x41LIGN_MODE_COLOR\x10\x00\x12\x14\n\x10\x41LIGN_MODE_DEPTH\x10\x01\x62\x06proto3')
+
+_globals = globals()
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.config.camera_config.camera_config_pb2', _globals)
+if not _descriptor._USE_C_DESCRIPTORS:
+ DESCRIPTOR._loaded_options = None
+ _globals['_CAMERAMODE']._serialized_start=822
+ _globals['_CAMERAMODE']._serialized_end=880
+ _globals['_STREAMMODE']._serialized_start=882
+ _globals['_STREAMMODE']._serialized_end=960
+ _globals['_ALIGNMODE']._serialized_start=962
+ _globals['_ALIGNMODE']._serialized_end=1017
+ _globals['_REALSENSECAMERACONFIG']._serialized_start=63
+ _globals['_REALSENSECAMERACONFIG']._serialized_end=411
+ _globals['_UVCCAMERACONFIG']._serialized_start=414
+ _globals['_UVCCAMERACONFIG']._serialized_end=689
+ _globals['_CAMERACONFIG']._serialized_start=692
+ _globals['_CAMERACONFIG']._serialized_end=820
+# @@protoc_insertion_point(module_scope)
diff --git a/generated/cmvr/config/camera_config/camera_config_pb2_grpc.py b/generated/cmvr/config/camera_config/camera_config_pb2_grpc.py
new file mode 100644
index 0000000..80a33da
--- /dev/null
+++ b/generated/cmvr/config/camera_config/camera_config_pb2_grpc.py
@@ -0,0 +1,24 @@
+# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
+"""Client and server classes corresponding to protobuf-defined services."""
+import grpc
+import warnings
+
+
+GRPC_GENERATED_VERSION = '1.76.0'
+GRPC_VERSION = grpc.__version__
+_version_not_supported = False
+
+try:
+ from grpc._utilities import first_version_is_lower
+ _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
+except ImportError:
+ _version_not_supported = True
+
+if _version_not_supported:
+ raise RuntimeError(
+ f'The grpc package installed is at version {GRPC_VERSION},'
+ + ' but the generated code in cmvr/config/camera_config/camera_config_pb2_grpc.py depends on'
+ + f' grpcio>={GRPC_GENERATED_VERSION}.'
+ + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
+ + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
+ )
diff --git a/generated/cmvr/config/dexhand_config/__init__.py b/generated/cmvr/config/dexhand_config/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/generated/cmvr/config/dexhand_config/dexhand_config_pb2.py b/generated/cmvr/config/dexhand_config/dexhand_config_pb2.py
new file mode 100644
index 0000000..35bf933
--- /dev/null
+++ b/generated/cmvr/config/dexhand_config/dexhand_config_pb2.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# NO CHECKED-IN PROTOBUF GENCODE
+# source: cmvr/config/dexhand_config/dexhand_config.proto
+# Protobuf Python Version: 6.31.1
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import runtime_version as _runtime_version
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf.internal import builder as _builder
+_runtime_version.ValidateProtobufRuntimeVersion(
+ _runtime_version.Domain.PUBLIC,
+ 6,
+ 31,
+ 1,
+ '',
+ 'cmvr/config/dexhand_config/dexhand_config.proto'
+)
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n/cmvr/config/dexhand_config/dexhand_config.proto\x12\x0b\x63mvr.config\"M\n\x15RH56DFTPDexHandConfig\x12\n\n\x02id\x18\x01 \x01(\t\x12\n\n\x02ip\x18\x02 \x01(\t\x12\x0c\n\x04port\x18\x03 \x01(\x05\x12\x0e\n\x06\x65nable\x18\x04 \x01(\x08\"N\n\rDexHandConfig\x12=\n\x11rh56dftp_dexhands\x18\x01 \x03(\x0b\x32\".cmvr.config.RH56DFTPDexHandConfigb\x06proto3')
+
+_globals = globals()
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.config.dexhand_config.dexhand_config_pb2', _globals)
+if not _descriptor._USE_C_DESCRIPTORS:
+ DESCRIPTOR._loaded_options = None
+ _globals['_RH56DFTPDEXHANDCONFIG']._serialized_start=64
+ _globals['_RH56DFTPDEXHANDCONFIG']._serialized_end=141
+ _globals['_DEXHANDCONFIG']._serialized_start=143
+ _globals['_DEXHANDCONFIG']._serialized_end=221
+# @@protoc_insertion_point(module_scope)
diff --git a/generated/cmvr/config/dexhand_config/dexhand_config_pb2_grpc.py b/generated/cmvr/config/dexhand_config/dexhand_config_pb2_grpc.py
new file mode 100644
index 0000000..48d4f5e
--- /dev/null
+++ b/generated/cmvr/config/dexhand_config/dexhand_config_pb2_grpc.py
@@ -0,0 +1,24 @@
+# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
+"""Client and server classes corresponding to protobuf-defined services."""
+import grpc
+import warnings
+
+
+GRPC_GENERATED_VERSION = '1.76.0'
+GRPC_VERSION = grpc.__version__
+_version_not_supported = False
+
+try:
+ from grpc._utilities import first_version_is_lower
+ _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
+except ImportError:
+ _version_not_supported = True
+
+if _version_not_supported:
+ raise RuntimeError(
+ f'The grpc package installed is at version {GRPC_VERSION},'
+ + ' but the generated code in cmvr/config/dexhand_config/dexhand_config_pb2_grpc.py depends on'
+ + f' grpcio>={GRPC_GENERATED_VERSION}.'
+ + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
+ + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
+ )
diff --git a/generated/cmvr/config/microphone_config/__init__.py b/generated/cmvr/config/microphone_config/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/generated/cmvr/config/microphone_config/microphone_config_pb2.py b/generated/cmvr/config/microphone_config/microphone_config_pb2.py
new file mode 100644
index 0000000..80620f7
--- /dev/null
+++ b/generated/cmvr/config/microphone_config/microphone_config_pb2.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# NO CHECKED-IN PROTOBUF GENCODE
+# source: cmvr/config/microphone_config/microphone_config.proto
+# Protobuf Python Version: 6.31.1
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import runtime_version as _runtime_version
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf.internal import builder as _builder
+_runtime_version.ValidateProtobufRuntimeVersion(
+ _runtime_version.Domain.PUBLIC,
+ 6,
+ 31,
+ 1,
+ '',
+ 'cmvr/config/microphone_config/microphone_config.proto'
+)
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n5cmvr/config/microphone_config/microphone_config.proto\x12\x0b\x63mvr.config\"\x80\x01\n\x16\x46\x46MpegMicroPhoneConfig\x12\n\n\x02id\x18\x01 \x01(\t\x12\x10\n\x08\x63hannels\x18\x02 \x01(\x05\x12\x12\n\nsampleRate\x18\x03 \x01(\x05\x12\x0e\n\x06volume\x18\x04 \x01(\x05\x12\x0e\n\x06\x65nable\x18\x05 \x01(\x08\x12\x14\n\x0cinput_device\x18\x06 \x01(\t\"S\n\x10MicroPhoneConfig\x12?\n\x12\x66\x66mpeg_microphones\x18\x01 \x03(\x0b\x32#.cmvr.config.FFMpegMicroPhoneConfigb\x06proto3')
+
+_globals = globals()
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.config.microphone_config.microphone_config_pb2', _globals)
+if not _descriptor._USE_C_DESCRIPTORS:
+ DESCRIPTOR._loaded_options = None
+ _globals['_FFMPEGMICROPHONECONFIG']._serialized_start=71
+ _globals['_FFMPEGMICROPHONECONFIG']._serialized_end=199
+ _globals['_MICROPHONECONFIG']._serialized_start=201
+ _globals['_MICROPHONECONFIG']._serialized_end=284
+# @@protoc_insertion_point(module_scope)
diff --git a/generated/cmvr/config/microphone_config/microphone_config_pb2_grpc.py b/generated/cmvr/config/microphone_config/microphone_config_pb2_grpc.py
new file mode 100644
index 0000000..a6a24d6
--- /dev/null
+++ b/generated/cmvr/config/microphone_config/microphone_config_pb2_grpc.py
@@ -0,0 +1,24 @@
+# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
+"""Client and server classes corresponding to protobuf-defined services."""
+import grpc
+import warnings
+
+
+GRPC_GENERATED_VERSION = '1.76.0'
+GRPC_VERSION = grpc.__version__
+_version_not_supported = False
+
+try:
+ from grpc._utilities import first_version_is_lower
+ _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
+except ImportError:
+ _version_not_supported = True
+
+if _version_not_supported:
+ raise RuntimeError(
+ f'The grpc package installed is at version {GRPC_VERSION},'
+ + ' but the generated code in cmvr/config/microphone_config/microphone_config_pb2_grpc.py depends on'
+ + f' grpcio>={GRPC_GENERATED_VERSION}.'
+ + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
+ + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
+ )
diff --git a/generated/cmvr/config/pinocchio_dls_ik_config_pb2.py b/generated/cmvr/config/pinocchio_dls_ik_config_pb2.py
new file mode 100644
index 0000000..58a880d
--- /dev/null
+++ b/generated/cmvr/config/pinocchio_dls_ik_config_pb2.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# NO CHECKED-IN PROTOBUF GENCODE
+# source: cmvr/config/pinocchio_dls_ik_config.proto
+# Protobuf Python Version: 6.31.1
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import runtime_version as _runtime_version
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf.internal import builder as _builder
+_runtime_version.ValidateProtobufRuntimeVersion(
+ _runtime_version.Domain.PUBLIC,
+ 6,
+ 31,
+ 1,
+ '',
+ 'cmvr/config/pinocchio_dls_ik_config.proto'
+)
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n)cmvr/config/pinocchio_dls_ik_config.proto')
+
+_globals = globals()
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.config.pinocchio_dls_ik_config_pb2', _globals)
+if not _descriptor._USE_C_DESCRIPTORS:
+ DESCRIPTOR._loaded_options = None
+# @@protoc_insertion_point(module_scope)
diff --git a/generated/cmvr/config/pinocchio_dls_ik_config_pb2_grpc.py b/generated/cmvr/config/pinocchio_dls_ik_config_pb2_grpc.py
new file mode 100644
index 0000000..5c56c92
--- /dev/null
+++ b/generated/cmvr/config/pinocchio_dls_ik_config_pb2_grpc.py
@@ -0,0 +1,24 @@
+# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
+"""Client and server classes corresponding to protobuf-defined services."""
+import grpc
+import warnings
+
+
+GRPC_GENERATED_VERSION = '1.76.0'
+GRPC_VERSION = grpc.__version__
+_version_not_supported = False
+
+try:
+ from grpc._utilities import first_version_is_lower
+ _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
+except ImportError:
+ _version_not_supported = True
+
+if _version_not_supported:
+ raise RuntimeError(
+ f'The grpc package installed is at version {GRPC_VERSION},'
+ + ' but the generated code in cmvr/config/pinocchio_dls_ik_config_pb2_grpc.py depends on'
+ + f' grpcio>={GRPC_GENERATED_VERSION}.'
+ + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
+ + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
+ )
diff --git a/generated/cmvr/config/pinocchio_qp_ik_solver_config_pb2.py b/generated/cmvr/config/pinocchio_qp_ik_solver_config_pb2.py
new file mode 100644
index 0000000..130cca9
--- /dev/null
+++ b/generated/cmvr/config/pinocchio_qp_ik_solver_config_pb2.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# NO CHECKED-IN PROTOBUF GENCODE
+# source: cmvr/config/pinocchio_qp_ik_solver_config.proto
+# Protobuf Python Version: 6.31.1
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import runtime_version as _runtime_version
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf.internal import builder as _builder
+_runtime_version.ValidateProtobufRuntimeVersion(
+ _runtime_version.Domain.PUBLIC,
+ 6,
+ 31,
+ 1,
+ '',
+ 'cmvr/config/pinocchio_qp_ik_solver_config.proto'
+)
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n/cmvr/config/pinocchio_qp_ik_solver_config.proto\x12\x0b\x63mvr.config\"\xcd\x01\n\x13PinocchioQpIKConfig\x12\x11\n\turdf_path\x18\x01 \x01(\t\x12\x17\n\x0f\x62\x61se_frame_name\x18\x02 \x01(\t\x12\x19\n\x11\x66lange_frame_name\x18\x03 \x01(\t\x12\x16\n\x0etcp_frame_name\x18\x04 \x01(\t\x12\x0e\n\x06lambda\x18\x05 \x01(\x01\x12\x10\n\x08w_posrot\x18\x06 \x01(\x01\x12\x11\n\tmax_iters\x18\x07 \x01(\x05\x12\x0b\n\x03tol\x18\x08 \x01(\x01\x12\x15\n\rqp_time_limit\x18\t \x01(\x01\x62\x06proto3')
+
+_globals = globals()
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.config.pinocchio_qp_ik_solver_config_pb2', _globals)
+if not _descriptor._USE_C_DESCRIPTORS:
+ DESCRIPTOR._loaded_options = None
+ _globals['_PINOCCHIOQPIKCONFIG']._serialized_start=65
+ _globals['_PINOCCHIOQPIKCONFIG']._serialized_end=270
+# @@protoc_insertion_point(module_scope)
diff --git a/generated/cmvr/config/pinocchio_qp_ik_solver_config_pb2_grpc.py b/generated/cmvr/config/pinocchio_qp_ik_solver_config_pb2_grpc.py
new file mode 100644
index 0000000..68c6d43
--- /dev/null
+++ b/generated/cmvr/config/pinocchio_qp_ik_solver_config_pb2_grpc.py
@@ -0,0 +1,24 @@
+# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
+"""Client and server classes corresponding to protobuf-defined services."""
+import grpc
+import warnings
+
+
+GRPC_GENERATED_VERSION = '1.76.0'
+GRPC_VERSION = grpc.__version__
+_version_not_supported = False
+
+try:
+ from grpc._utilities import first_version_is_lower
+ _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
+except ImportError:
+ _version_not_supported = True
+
+if _version_not_supported:
+ raise RuntimeError(
+ f'The grpc package installed is at version {GRPC_VERSION},'
+ + ' but the generated code in cmvr/config/pinocchio_qp_ik_solver_config_pb2_grpc.py depends on'
+ + f' grpcio>={GRPC_GENERATED_VERSION}.'
+ + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
+ + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
+ )
diff --git a/generated/cmvr/config/speaker_config/__init__.py b/generated/cmvr/config/speaker_config/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/generated/cmvr/config/speaker_config/speaker_conifg_pb2.py b/generated/cmvr/config/speaker_config/speaker_conifg_pb2.py
new file mode 100644
index 0000000..6eae730
--- /dev/null
+++ b/generated/cmvr/config/speaker_config/speaker_conifg_pb2.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# NO CHECKED-IN PROTOBUF GENCODE
+# source: cmvr/config/speaker_config/speaker_conifg.proto
+# Protobuf Python Version: 6.31.1
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import runtime_version as _runtime_version
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf.internal import builder as _builder
+_runtime_version.ValidateProtobufRuntimeVersion(
+ _runtime_version.Domain.PUBLIC,
+ 6,
+ 31,
+ 1,
+ '',
+ 'cmvr/config/speaker_config/speaker_conifg.proto'
+)
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n/cmvr/config/speaker_config/speaker_conifg.proto\x12\x0b\x63mvr.config\"1\n\x13\x46\x46MpegSpeakerConfig\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0e\n\x06\x65nable\x18\x02 \x01(\x08\"J\n\rSpeakerConfig\x12\x39\n\x0f\x66\x66mpeg_speakers\x18\x01 \x03(\x0b\x32 .cmvr.config.FFMpegSpeakerConfigb\x06proto3')
+
+_globals = globals()
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.config.speaker_config.speaker_conifg_pb2', _globals)
+if not _descriptor._USE_C_DESCRIPTORS:
+ DESCRIPTOR._loaded_options = None
+ _globals['_FFMPEGSPEAKERCONFIG']._serialized_start=64
+ _globals['_FFMPEGSPEAKERCONFIG']._serialized_end=113
+ _globals['_SPEAKERCONFIG']._serialized_start=115
+ _globals['_SPEAKERCONFIG']._serialized_end=189
+# @@protoc_insertion_point(module_scope)
diff --git a/generated/cmvr/config/speaker_config/speaker_conifg_pb2_grpc.py b/generated/cmvr/config/speaker_config/speaker_conifg_pb2_grpc.py
new file mode 100644
index 0000000..fcf45ff
--- /dev/null
+++ b/generated/cmvr/config/speaker_config/speaker_conifg_pb2_grpc.py
@@ -0,0 +1,24 @@
+# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
+"""Client and server classes corresponding to protobuf-defined services."""
+import grpc
+import warnings
+
+
+GRPC_GENERATED_VERSION = '1.76.0'
+GRPC_VERSION = grpc.__version__
+_version_not_supported = False
+
+try:
+ from grpc._utilities import first_version_is_lower
+ _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
+except ImportError:
+ _version_not_supported = True
+
+if _version_not_supported:
+ raise RuntimeError(
+ f'The grpc package installed is at version {GRPC_VERSION},'
+ + ' but the generated code in cmvr/config/speaker_config/speaker_conifg_pb2_grpc.py depends on'
+ + f' grpcio>={GRPC_GENERATED_VERSION}.'
+ + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
+ + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
+ )
diff --git a/generated/cmvr/config/touch_screen_app_config/__init__.py b/generated/cmvr/config/touch_screen_app_config/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/generated/cmvr/config/touch_screen_app_config/touch_screen_app_config_pb2.py b/generated/cmvr/config/touch_screen_app_config/touch_screen_app_config_pb2.py
new file mode 100644
index 0000000..c543ace
--- /dev/null
+++ b/generated/cmvr/config/touch_screen_app_config/touch_screen_app_config_pb2.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# NO CHECKED-IN PROTOBUF GENCODE
+# source: cmvr/config/touch_screen_app_config/touch_screen_app_config.proto
+# Protobuf Python Version: 6.31.1
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import runtime_version as _runtime_version
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf.internal import builder as _builder
+_runtime_version.ValidateProtobufRuntimeVersion(
+ _runtime_version.Domain.PUBLIC,
+ 6,
+ 31,
+ 1,
+ '',
+ 'cmvr/config/touch_screen_app_config/touch_screen_app_config.proto'
+)
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\nAcmvr/config/touch_screen_app_config/touch_screen_app_config.proto\x12\x0b\x63mvr.config\"S\n\x0fTouchScreenVec3\x12\x0e\n\x01x\x18\x01 \x01(\x01H\x00\x88\x01\x01\x12\x0e\n\x01y\x18\x02 \x01(\x01H\x01\x88\x01\x01\x12\x0e\n\x01z\x18\x03 \x01(\x01H\x02\x88\x01\x01\x42\x04\n\x02_xB\x04\n\x02_yB\x04\n\x02_z\"\xa3\x01\n\x11TouchScreenTwist6\x12\x0f\n\x02vx\x18\x01 \x01(\x01H\x00\x88\x01\x01\x12\x0f\n\x02vy\x18\x02 \x01(\x01H\x01\x88\x01\x01\x12\x0f\n\x02vz\x18\x03 \x01(\x01H\x02\x88\x01\x01\x12\x0f\n\x02wx\x18\x04 \x01(\x01H\x03\x88\x01\x01\x12\x0f\n\x02wy\x18\x05 \x01(\x01H\x04\x88\x01\x01\x12\x0f\n\x02wz\x18\x06 \x01(\x01H\x05\x88\x01\x01\x42\x05\n\x03_vxB\x05\n\x03_vyB\x05\n\x03_vzB\x05\n\x03_wxB\x05\n\x03_wyB\x05\n\x03_wz\"\xff\x01\n\x13TouchScreenMatrix3d\x12\x10\n\x03m00\x18\x01 \x01(\x01H\x00\x88\x01\x01\x12\x10\n\x03m01\x18\x02 \x01(\x01H\x01\x88\x01\x01\x12\x10\n\x03m02\x18\x03 \x01(\x01H\x02\x88\x01\x01\x12\x10\n\x03m10\x18\x04 \x01(\x01H\x03\x88\x01\x01\x12\x10\n\x03m11\x18\x05 \x01(\x01H\x04\x88\x01\x01\x12\x10\n\x03m12\x18\x06 \x01(\x01H\x05\x88\x01\x01\x12\x10\n\x03m20\x18\x07 \x01(\x01H\x06\x88\x01\x01\x12\x10\n\x03m21\x18\x08 \x01(\x01H\x07\x88\x01\x01\x12\x10\n\x03m22\x18\t \x01(\x01H\x08\x88\x01\x01\x42\x06\n\x04_m00B\x06\n\x04_m01B\x06\n\x04_m02B\x06\n\x04_m10B\x06\n\x04_m11B\x06\n\x04_m12B\x06\n\x04_m20B\x06\n\x04_m21B\x06\n\x04_m22\"]\n\x19TouchScreenInitJointPoint\x12\x17\n\njoint_name\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x10\n\x03rad\x18\x02 \x01(\x01H\x01\x88\x01\x01\x42\r\n\x0b_joint_nameB\x06\n\x04_rad\"\xba\x15\n\x14TouchScreenAppConfig\x12\x15\n\x08robot_id\x18( \x01(\tH\x00\x88\x01\x01\x12\x17\n\ndexhand_id\x18) \x01(\tH\x01\x88\x01\x01\x12\x16\n\tcamera_id\x18* \x01(\tH\x02\x88\x01\x01\x12\"\n\x15move_to_init_position\x18+ \x01(\x08H\x03\x88\x01\x01\x12\x44\n\x14init_joint_positions\x18, \x03(\x0b\x32&.cmvr.config.TouchScreenInitJointPoint\x12\x1b\n\x0einit_movej_vel\x18- \x01(\x01H\x04\x88\x01\x01\x12\x1b\n\x0einit_movej_acc\x18. \x01(\x01H\x05\x88\x01\x01\x12/\n\"move_to_init_position_before_start\x18/ \x01(\x08H\x06\x88\x01\x01\x12\x16\n\turdf_path\x18\x01 \x01(\tH\x07\x88\x01\x01\x12\x16\n\tbase_link\x18\x02 \x01(\tH\x08\x88\x01\x01\x12\x18\n\x0b\x66lange_link\x18\x03 \x01(\tH\t\x88\x01\x01\x12\x18\n\x0b\x63\x61mera_link\x18\x04 \x01(\tH\n\x88\x01\x01\x12\x17\n\ntag_size_m\x18\x05 \x01(\x01H\x0b\x88\x01\x01\x12>\n\x0c\x64\x65pth_policy\x18\x06 \x01(\x0e\x32#.cmvr.config.TouchScreenDepthPolicyH\x0c\x88\x01\x01\x12K\n\x13target_point_method\x18\x07 \x01(\x0e\x32).cmvr.config.TouchScreenTargetPointMethodH\r\x88\x01\x01\x12<\n\x16hover_target_in_camera\x18\x08 \x01(\x0b\x32\x1c.cmvr.config.TouchScreenVec3\x12\x16\n\ttarget_rx\x18\t \x01(\x01H\x0e\x88\x01\x01\x12\x16\n\ttarget_ry\x18\n \x01(\x01H\x0f\x88\x01\x01\x12\x16\n\ttarget_rz\x18\x0b \x01(\x01H\x10\x88\x01\x01\x12\x18\n\x0bibvs_lambda\x18\x0c \x01(\x01H\x11\x88\x01\x01\x12\x14\n\x07ibvs_mu\x18\r \x01(\x01H\x12\x88\x01\x01\x12\x1a\n\ribvs_qdot_max\x18\x0e \x01(\x01H\x13\x88\x01\x01\x12\x32\n\nibvs_vmax6\x18\x0f \x01(\x0b\x32\x1e.cmvr.config.TouchScreenTwist6\x12)\n\x1c\x65nable_joint_limit_avoidance\x18\x10 \x01(\x08H\x14\x88\x01\x01\x12\'\n\x1ajoint_limit_avoidance_gain\x18\x11 \x01(\x01H\x15\x88\x01\x01\x12/\n\"joint_limit_avoidance_margin_ratio\x18\x12 \x01(\x01H\x16\x88\x01\x01\x12+\n\x1ejoint_limit_avoidance_max_push\x18\x13 \x01(\x01H\x17\x88\x01\x01\x12:\n\x10r_camera_to_visp\x18\x14 \x01(\x0b\x32 .cmvr.config.TouchScreenMatrix3d\x12:\n\x10r_camera_to_urdf\x18\x15 \x01(\x0b\x32 .cmvr.config.TouchScreenMatrix3d\x12\x1b\n\x13\x63ontrol_joint_names\x18\x16 \x03(\t\x12!\n\x14\x61lign_xy_threshold_m\x18\x17 \x01(\x01H\x18\x88\x01\x01\x12 \n\x13\x61lign_z_threshold_m\x18\x18 \x01(\x01H\x19\x88\x01\x01\x12$\n\x17\x61lign_rot_threshold_rad\x18\x19 \x01(\x01H\x1a\x88\x01\x01\x12 \n\x13\x61lign_stable_frames\x18\x1a \x01(\x05H\x1b\x88\x01\x01\x12\x1c\n\x0f\x61lign_timeout_s\x18\x1b \x01(\x01H\x1c\x88\x01\x01\x12&\n\x19pause_after_align_reached\x18\x1c \x01(\x08H\x1d\x88\x01\x01\x12\x38\n\x10touch_twist_base\x18\x1d \x01(\x0b\x32\x1e.cmvr.config.TouchScreenTwist6\x12\x1f\n\x12touch_acceleration\x18\x1e \x01(\x01H\x1e\x88\x01\x01\x12%\n\x18touch_forward_duration_s\x18\x1f \x01(\x01H\x1f\x88\x01\x01\x12\x19\n\x0c\x64well_time_s\x18 \x01(\x01H \x88\x01\x01\x12:\n\x12retract_twist_base\x18! \x01(\x0b\x32\x1e.cmvr.config.TouchScreenTwist6\x12!\n\x14retract_acceleration\x18\" \x01(\x01H!\x88\x01\x01\x12\x1f\n\x12retract_duration_s\x18# \x01(\x01H\"\x88\x01\x01\x12?\n\x0etactile_finger\x18$ \x01(\x0e\x32\".cmvr.config.TouchScreenFingerTypeH#\x88\x01\x01\x12\x42\n\x0etactile_region\x18% \x01(\x0e\x32%.cmvr.config.TouchScreenTactileRegionH$\x88\x01\x01\x12+\n\x1etactile_pressure_sum_threshold\x18& \x01(\x01H%\x88\x01\x01\x12,\n\x1ftactile_pressure_peak_threshold\x18\' \x01(\x01H&\x88\x01\x01\x42\x0b\n\t_robot_idB\r\n\x0b_dexhand_idB\x0c\n\n_camera_idB\x18\n\x16_move_to_init_positionB\x11\n\x0f_init_movej_velB\x11\n\x0f_init_movej_accB%\n#_move_to_init_position_before_startB\x0c\n\n_urdf_pathB\x0c\n\n_base_linkB\x0e\n\x0c_flange_linkB\x0e\n\x0c_camera_linkB\r\n\x0b_tag_size_mB\x0f\n\r_depth_policyB\x16\n\x14_target_point_methodB\x0c\n\n_target_rxB\x0c\n\n_target_ryB\x0c\n\n_target_rzB\x0e\n\x0c_ibvs_lambdaB\n\n\x08_ibvs_muB\x10\n\x0e_ibvs_qdot_maxB\x1f\n\x1d_enable_joint_limit_avoidanceB\x1d\n\x1b_joint_limit_avoidance_gainB%\n#_joint_limit_avoidance_margin_ratioB!\n\x1f_joint_limit_avoidance_max_pushB\x17\n\x15_align_xy_threshold_mB\x16\n\x14_align_z_threshold_mB\x1a\n\x18_align_rot_threshold_radB\x16\n\x14_align_stable_framesB\x12\n\x10_align_timeout_sB\x1c\n\x1a_pause_after_align_reachedB\x15\n\x13_touch_accelerationB\x1b\n\x19_touch_forward_duration_sB\x0f\n\r_dwell_time_sB\x17\n\x15_retract_accelerationB\x15\n\x13_retract_duration_sB\x11\n\x0f_tactile_fingerB\x11\n\x0f_tactile_regionB!\n\x1f_tactile_pressure_sum_thresholdB\"\n _tactile_pressure_peak_threshold*\x89\x01\n\x16TouchScreenDepthPolicy\x12\"\n\x1eTOUCH_SCREEN_DEPTH_POLICY_NONE\x10\x00\x12$\n TOUCH_SCREEN_DEPTH_POLICY_PREFER\x10\x01\x12%\n!TOUCH_SCREEN_DEPTH_POLICY_REQUIRE\x10\x02*\x80\x01\n\x1cTouchScreenTargetPointMethod\x12.\n*TOUCH_SCREEN_TARGET_POINT_METHOD_TAG_PLANE\x10\x00\x12\x30\n,TOUCH_SCREEN_TARGET_POINT_METHOD_DEPTH_IMAGE\x10\x01*\xcb\x01\n\x15TouchScreenFingerType\x12\"\n\x1eTOUCH_SCREEN_FINGER_TYPE_PINKY\x10\x00\x12!\n\x1dTOUCH_SCREEN_FINGER_TYPE_RING\x10\x01\x12#\n\x1fTOUCH_SCREEN_FINGER_TYPE_MIDDLE\x10\x02\x12\"\n\x1eTOUCH_SCREEN_FINGER_TYPE_INDEX\x10\x03\x12\"\n\x1eTOUCH_SCREEN_FINGER_TYPE_THUMB\x10\x04*\xea\x01\n\x18TouchScreenTactileRegion\x12#\n\x1fTOUCH_SCREEN_TACTILE_REGION_TIP\x10\x00\x12&\n\"TOUCH_SCREEN_TACTILE_REGION_FINGER\x10\x01\x12#\n\x1fTOUCH_SCREEN_TACTILE_REGION_PAD\x10\x02\x12.\n*TOUCH_SCREEN_TACTILE_REGION_TIP_AND_FINGER\x10\x03\x12,\n(TOUCH_SCREEN_TACTILE_REGION_THUMB_MIDDLE\x10\x04\x62\x06proto3')
+
+_globals = globals()
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmvr.config.touch_screen_app_config.touch_screen_app_config_pb2', _globals)
+if not _descriptor._USE_C_DESCRIPTORS:
+ DESCRIPTOR._loaded_options = None
+ _globals['_TOUCHSCREENDEPTHPOLICY']._serialized_start=3436
+ _globals['_TOUCHSCREENDEPTHPOLICY']._serialized_end=3573
+ _globals['_TOUCHSCREENTARGETPOINTMETHOD']._serialized_start=3576
+ _globals['_TOUCHSCREENTARGETPOINTMETHOD']._serialized_end=3704
+ _globals['_TOUCHSCREENFINGERTYPE']._serialized_start=3707
+ _globals['_TOUCHSCREENFINGERTYPE']._serialized_end=3910
+ _globals['_TOUCHSCREENTACTILEREGION']._serialized_start=3913
+ _globals['_TOUCHSCREENTACTILEREGION']._serialized_end=4147
+ _globals['_TOUCHSCREENVEC3']._serialized_start=82
+ _globals['_TOUCHSCREENVEC3']._serialized_end=165
+ _globals['_TOUCHSCREENTWIST6']._serialized_start=168
+ _globals['_TOUCHSCREENTWIST6']._serialized_end=331
+ _globals['_TOUCHSCREENMATRIX3D']._serialized_start=334
+ _globals['_TOUCHSCREENMATRIX3D']._serialized_end=589
+ _globals['_TOUCHSCREENINITJOINTPOINT']._serialized_start=591
+ _globals['_TOUCHSCREENINITJOINTPOINT']._serialized_end=684
+ _globals['_TOUCHSCREENAPPCONFIG']._serialized_start=687
+ _globals['_TOUCHSCREENAPPCONFIG']._serialized_end=3433
+# @@protoc_insertion_point(module_scope)
diff --git a/generated/cmvr/config/touch_screen_app_config/touch_screen_app_config_pb2_grpc.py b/generated/cmvr/config/touch_screen_app_config/touch_screen_app_config_pb2_grpc.py
new file mode 100644
index 0000000..cf0475d
--- /dev/null
+++ b/generated/cmvr/config/touch_screen_app_config/touch_screen_app_config_pb2_grpc.py
@@ -0,0 +1,24 @@
+# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
+"""Client and server classes corresponding to protobuf-defined services."""
+import grpc
+import warnings
+
+
+GRPC_GENERATED_VERSION = '1.76.0'
+GRPC_VERSION = grpc.__version__
+_version_not_supported = False
+
+try:
+ from grpc._utilities import first_version_is_lower
+ _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
+except ImportError:
+ _version_not_supported = True
+
+if _version_not_supported:
+ raise RuntimeError(
+ f'The grpc package installed is at version {GRPC_VERSION},'
+ + ' but the generated code in cmvr/config/touch_screen_app_config/touch_screen_app_config_pb2_grpc.py depends on'
+ + f' grpcio>={GRPC_GENERATED_VERSION}.'
+ + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
+ + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
+ )
diff --git a/generated_apriltags/1.0x1.0cm/1.ai b/generated_apriltags/1.0x1.0cm/1.ai
new file mode 100644
index 0000000..9dcf321
Binary files /dev/null and b/generated_apriltags/1.0x1.0cm/1.ai differ
diff --git a/generated_apriltags/1.0x1.0cm/10.ai b/generated_apriltags/1.0x1.0cm/10.ai
new file mode 100644
index 0000000..2acad88
Binary files /dev/null and b/generated_apriltags/1.0x1.0cm/10.ai differ
diff --git a/generated_apriltags/1.0x1.0cm/11.ai b/generated_apriltags/1.0x1.0cm/11.ai
new file mode 100644
index 0000000..409021b
Binary files /dev/null and b/generated_apriltags/1.0x1.0cm/11.ai differ
diff --git a/generated_apriltags/1.0x1.0cm/12.ai b/generated_apriltags/1.0x1.0cm/12.ai
new file mode 100644
index 0000000..2b83698
Binary files /dev/null and b/generated_apriltags/1.0x1.0cm/12.ai differ
diff --git a/generated_apriltags/1.0x1.0cm/13.ai b/generated_apriltags/1.0x1.0cm/13.ai
new file mode 100644
index 0000000..665d46d
Binary files /dev/null and b/generated_apriltags/1.0x1.0cm/13.ai differ
diff --git a/generated_apriltags/1.0x1.0cm/14.ai b/generated_apriltags/1.0x1.0cm/14.ai
new file mode 100644
index 0000000..5e9cedf
Binary files /dev/null and b/generated_apriltags/1.0x1.0cm/14.ai differ
diff --git a/generated_apriltags/1.0x1.0cm/15.ai b/generated_apriltags/1.0x1.0cm/15.ai
new file mode 100644
index 0000000..9680be2
Binary files /dev/null and b/generated_apriltags/1.0x1.0cm/15.ai differ
diff --git a/generated_apriltags/1.0x1.0cm/16.ai b/generated_apriltags/1.0x1.0cm/16.ai
new file mode 100644
index 0000000..2572c56
Binary files /dev/null and b/generated_apriltags/1.0x1.0cm/16.ai differ
diff --git a/generated_apriltags/1.0x1.0cm/17.ai b/generated_apriltags/1.0x1.0cm/17.ai
new file mode 100644
index 0000000..c3a4aad
Binary files /dev/null and b/generated_apriltags/1.0x1.0cm/17.ai differ
diff --git a/generated_apriltags/1.0x1.0cm/18.ai b/generated_apriltags/1.0x1.0cm/18.ai
new file mode 100644
index 0000000..1c12931
Binary files /dev/null and b/generated_apriltags/1.0x1.0cm/18.ai differ
diff --git a/generated_apriltags/1.0x1.0cm/19.ai b/generated_apriltags/1.0x1.0cm/19.ai
new file mode 100644
index 0000000..8be511f
Binary files /dev/null and b/generated_apriltags/1.0x1.0cm/19.ai differ
diff --git a/generated_apriltags/1.0x1.0cm/2.ai b/generated_apriltags/1.0x1.0cm/2.ai
new file mode 100644
index 0000000..3bfa606
Binary files /dev/null and b/generated_apriltags/1.0x1.0cm/2.ai differ
diff --git a/generated_apriltags/1.0x1.0cm/20.ai b/generated_apriltags/1.0x1.0cm/20.ai
new file mode 100644
index 0000000..d9d80e2
Binary files /dev/null and b/generated_apriltags/1.0x1.0cm/20.ai differ
diff --git a/generated_apriltags/1.0x1.0cm/3.ai b/generated_apriltags/1.0x1.0cm/3.ai
new file mode 100644
index 0000000..6e53c0c
Binary files /dev/null and b/generated_apriltags/1.0x1.0cm/3.ai differ
diff --git a/generated_apriltags/1.0x1.0cm/4.ai b/generated_apriltags/1.0x1.0cm/4.ai
new file mode 100644
index 0000000..2b87322
Binary files /dev/null and b/generated_apriltags/1.0x1.0cm/4.ai differ
diff --git a/generated_apriltags/1.0x1.0cm/5.ai b/generated_apriltags/1.0x1.0cm/5.ai
new file mode 100644
index 0000000..2fade7e
Binary files /dev/null and b/generated_apriltags/1.0x1.0cm/5.ai differ
diff --git a/generated_apriltags/1.0x1.0cm/6.ai b/generated_apriltags/1.0x1.0cm/6.ai
new file mode 100644
index 0000000..81111d4
Binary files /dev/null and b/generated_apriltags/1.0x1.0cm/6.ai differ
diff --git a/generated_apriltags/1.0x1.0cm/7.ai b/generated_apriltags/1.0x1.0cm/7.ai
new file mode 100644
index 0000000..a08f7a4
Binary files /dev/null and b/generated_apriltags/1.0x1.0cm/7.ai differ
diff --git a/generated_apriltags/1.0x1.0cm/8.ai b/generated_apriltags/1.0x1.0cm/8.ai
new file mode 100644
index 0000000..7bb3bdc
Binary files /dev/null and b/generated_apriltags/1.0x1.0cm/8.ai differ
diff --git a/generated_apriltags/1.0x1.0cm/9.ai b/generated_apriltags/1.0x1.0cm/9.ai
new file mode 100644
index 0000000..81b27a2
Binary files /dev/null and b/generated_apriltags/1.0x1.0cm/9.ai differ
diff --git a/generated_apriltags/1.5x1.5cm/1.ai b/generated_apriltags/1.5x1.5cm/1.ai
new file mode 100644
index 0000000..c135ce1
Binary files /dev/null and b/generated_apriltags/1.5x1.5cm/1.ai differ
diff --git a/generated_apriltags/1.5x1.5cm/10.ai b/generated_apriltags/1.5x1.5cm/10.ai
new file mode 100644
index 0000000..f900677
Binary files /dev/null and b/generated_apriltags/1.5x1.5cm/10.ai differ
diff --git a/generated_apriltags/1.5x1.5cm/11.ai b/generated_apriltags/1.5x1.5cm/11.ai
new file mode 100644
index 0000000..13afcca
Binary files /dev/null and b/generated_apriltags/1.5x1.5cm/11.ai differ
diff --git a/generated_apriltags/1.5x1.5cm/12.ai b/generated_apriltags/1.5x1.5cm/12.ai
new file mode 100644
index 0000000..a0cf410
Binary files /dev/null and b/generated_apriltags/1.5x1.5cm/12.ai differ
diff --git a/generated_apriltags/1.5x1.5cm/13.ai b/generated_apriltags/1.5x1.5cm/13.ai
new file mode 100644
index 0000000..8f2bd9f
Binary files /dev/null and b/generated_apriltags/1.5x1.5cm/13.ai differ
diff --git a/generated_apriltags/1.5x1.5cm/14.ai b/generated_apriltags/1.5x1.5cm/14.ai
new file mode 100644
index 0000000..b55a656
Binary files /dev/null and b/generated_apriltags/1.5x1.5cm/14.ai differ
diff --git a/generated_apriltags/1.5x1.5cm/15.ai b/generated_apriltags/1.5x1.5cm/15.ai
new file mode 100644
index 0000000..89b2de8
Binary files /dev/null and b/generated_apriltags/1.5x1.5cm/15.ai differ
diff --git a/generated_apriltags/1.5x1.5cm/16.ai b/generated_apriltags/1.5x1.5cm/16.ai
new file mode 100644
index 0000000..c21ca96
Binary files /dev/null and b/generated_apriltags/1.5x1.5cm/16.ai differ
diff --git a/generated_apriltags/1.5x1.5cm/17.ai b/generated_apriltags/1.5x1.5cm/17.ai
new file mode 100644
index 0000000..ea966be
Binary files /dev/null and b/generated_apriltags/1.5x1.5cm/17.ai differ
diff --git a/generated_apriltags/1.5x1.5cm/18.ai b/generated_apriltags/1.5x1.5cm/18.ai
new file mode 100644
index 0000000..ce85262
Binary files /dev/null and b/generated_apriltags/1.5x1.5cm/18.ai differ
diff --git a/generated_apriltags/1.5x1.5cm/19.ai b/generated_apriltags/1.5x1.5cm/19.ai
new file mode 100644
index 0000000..27003ef
Binary files /dev/null and b/generated_apriltags/1.5x1.5cm/19.ai differ
diff --git a/generated_apriltags/1.5x1.5cm/2.ai b/generated_apriltags/1.5x1.5cm/2.ai
new file mode 100644
index 0000000..e891a3b
Binary files /dev/null and b/generated_apriltags/1.5x1.5cm/2.ai differ
diff --git a/generated_apriltags/1.5x1.5cm/20.ai b/generated_apriltags/1.5x1.5cm/20.ai
new file mode 100644
index 0000000..fdb4498
Binary files /dev/null and b/generated_apriltags/1.5x1.5cm/20.ai differ
diff --git a/generated_apriltags/1.5x1.5cm/3.ai b/generated_apriltags/1.5x1.5cm/3.ai
new file mode 100644
index 0000000..64f863b
Binary files /dev/null and b/generated_apriltags/1.5x1.5cm/3.ai differ
diff --git a/generated_apriltags/1.5x1.5cm/4.ai b/generated_apriltags/1.5x1.5cm/4.ai
new file mode 100644
index 0000000..7e27222
--- /dev/null
+++ b/generated_apriltags/1.5x1.5cm/4.ai
@@ -0,0 +1,72 @@
+%PDF-1.4
+%
+1 0 obj
+<< /Pages 2 0 R /Type /Catalog >>
+endobj
+8 0 obj
+<< /ExtGState 4 0 R /Font 3 0 R /Pattern 5 0 R
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Shading 6 0 R
+/XObject 7 0 R >>
+endobj
+11 0 obj
+<< /Annots 10 0 R /Contents 9 0 R
+/MediaBox [ 0 0 42.5196850394 42.5196850394 ] /Parent 2 0 R
+/Resources 8 0 R /Type /Page >>
+endobj
+9 0 obj
+<< /Filter /FlateDecode /Length 12 0 R >>
+stream
+xN0ENY|@UFJ8 |oZgĨjdeff:\?w
nxs7'tvӘO7l.y<(|](dIAn_܉B},*84X,X1aܸ(M6}\O?s1ŰدBRAد=LPJV
O/KPn Pa!\/QS
+uvQ
ePr;l(ccPYKh͙RI{x-CF2lmA˱1Le9"+l(c eqkCM$Ȃ ( akFym((TXCh~s*
+endstream
+endobj
+12 0 obj
+404
+endobj
+10 0 obj
+[ ]
+endobj
+3 0 obj
+<< >>
+endobj
+4 0 obj
+<< /A1 << /CA 0 /Type /ExtGState /ca 1 >> >>
+endobj
+5 0 obj
+<< >>
+endobj
+6 0 obj
+<< >>
+endobj
+7 0 obj
+<< >>
+endobj
+2 0 obj
+<< /Count 1 /Kids [ 11 0 R ] /Type /Pages >>
+endobj
+13 0 obj
+<< /CreationDate (D:20260318165303+08'00')
+/Creator (Matplotlib v3.5.3, https://matplotlib.org)
+/Producer (Matplotlib pdf backend v3.5.3) >>
+endobj
+xref
+0 14
+0000000000 65535 f
+0000000016 00000 n
+0000001013 00000 n
+0000000869 00000 n
+0000000890 00000 n
+0000000950 00000 n
+0000000971 00000 n
+0000000992 00000 n
+0000000065 00000 n
+0000000350 00000 n
+0000000849 00000 n
+0000000208 00000 n
+0000000829 00000 n
+0000001073 00000 n
+trailer
+<< /Info 13 0 R /Root 1 0 R /Size 14 >>
+startxref
+1230
+%%EOF
diff --git a/generated_apriltags/1.5x1.5cm/5.ai b/generated_apriltags/1.5x1.5cm/5.ai
new file mode 100644
index 0000000..b5ed8ec
Binary files /dev/null and b/generated_apriltags/1.5x1.5cm/5.ai differ
diff --git a/generated_apriltags/1.5x1.5cm/6.ai b/generated_apriltags/1.5x1.5cm/6.ai
new file mode 100644
index 0000000..363dff9
Binary files /dev/null and b/generated_apriltags/1.5x1.5cm/6.ai differ
diff --git a/generated_apriltags/1.5x1.5cm/7.ai b/generated_apriltags/1.5x1.5cm/7.ai
new file mode 100644
index 0000000..2e3e191
Binary files /dev/null and b/generated_apriltags/1.5x1.5cm/7.ai differ
diff --git a/generated_apriltags/1.5x1.5cm/8.ai b/generated_apriltags/1.5x1.5cm/8.ai
new file mode 100644
index 0000000..8ece1fc
Binary files /dev/null and b/generated_apriltags/1.5x1.5cm/8.ai differ
diff --git a/generated_apriltags/1.5x1.5cm/9.ai b/generated_apriltags/1.5x1.5cm/9.ai
new file mode 100644
index 0000000..e0d451b
--- /dev/null
+++ b/generated_apriltags/1.5x1.5cm/9.ai
@@ -0,0 +1,73 @@
+%PDF-1.4
+%
+1 0 obj
+<< /Pages 2 0 R /Type /Catalog >>
+endobj
+8 0 obj
+<< /ExtGState 4 0 R /Font 3 0 R /Pattern 5 0 R
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Shading 6 0 R
+/XObject 7 0 R >>
+endobj
+11 0 obj
+<< /Annots 10 0 R /Contents 9 0 R
+/MediaBox [ 0 0 42.5196850394 42.5196850394 ] /Parent 2 0 R
+/Resources 8 0 R /Type /Page >>
+endobj
+9 0 obj
+<< /Filter /FlateDecode /Length 12 0 R >>
+stream
+xj0E
+}"ٲe/.!}߯܂U2^23>h^wy0vG|X_ع!4~'Ͻ}vsіGyPro|
bDoD}Y1OW(1uKlucJ!ĈޘFÐu K0,h74$vIiJR&L!4$J0`C${
&xU[I\(_ɰ*I@RAТYDCɚ7=&d(QA%Ca'Vv9Ȃͪ1 r+˭]eHG-#6(R<uk(P%G+CW!P4Gym@Ɇ*(9
+填4Ĕ^P2WY)ɠylXS8
+endstream
+endobj
+12 0 obj
+438
+endobj
+10 0 obj
+[ ]
+endobj
+3 0 obj
+<< >>
+endobj
+4 0 obj
+<< /A1 << /CA 0 /Type /ExtGState /ca 1 >> >>
+endobj
+5 0 obj
+<< >>
+endobj
+6 0 obj
+<< >>
+endobj
+7 0 obj
+<< >>
+endobj
+2 0 obj
+<< /Count 1 /Kids [ 11 0 R ] /Type /Pages >>
+endobj
+13 0 obj
+<< /CreationDate (D:20260318165303+08'00')
+/Creator (Matplotlib v3.5.3, https://matplotlib.org)
+/Producer (Matplotlib pdf backend v3.5.3) >>
+endobj
+xref
+0 14
+0000000000 65535 f
+0000000016 00000 n
+0000001047 00000 n
+0000000903 00000 n
+0000000924 00000 n
+0000000984 00000 n
+0000001005 00000 n
+0000001026 00000 n
+0000000065 00000 n
+0000000350 00000 n
+0000000883 00000 n
+0000000208 00000 n
+0000000863 00000 n
+0000001107 00000 n
+trailer
+<< /Info 13 0 R /Root 1 0 R /Size 14 >>
+startxref
+1264
+%%EOF
diff --git a/generated_apriltags/2.0x2.0cm/1.ai b/generated_apriltags/2.0x2.0cm/1.ai
new file mode 100644
index 0000000..a17cdd1
Binary files /dev/null and b/generated_apriltags/2.0x2.0cm/1.ai differ
diff --git a/generated_apriltags/2.0x2.0cm/10.ai b/generated_apriltags/2.0x2.0cm/10.ai
new file mode 100644
index 0000000..f507122
--- /dev/null
+++ b/generated_apriltags/2.0x2.0cm/10.ai
@@ -0,0 +1,73 @@
+%PDF-1.4
+%
+1 0 obj
+<< /Pages 2 0 R /Type /Catalog >>
+endobj
+8 0 obj
+<< /ExtGState 4 0 R /Font 3 0 R /Pattern 5 0 R
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Shading 6 0 R
+/XObject 7 0 R >>
+endobj
+11 0 obj
+<< /Annots 10 0 R /Contents 9 0 R
+/MediaBox [ 0 0 56.6929133858 56.6929133858 ] /Parent 2 0 R
+/Resources 8 0 R /Type /Page >>
+endobj
+9 0 obj
+<< /Filter /FlateDecode /Length 12 0 R >>
+stream
+xMj0F:NH%;@m=@HBThE"،1m}ow[}V{mh[;0^vYՍӯGO|csZ+\Z+{>)B^+L<_3N*S-Ƈ{wMe`ơm+ԯMk.i؍L2F:r,!UЏ).i(}(5Aa%I:3
:4r,v϶*7 &\ҰtJzRF:r,aPHVRe4b%
qKE
+th(O$˙93mJHۛy thXBt8E6oF*
+thX)U2
:4r,*2ֽ\/Nx
+endstream
+endobj
+12 0 obj
+407
+endobj
+10 0 obj
+[ ]
+endobj
+3 0 obj
+<< >>
+endobj
+4 0 obj
+<< /A1 << /CA 0 /Type /ExtGState /ca 1 >> >>
+endobj
+5 0 obj
+<< >>
+endobj
+6 0 obj
+<< >>
+endobj
+7 0 obj
+<< >>
+endobj
+2 0 obj
+<< /Count 1 /Kids [ 11 0 R ] /Type /Pages >>
+endobj
+13 0 obj
+<< /CreationDate (D:20260318165303+08'00')
+/Creator (Matplotlib v3.5.3, https://matplotlib.org)
+/Producer (Matplotlib pdf backend v3.5.3) >>
+endobj
+xref
+0 14
+0000000000 65535 f
+0000000016 00000 n
+0000001016 00000 n
+0000000872 00000 n
+0000000893 00000 n
+0000000953 00000 n
+0000000974 00000 n
+0000000995 00000 n
+0000000065 00000 n
+0000000350 00000 n
+0000000852 00000 n
+0000000208 00000 n
+0000000832 00000 n
+0000001076 00000 n
+trailer
+<< /Info 13 0 R /Root 1 0 R /Size 14 >>
+startxref
+1233
+%%EOF
diff --git a/generated_apriltags/2.0x2.0cm/11.ai b/generated_apriltags/2.0x2.0cm/11.ai
new file mode 100644
index 0000000..9293c73
--- /dev/null
+++ b/generated_apriltags/2.0x2.0cm/11.ai
@@ -0,0 +1,75 @@
+%PDF-1.4
+%
+1 0 obj
+<< /Pages 2 0 R /Type /Catalog >>
+endobj
+8 0 obj
+<< /ExtGState 4 0 R /Font 3 0 R /Pattern 5 0 R
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Shading 6 0 R
+/XObject 7 0 R >>
+endobj
+11 0 obj
+<< /Annots 10 0 R /Contents 9 0 R
+/MediaBox [ 0 0 56.6929133858 56.6929133858 ] /Parent 2 0 R
+/Resources 8 0 R /Type /Page >>
+endobj
+9 0 obj
+<< /Filter /FlateDecode /Length 12 0 R >>
+stream
+xKj0@:NH%~ 붋 @{:)<yQlFx峺~o[{dVL;iJB}/V_[l?&׃ow3^y[A1/W_(kc˧l
+717Qe%ڃPP6(&wJg
+}q!5
C)6]r)
+drlzܦn[C~(6AQqa&J+Tt*(𪝮LXY)(lAQ:IzOAaJFTl+T :Mi(@Q(m$cOeBi#+涠Si@o*ֻ
+dPr:6ȠX0wA%0*4*Uz dr,tXEڙ>X(lAɱR PD??kCh
+endstream
+endobj
+12 0 obj
+434
+endobj
+10 0 obj
+[ ]
+endobj
+3 0 obj
+<< >>
+endobj
+4 0 obj
+<< /A1 << /CA 0 /Type /ExtGState /ca 1 >> >>
+endobj
+5 0 obj
+<< >>
+endobj
+6 0 obj
+<< >>
+endobj
+7 0 obj
+<< >>
+endobj
+2 0 obj
+<< /Count 1 /Kids [ 11 0 R ] /Type /Pages >>
+endobj
+13 0 obj
+<< /CreationDate (D:20260318165303+08'00')
+/Creator (Matplotlib v3.5.3, https://matplotlib.org)
+/Producer (Matplotlib pdf backend v3.5.3) >>
+endobj
+xref
+0 14
+0000000000 65535 f
+0000000016 00000 n
+0000001043 00000 n
+0000000899 00000 n
+0000000920 00000 n
+0000000980 00000 n
+0000001001 00000 n
+0000001022 00000 n
+0000000065 00000 n
+0000000350 00000 n
+0000000879 00000 n
+0000000208 00000 n
+0000000859 00000 n
+0000001103 00000 n
+trailer
+<< /Info 13 0 R /Root 1 0 R /Size 14 >>
+startxref
+1260
+%%EOF
diff --git a/generated_apriltags/2.0x2.0cm/12.ai b/generated_apriltags/2.0x2.0cm/12.ai
new file mode 100644
index 0000000..479d760
--- /dev/null
+++ b/generated_apriltags/2.0x2.0cm/12.ai
@@ -0,0 +1,72 @@
+%PDF-1.4
+%
+1 0 obj
+<< /Pages 2 0 R /Type /Catalog >>
+endobj
+8 0 obj
+<< /ExtGState 4 0 R /Font 3 0 R /Pattern 5 0 R
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Shading 6 0 R
+/XObject 7 0 R >>
+endobj
+11 0 obj
+<< /Annots 10 0 R /Contents 9 0 R
+/MediaBox [ 0 0 56.6929133858 56.6929133858 ] /Parent 2 0 R
+/Resources 8 0 R /Type /Page >>
+endobj
+9 0 obj
+<< /Filter /FlateDecode /Length 12 0 R >>
+stream
+xj0E
+}"Y/{dv#D6~^iQlF{t;Yo7{R+6N}j[?14
~k^8zTZ(ԨDm]M`/ZhOpTkT{!H
b\61Ŷ~2iOl`R.Ņ0Ry6 刾؉tlZʱTdJ4b"i2Ib'SPBˎL
urlUv/@)iBIb{2$vlRJ'S8i4īQ/2% NDɆ:96*р<Ӭmp#~ٱ)(۠c#(~5D秛&(fd
urltd&"??B
+endstream
+endobj
+12 0 obj
+416
+endobj
+10 0 obj
+[ ]
+endobj
+3 0 obj
+<< >>
+endobj
+4 0 obj
+<< /A1 << /CA 0 /Type /ExtGState /ca 1 >> >>
+endobj
+5 0 obj
+<< >>
+endobj
+6 0 obj
+<< >>
+endobj
+7 0 obj
+<< >>
+endobj
+2 0 obj
+<< /Count 1 /Kids [ 11 0 R ] /Type /Pages >>
+endobj
+13 0 obj
+<< /CreationDate (D:20260318165303+08'00')
+/Creator (Matplotlib v3.5.3, https://matplotlib.org)
+/Producer (Matplotlib pdf backend v3.5.3) >>
+endobj
+xref
+0 14
+0000000000 65535 f
+0000000016 00000 n
+0000001025 00000 n
+0000000881 00000 n
+0000000902 00000 n
+0000000962 00000 n
+0000000983 00000 n
+0000001004 00000 n
+0000000065 00000 n
+0000000350 00000 n
+0000000861 00000 n
+0000000208 00000 n
+0000000841 00000 n
+0000001085 00000 n
+trailer
+<< /Info 13 0 R /Root 1 0 R /Size 14 >>
+startxref
+1242
+%%EOF
diff --git a/generated_apriltags/2.0x2.0cm/13.ai b/generated_apriltags/2.0x2.0cm/13.ai
new file mode 100644
index 0000000..9930720
Binary files /dev/null and b/generated_apriltags/2.0x2.0cm/13.ai differ
diff --git a/generated_apriltags/2.0x2.0cm/14.ai b/generated_apriltags/2.0x2.0cm/14.ai
new file mode 100644
index 0000000..e0cb14f
--- /dev/null
+++ b/generated_apriltags/2.0x2.0cm/14.ai
@@ -0,0 +1,74 @@
+%PDF-1.4
+%
+1 0 obj
+<< /Pages 2 0 R /Type /Catalog >>
+endobj
+8 0 obj
+<< /ExtGState 4 0 R /Font 3 0 R /Pattern 5 0 R
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Shading 6 0 R
+/XObject 7 0 R >>
+endobj
+11 0 obj
+<< /Annots 10 0 R /Contents 9 0 R
+/MediaBox [ 0 0 56.6929133858 56.6929133858 ] /Parent 2 0 R
+/Resources 8 0 R /Type /Page >>
+endobj
+9 0 obj
+<< /Filter /FlateDecode /Length 12 0 R >>
+stream
+xKj0@:NH%~ 붋 @{:)<yQlF{Ќd~o[{dV2L;iKB}/F_[l?&׃oql-Fb~͘`/Ʊ16盘2AbjcGzN@b(;%i>8Аsw}6)
+dr!um?s(6AQvTP;MzFb-ZܡPH-Z/JɠXiwSn3
2(9:,6ȠX)TYK(4؟#H
+endstream
+endobj
+12 0 obj
+448
+endobj
+10 0 obj
+[ ]
+endobj
+3 0 obj
+<< >>
+endobj
+4 0 obj
+<< /A1 << /CA 0 /Type /ExtGState /ca 1 >> >>
+endobj
+5 0 obj
+<< >>
+endobj
+6 0 obj
+<< >>
+endobj
+7 0 obj
+<< >>
+endobj
+2 0 obj
+<< /Count 1 /Kids [ 11 0 R ] /Type /Pages >>
+endobj
+13 0 obj
+<< /CreationDate (D:20260318165303+08'00')
+/Creator (Matplotlib v3.5.3, https://matplotlib.org)
+/Producer (Matplotlib pdf backend v3.5.3) >>
+endobj
+xref
+0 14
+0000000000 65535 f
+0000000016 00000 n
+0000001057 00000 n
+0000000913 00000 n
+0000000934 00000 n
+0000000994 00000 n
+0000001015 00000 n
+0000001036 00000 n
+0000000065 00000 n
+0000000350 00000 n
+0000000893 00000 n
+0000000208 00000 n
+0000000873 00000 n
+0000001117 00000 n
+trailer
+<< /Info 13 0 R /Root 1 0 R /Size 14 >>
+startxref
+1274
+%%EOF
diff --git a/generated_apriltags/2.0x2.0cm/15.ai b/generated_apriltags/2.0x2.0cm/15.ai
new file mode 100644
index 0000000..855fef0
Binary files /dev/null and b/generated_apriltags/2.0x2.0cm/15.ai differ
diff --git a/generated_apriltags/2.0x2.0cm/16.ai b/generated_apriltags/2.0x2.0cm/16.ai
new file mode 100644
index 0000000..14a152c
Binary files /dev/null and b/generated_apriltags/2.0x2.0cm/16.ai differ
diff --git a/generated_apriltags/2.0x2.0cm/17.ai b/generated_apriltags/2.0x2.0cm/17.ai
new file mode 100644
index 0000000..c28ce96
Binary files /dev/null and b/generated_apriltags/2.0x2.0cm/17.ai differ
diff --git a/generated_apriltags/2.0x2.0cm/18.ai b/generated_apriltags/2.0x2.0cm/18.ai
new file mode 100644
index 0000000..e9e6962
--- /dev/null
+++ b/generated_apriltags/2.0x2.0cm/18.ai
@@ -0,0 +1,74 @@
+%PDF-1.4
+%
+1 0 obj
+<< /Pages 2 0 R /Type /Catalog >>
+endobj
+8 0 obj
+<< /ExtGState 4 0 R /Font 3 0 R /Pattern 5 0 R
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Shading 6 0 R
+/XObject 7 0 R >>
+endobj
+11 0 obj
+<< /Annots 10 0 R /Contents 9 0 R
+/MediaBox [ 0 0 56.6929133858 56.6929133858 ] /Parent 2 0 R
+/Resources 8 0 R /Type /Page >>
+endobj
+9 0 obj
+<< /Filter /FlateDecode /Length 12 0 R >>
+stream
+xKj0EZV Kȸ'D6nNJ<(X?ޕvwOޫOm_L&
c5g_zߏxmNsuV2Ԏ3{)8W*t~@GglS۞T!D/Y9glY~04$\uڮLc]B?SJPQq!҉FƵAeƇb7[tj(\B@a
+MyB"ᚆ25Qȸ:3JP˱Tƫ˂&uKq4IOu("(lPciLAa
+ALI9/T,K(mB-BB@w$XPQv Aa
+eӱ)(lPc! oCO x
+endstream
+endobj
+12 0 obj
+412
+endobj
+10 0 obj
+[ ]
+endobj
+3 0 obj
+<< >>
+endobj
+4 0 obj
+<< /A1 << /CA 0 /Type /ExtGState /ca 1 >> >>
+endobj
+5 0 obj
+<< >>
+endobj
+6 0 obj
+<< >>
+endobj
+7 0 obj
+<< >>
+endobj
+2 0 obj
+<< /Count 1 /Kids [ 11 0 R ] /Type /Pages >>
+endobj
+13 0 obj
+<< /CreationDate (D:20260318165303+08'00')
+/Creator (Matplotlib v3.5.3, https://matplotlib.org)
+/Producer (Matplotlib pdf backend v3.5.3) >>
+endobj
+xref
+0 14
+0000000000 65535 f
+0000000016 00000 n
+0000001021 00000 n
+0000000877 00000 n
+0000000898 00000 n
+0000000958 00000 n
+0000000979 00000 n
+0000001000 00000 n
+0000000065 00000 n
+0000000350 00000 n
+0000000857 00000 n
+0000000208 00000 n
+0000000837 00000 n
+0000001081 00000 n
+trailer
+<< /Info 13 0 R /Root 1 0 R /Size 14 >>
+startxref
+1238
+%%EOF
diff --git a/generated_apriltags/2.0x2.0cm/19.ai b/generated_apriltags/2.0x2.0cm/19.ai
new file mode 100644
index 0000000..a09015d
--- /dev/null
+++ b/generated_apriltags/2.0x2.0cm/19.ai
@@ -0,0 +1,72 @@
+%PDF-1.4
+%
+1 0 obj
+<< /Pages 2 0 R /Type /Catalog >>
+endobj
+8 0 obj
+<< /ExtGState 4 0 R /Font 3 0 R /Pattern 5 0 R
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Shading 6 0 R
+/XObject 7 0 R >>
+endobj
+11 0 obj
+<< /Annots 10 0 R /Contents 9 0 R
+/MediaBox [ 0 0 56.6929133858 56.6929133858 ] /Parent 2 0 R
+/Resources 8 0 R /Type /Page >>
+endobj
+9 0 obj
+<< /Filter /FlateDecode /Length 12 0 R >>
+stream
+xj0E
+}"Y/{dv#D6~^iQl=:̌V=frz>Ly?bCn~=zSq}]:R/AW^(ԹRѤ::c21
wGzP岱69,Ic~bC4rv).BEPA)Gu1qmAi
rl4SFgNbYs t#+vl(cNSSdC*#4ëiSRlmAʱ/,ѹ9i+b5V$J6Aȱ`SSdC*"uTi2'#+ EPR8ر]SPA)FJDWȊ|% HBKDb~<@
+endstream
+endobj
+12 0 obj
+424
+endobj
+10 0 obj
+[ ]
+endobj
+3 0 obj
+<< >>
+endobj
+4 0 obj
+<< /A1 << /CA 0 /Type /ExtGState /ca 1 >> >>
+endobj
+5 0 obj
+<< >>
+endobj
+6 0 obj
+<< >>
+endobj
+7 0 obj
+<< >>
+endobj
+2 0 obj
+<< /Count 1 /Kids [ 11 0 R ] /Type /Pages >>
+endobj
+13 0 obj
+<< /CreationDate (D:20260318165303+08'00')
+/Creator (Matplotlib v3.5.3, https://matplotlib.org)
+/Producer (Matplotlib pdf backend v3.5.3) >>
+endobj
+xref
+0 14
+0000000000 65535 f
+0000000016 00000 n
+0000001033 00000 n
+0000000889 00000 n
+0000000910 00000 n
+0000000970 00000 n
+0000000991 00000 n
+0000001012 00000 n
+0000000065 00000 n
+0000000350 00000 n
+0000000869 00000 n
+0000000208 00000 n
+0000000849 00000 n
+0000001093 00000 n
+trailer
+<< /Info 13 0 R /Root 1 0 R /Size 14 >>
+startxref
+1250
+%%EOF
diff --git a/generated_apriltags/2.0x2.0cm/2.ai b/generated_apriltags/2.0x2.0cm/2.ai
new file mode 100644
index 0000000..f97bb36
Binary files /dev/null and b/generated_apriltags/2.0x2.0cm/2.ai differ
diff --git a/generated_apriltags/2.0x2.0cm/20.ai b/generated_apriltags/2.0x2.0cm/20.ai
new file mode 100644
index 0000000..be696de
Binary files /dev/null and b/generated_apriltags/2.0x2.0cm/20.ai differ
diff --git a/generated_apriltags/2.0x2.0cm/3.ai b/generated_apriltags/2.0x2.0cm/3.ai
new file mode 100644
index 0000000..651f93a
Binary files /dev/null and b/generated_apriltags/2.0x2.0cm/3.ai differ
diff --git a/generated_apriltags/2.0x2.0cm/4.ai b/generated_apriltags/2.0x2.0cm/4.ai
new file mode 100644
index 0000000..75ca3be
Binary files /dev/null and b/generated_apriltags/2.0x2.0cm/4.ai differ
diff --git a/generated_apriltags/2.0x2.0cm/5.ai b/generated_apriltags/2.0x2.0cm/5.ai
new file mode 100644
index 0000000..e803900
--- /dev/null
+++ b/generated_apriltags/2.0x2.0cm/5.ai
@@ -0,0 +1,74 @@
+%PDF-1.4
+%
+1 0 obj
+<< /Pages 2 0 R /Type /Catalog >>
+endobj
+8 0 obj
+<< /ExtGState 4 0 R /Font 3 0 R /Pattern 5 0 R
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Shading 6 0 R
+/XObject 7 0 R >>
+endobj
+11 0 obj
+<< /Annots 10 0 R /Contents 9 0 R
+/MediaBox [ 0 0 56.6929133858 56.6929133858 ] /Parent 2 0 R
+/Resources 8 0 R /Type /Page >>
+endobj
+9 0 obj
+<< /Filter /FlateDecode /Length 12 0 R >>
+stream
+xMj0F:N K%@m=@HBThE"$H{1߷ͭ{RG^}j?!8~knn~=OnN{uR=)B+L<ߠ3mO*ZV.:݉^
+V1I}bG`HC5
}g}7F*r:.D:QA%zcm?ݬ-4:&
X'i*
+TXB3~Vh;."ᚆxL(ݨ(6Rc)'>ӳL'J#H8ϝPHZgmAxyW=
*Tr,2
*Tr,*#i5
K,-d*6X)-d*6Rc!(-93E*
+TXBZ&O
+endstream
+endobj
+12 0 obj
+425
+endobj
+10 0 obj
+[ ]
+endobj
+3 0 obj
+<< >>
+endobj
+4 0 obj
+<< /A1 << /CA 0 /Type /ExtGState /ca 1 >> >>
+endobj
+5 0 obj
+<< >>
+endobj
+6 0 obj
+<< >>
+endobj
+7 0 obj
+<< >>
+endobj
+2 0 obj
+<< /Count 1 /Kids [ 11 0 R ] /Type /Pages >>
+endobj
+13 0 obj
+<< /CreationDate (D:20260318165303+08'00')
+/Creator (Matplotlib v3.5.3, https://matplotlib.org)
+/Producer (Matplotlib pdf backend v3.5.3) >>
+endobj
+xref
+0 14
+0000000000 65535 f
+0000000016 00000 n
+0000001034 00000 n
+0000000890 00000 n
+0000000911 00000 n
+0000000971 00000 n
+0000000992 00000 n
+0000001013 00000 n
+0000000065 00000 n
+0000000350 00000 n
+0000000870 00000 n
+0000000208 00000 n
+0000000850 00000 n
+0000001094 00000 n
+trailer
+<< /Info 13 0 R /Root 1 0 R /Size 14 >>
+startxref
+1251
+%%EOF
diff --git a/generated_apriltags/2.0x2.0cm/6.ai b/generated_apriltags/2.0x2.0cm/6.ai
new file mode 100644
index 0000000..9dbde59
Binary files /dev/null and b/generated_apriltags/2.0x2.0cm/6.ai differ
diff --git a/generated_apriltags/2.0x2.0cm/7.ai b/generated_apriltags/2.0x2.0cm/7.ai
new file mode 100644
index 0000000..a8f8d29
Binary files /dev/null and b/generated_apriltags/2.0x2.0cm/7.ai differ
diff --git a/generated_apriltags/2.0x2.0cm/8.ai b/generated_apriltags/2.0x2.0cm/8.ai
new file mode 100644
index 0000000..5eaa25c
Binary files /dev/null and b/generated_apriltags/2.0x2.0cm/8.ai differ
diff --git a/generated_apriltags/2.0x2.0cm/9.ai b/generated_apriltags/2.0x2.0cm/9.ai
new file mode 100644
index 0000000..b3accd6
--- /dev/null
+++ b/generated_apriltags/2.0x2.0cm/9.ai
@@ -0,0 +1,76 @@
+%PDF-1.4
+%
+1 0 obj
+<< /Pages 2 0 R /Type /Catalog >>
+endobj
+8 0 obj
+<< /ExtGState 4 0 R /Font 3 0 R /Pattern 5 0 R
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Shading 6 0 R
+/XObject 7 0 R >>
+endobj
+11 0 obj
+<< /Annots 10 0 R /Contents 9 0 R
+/MediaBox [ 0 0 56.6929133858 56.6929133858 ] /Parent 2 0 R
+/Resources 8 0 R /Type /Page >>
+endobj
+9 0 obj
+<< /Filter /FlateDecode /Length 12 0 R >>
+stream
+xKj0@:NHϲȺ'D6^NJ/ZCmAQSԙTA%RJ@aJ2{X;P cr8D7Lo*;LbHX
+rfJɠX\;Zfʕ#v#N7R\=P L6:k
+
+dPr,*Wd
+
g~z
+endstream
+endobj
+12 0 obj
+442
+endobj
+10 0 obj
+[ ]
+endobj
+3 0 obj
+<< >>
+endobj
+4 0 obj
+<< /A1 << /CA 0 /Type /ExtGState /ca 1 >> >>
+endobj
+5 0 obj
+<< >>
+endobj
+6 0 obj
+<< >>
+endobj
+7 0 obj
+<< >>
+endobj
+2 0 obj
+<< /Count 1 /Kids [ 11 0 R ] /Type /Pages >>
+endobj
+13 0 obj
+<< /CreationDate (D:20260318165303+08'00')
+/Creator (Matplotlib v3.5.3, https://matplotlib.org)
+/Producer (Matplotlib pdf backend v3.5.3) >>
+endobj
+xref
+0 14
+0000000000 65535 f
+0000000016 00000 n
+0000001051 00000 n
+0000000907 00000 n
+0000000928 00000 n
+0000000988 00000 n
+0000001009 00000 n
+0000001030 00000 n
+0000000065 00000 n
+0000000350 00000 n
+0000000887 00000 n
+0000000208 00000 n
+0000000867 00000 n
+0000001111 00000 n
+trailer
+<< /Info 13 0 R /Root 1 0 R /Size 14 >>
+startxref
+1268
+%%EOF
diff --git a/generated_apriltags_svg_check/1.0x1.0cm/1.svg b/generated_apriltags_svg_check/1.0x1.0cm/1.svg
new file mode 100644
index 0000000..88fcfd5
--- /dev/null
+++ b/generated_apriltags_svg_check/1.0x1.0cm/1.svg
@@ -0,0 +1,191 @@
+
+
+
diff --git a/generated_apriltags_svg_check/1.5x1.5cm/1.svg b/generated_apriltags_svg_check/1.5x1.5cm/1.svg
new file mode 100644
index 0000000..32dd73d
--- /dev/null
+++ b/generated_apriltags_svg_check/1.5x1.5cm/1.svg
@@ -0,0 +1,191 @@
+
+
+
diff --git a/protos/cmvr/api/biohead_command.proto b/protos/cmvr/api/biohead_command.proto
index 98527ce..cc91e3e 100644
--- a/protos/cmvr/api/biohead_command.proto
+++ b/protos/cmvr/api/biohead_command.proto
@@ -174,4 +174,97 @@ message EmergencyStop {
}
}
+message SpeakStart {
+ message Request {
+ CommandHeader.Request header = 1; // 通用命令头
+ }
+
+ message Feedback {
+ CommandHeader.Feedback header = 1; // 通用反馈头
+ }
+}
+
+message SpeakStop {
+ message Request {
+ CommandHeader.Request header = 1; // 通用命令头
+ }
+
+ message Feedback {
+ CommandHeader.Feedback header = 1; // 通用反馈头
+ }
+}
+
+
+
+// 表情控制 - 微笑
+message Happy {
+ message Request {
+ CommandHeader.Request header = 1; // 通用命令头
+ }
+
+ message Feedback {
+ CommandHeader.Feedback header = 1; // 通用反馈头
+ }
+}
+
+
+// 表情控制 - 惊讶
+message Surprise {
+ message Request {
+ CommandHeader.Request header = 1; // 通用命令头
+ }
+
+ message Feedback {
+ CommandHeader.Feedback header = 1; // 通用反馈头
+ }
+}
+
+
+//表情控制-疲惫
+message ExpressionTired {
+ message Request {
+ CommandHeader.Request header = 1;
+ }
+
+ message Feedback {
+ CommandHeader.Feedback header = 1;
+ }
+}
+
+//表情控制-愤怒
+message ExpressionAngry {
+ message Request {
+ CommandHeader.Request header = 1;
+ }
+
+ message Feedback {
+ CommandHeader.Feedback header = 1;
+ }
+}
+
+
+//表情控制-悲伤
+message ExpressionSadness {
+ message Request {
+ CommandHeader.Request header = 1;
+ }
+
+ message Feedback {
+ CommandHeader.Feedback header = 1;
+ }
+}
+
+//表情控制-打哈欠
+message ExpressionYawn {
+ message Request {
+ CommandHeader.Request header = 1;
+ }
+
+ message Feedback {
+ CommandHeader.Feedback header = 1;
+ }
+}
+
+
+
diff --git a/protos/cmvr/api/biohead_service.proto b/protos/cmvr/api/biohead_service.proto
index 7ca4d83..f2dda32 100644
--- a/protos/cmvr/api/biohead_service.proto
+++ b/protos/cmvr/api/biohead_service.proto
@@ -19,4 +19,29 @@ service BioHeadService {
// 紧急停止
rpc EmergencyStop(EmergencyStop.Request) returns (EmergencyStop.Feedback){};
+
+ // 开始说话
+ rpc SpeakStart(SpeakStart.Request) returns (SpeakStart.Feedback){};
+ // 停止说话
+ rpc SpeakStop(SpeakStop.Request) returns (SpeakStop.Feedback){};
+
+ rpc Happy(Happy.Request) returns (Happy.Feedback){};
+
+ rpc Surprise(Surprise.Request) returns (Surprise.Feedback){};
+
+ rpc ExpressionTired(ExpressionTired.Request) returns (ExpressionTired.Feedback){};
+
+ rpc ExpressionAngry(ExpressionAngry.Request) returns (ExpressionAngry.Feedback){};
+
+ rpc ExpressionSadness(ExpressionSadness.Request) returns (ExpressionSadness.Feedback){};
+
+ rpc ExpressionYawn(ExpressionYawn.Request) returns (ExpressionYawn.Feedback){};
+
+
+
+
+
+
+
+
}
\ No newline at end of file
diff --git a/protos/cmvr/api/humanoid_robot_command.proto b/protos/cmvr/api/humanoid_robot_command.proto
index 13273a1..75804fb 100644
--- a/protos/cmvr/api/humanoid_robot_command.proto
+++ b/protos/cmvr/api/humanoid_robot_command.proto
@@ -40,6 +40,17 @@ enum RobotJointIndexDirection{
ROTATE_Y = 9; // 绕Y轴旋转
ROTATE_Z = 10; // 绕Z轴旋转
}
+
+
+// 4x4 齐次变换矩阵
+message TransformMatrix4x4 {
+ double m00 = 1; double m01 = 2; double m02 = 3; double m03 = 4;
+ double m10 = 5; double m11 = 6; double m12 = 7; double m13 = 8;
+ double m20 = 9; double m21 = 10; double m22 = 11; double m23 = 12;
+ double m30 = 13; double m31 = 14; double m32 = 15; double m33 = 16;
+}
+
+
message MoveJ{
message Request{
CommandHeader.Request header = 1;
@@ -157,4 +168,35 @@ message ServoJ{
CommandHeader.Feedback header= 1;
}
+}
+
+
+
+// 获取当前位姿的4x4矩阵
+message GetPoseMatrix {
+ message Request {
+ CommandHeader.Request header = 1;
+ string base_link = 2; // 基坐标系
+ string ee_link = 3; // 末端连杆名称
+ }
+
+ message Response {
+ CommandHeader.Feedback header = 1;
+ TransformMatrix4x4 matrix = 2; // 4x4 位姿矩阵
+ }
+}
+
+// 根据关节角度计算正运动学位姿矩阵
+message ComputeForwardKinematics {
+ message Request {
+ CommandHeader.Request header = 1;
+ string base_link = 2; // 基坐标系
+ string ee_link = 3; // 末端连杆名称
+ repeated JointCmd joints = 4; // 关节角度
+ }
+
+ message Response {
+ CommandHeader.Feedback header = 1;
+ TransformMatrix4x4 matrix = 2; // 计算出的4x4位姿矩阵
+ }
}
\ No newline at end of file
diff --git a/protos/cmvr/api/humanoid_robot_service.proto b/protos/cmvr/api/humanoid_robot_service.proto
index a4d52f5..b046eb9 100644
--- a/protos/cmvr/api/humanoid_robot_service.proto
+++ b/protos/cmvr/api/humanoid_robot_service.proto
@@ -16,4 +16,7 @@ service HumanoidRobotService{
rpc getPose(GetPose.Request) returns (GetPose.Response);
rpc calibrateZeroQ(CalibrateZeroQ.Request) returns (CalibrateZeroQ.Response);
rpc servoJ(ServoJ.Request) returns (ServoJ.Response);
+
+ rpc getPoseMatrix(GetPoseMatrix.Request) returns (GetPoseMatrix.Response);
+ rpc computeForwardKinematics(ComputeForwardKinematics.Request) returns (ComputeForwardKinematics.Response);
}
diff --git a/protos/cmvr/api/system_command.proto b/protos/cmvr/api/system_command.proto
index ea6f21d..52b9922 100644
--- a/protos/cmvr/api/system_command.proto
+++ b/protos/cmvr/api/system_command.proto
@@ -59,3 +59,14 @@ message UpdateParamsCommand {
CommandHeader.Feedback header = 1;
}
}
+
+
+message StopAllCommand {
+ message Request {
+ CommandHeader.Request header = 1;
+ }
+
+ message Feedback {
+ CommandHeader.Feedback header = 1;
+ }
+}
\ No newline at end of file
diff --git a/protos/cmvr/api/system_service.proto b/protos/cmvr/api/system_service.proto
index f96ea81..c8aee52 100644
--- a/protos/cmvr/api/system_service.proto
+++ b/protos/cmvr/api/system_service.proto
@@ -10,4 +10,6 @@ service SystemService {
rpc GetSystemStatus(GetSystemStatusCommand.Request) returns (GetSystemStatusCommand.Feedback) {}
rpc UpdateParams(UpdateParamsCommand.Request) returns (UpdateParamsCommand.Feedback) {}
+
+ rpc StopAll(StopAllCommand.Request) returns (StopAllCommand.Feedback) {}
}
\ No newline at end of file
diff --git a/protos/cmvr/config/camera_config/camera_config.proto b/protos/cmvr/config/camera_config/camera_config.proto
new file mode 100644
index 0000000..602337f
--- /dev/null
+++ b/protos/cmvr/config/camera_config/camera_config.proto
@@ -0,0 +1,60 @@
+// camera_config.proto - 所有配置都在这里
+syntax = "proto3";
+package cmvr.config;
+
+// 枚举定义
+enum CameraMode {
+ CAMERA_MODE_PHOTO = 0;
+ CAMERA_MODE_VIDEO = 1;
+}
+
+enum StreamMode {
+ STREAM_MODE_RGB = 0;
+ STREAM_MODE_RGBD = 1;
+ STREAM_MODE_DEPTH = 2;
+}
+
+enum AlignMode {
+ ALIGN_MODE_COLOR = 0;
+ ALIGN_MODE_DEPTH = 1;
+}
+
+// RealSense摄像头
+message RealSenseCameraConfig {
+ string id = 1;
+ string serialNumber = 2;
+ int32 width = 3;
+ int32 height = 4;
+ int32 fps = 5;
+ string codec = 6;
+ CameraMode camera_mode = 7;
+ StreamMode stream_mode = 8;
+ AlignMode align_mode = 9;
+ int32 buffer_size = 10;
+ bool sync = 11;
+ bool enable = 12;
+ int32 encode_width = 13;
+ int32 encode_height = 14;
+}
+
+// UVC摄像头
+message UVCCameraConfig {
+ string id = 1;
+ string usb = 2; // 或者叫 device_path
+ int32 width = 3;
+ int32 height = 4;
+ int32 fps = 5;
+ string codec = 6;
+ CameraMode camera_mode = 7;
+ StreamMode stream_mode = 8;
+ int32 buffer_size = 9;
+ bool enable = 10;
+ int32 encode_width = 11;
+ int32 encode_height = 12;
+}
+
+// 主配置消息 - 统一管理所有摄像头
+message CameraConfig {
+ repeated UVCCameraConfig uvc_cameras = 1;
+ repeated RealSenseCameraConfig realsense_cameras = 2;
+}
diff --git a/protos/cmvr/config/dexhand_config/dexhand_config.proto b/protos/cmvr/config/dexhand_config/dexhand_config.proto
new file mode 100644
index 0000000..8502021
--- /dev/null
+++ b/protos/cmvr/config/dexhand_config/dexhand_config.proto
@@ -0,0 +1,15 @@
+syntax = "proto3";
+package cmvr.config;
+
+message RH56DFTPDexHandConfig{
+ string id = 1;
+ string ip = 2;
+ int32 port = 3;
+ bool enable = 4;
+}
+
+
+
+message DexHandConfig{
+ repeated RH56DFTPDexHandConfig rh56dftp_dexhands = 1;
+}
\ No newline at end of file
diff --git a/protos/cmvr/config/microphone_config/microphone_config.proto b/protos/cmvr/config/microphone_config/microphone_config.proto
new file mode 100644
index 0000000..be4451f
--- /dev/null
+++ b/protos/cmvr/config/microphone_config/microphone_config.proto
@@ -0,0 +1,15 @@
+syntax = "proto3";
+package cmvr.config;
+
+message FFMpegMicroPhoneConfig{
+ string id = 1;
+ int32 channels = 2;
+ int32 sampleRate = 3;
+ int32 volume = 4;
+ bool enable = 5;
+ string input_device = 6;
+}
+
+message MicroPhoneConfig{
+ repeated FFMpegMicroPhoneConfig ffmpeg_microphones = 1;
+}
\ No newline at end of file
diff --git a/protos/cmvr/config/pinocchio_dls_ik_config.proto b/protos/cmvr/config/pinocchio_dls_ik_config.proto
new file mode 100644
index 0000000..e69de29
diff --git a/protos/cmvr/config/pinocchio_qp_ik_solver_config.proto b/protos/cmvr/config/pinocchio_qp_ik_solver_config.proto
new file mode 100644
index 0000000..45cbc10
--- /dev/null
+++ b/protos/cmvr/config/pinocchio_qp_ik_solver_config.proto
@@ -0,0 +1,17 @@
+syntax = "proto3";
+
+package cmvr.config;
+
+// 只管 QP 这个 IK 的参数
+message PinocchioQpIKConfig {
+ string urdf_path = 1;
+ string base_frame_name = 2;
+ string flange_frame_name = 3;
+ string tcp_frame_name = 4;
+
+ double lambda = 5;
+ double w_posrot = 6;
+ int32 max_iters = 7;
+ double tol = 8;
+ double qp_time_limit = 9;
+}
diff --git a/protos/cmvr/config/speaker_config/speaker_conifg.proto b/protos/cmvr/config/speaker_config/speaker_conifg.proto
new file mode 100644
index 0000000..7402b46
--- /dev/null
+++ b/protos/cmvr/config/speaker_config/speaker_conifg.proto
@@ -0,0 +1,11 @@
+syntax = "proto3";
+package cmvr.config;
+
+message FFMpegSpeakerConfig{
+ string id = 1;
+ bool enable = 2;
+}
+
+message SpeakerConfig{
+ repeated FFMpegSpeakerConfig ffmpeg_speakers = 1;
+}
\ No newline at end of file
diff --git a/protos/cmvr/config/touch_screen_app_config/touch_screen_app_config.proto b/protos/cmvr/config/touch_screen_app_config/touch_screen_app_config.proto
new file mode 100644
index 0000000..8b8bf17
--- /dev/null
+++ b/protos/cmvr/config/touch_screen_app_config/touch_screen_app_config.proto
@@ -0,0 +1,122 @@
+syntax = "proto3";
+
+package cmvr.config;
+
+message TouchScreenVec3 {
+ optional double x = 1;
+ optional double y = 2;
+ optional double z = 3;
+}
+
+message TouchScreenTwist6 {
+ optional double vx = 1;
+ optional double vy = 2;
+ optional double vz = 3;
+ optional double wx = 4;
+ optional double wy = 5;
+ optional double wz = 6;
+}
+
+message TouchScreenMatrix3d {
+ optional double m00 = 1;
+ optional double m01 = 2;
+ optional double m02 = 3;
+ optional double m10 = 4;
+ optional double m11 = 5;
+ optional double m12 = 6;
+ optional double m20 = 7;
+ optional double m21 = 8;
+ optional double m22 = 9;
+}
+
+message TouchScreenInitJointPoint {
+ optional string joint_name = 1;
+ optional double rad = 2;
+}
+
+enum TouchScreenDepthPolicy {
+ TOUCH_SCREEN_DEPTH_POLICY_NONE = 0;
+ TOUCH_SCREEN_DEPTH_POLICY_PREFER = 1;
+ TOUCH_SCREEN_DEPTH_POLICY_REQUIRE = 2;
+}
+
+enum TouchScreenTargetPointMethod {
+ TOUCH_SCREEN_TARGET_POINT_METHOD_TAG_PLANE = 0;
+ TOUCH_SCREEN_TARGET_POINT_METHOD_DEPTH_IMAGE = 1;
+}
+
+enum TouchScreenFingerType {
+ TOUCH_SCREEN_FINGER_TYPE_PINKY = 0;
+ TOUCH_SCREEN_FINGER_TYPE_RING = 1;
+ TOUCH_SCREEN_FINGER_TYPE_MIDDLE = 2;
+ TOUCH_SCREEN_FINGER_TYPE_INDEX = 3;
+ TOUCH_SCREEN_FINGER_TYPE_THUMB = 4;
+}
+
+enum TouchScreenTactileRegion {
+ TOUCH_SCREEN_TACTILE_REGION_TIP = 0;
+ TOUCH_SCREEN_TACTILE_REGION_FINGER = 1;
+ TOUCH_SCREEN_TACTILE_REGION_PAD = 2;
+ TOUCH_SCREEN_TACTILE_REGION_TIP_AND_FINGER = 3;
+ TOUCH_SCREEN_TACTILE_REGION_THUMB_MIDDLE = 4;
+}
+
+message TouchScreenAppConfig {
+ optional string robot_id = 40;
+ optional string dexhand_id = 41;
+ optional string camera_id = 42;
+ optional bool move_to_init_position = 43;
+ repeated TouchScreenInitJointPoint init_joint_positions = 44;
+ optional double init_movej_vel = 45;
+ optional double init_movej_acc = 46;
+ optional bool move_to_init_position_before_start = 47;
+
+ optional string urdf_path = 1;
+ optional string base_link = 2;
+ optional string flange_link = 3;
+ optional string camera_link = 4;
+
+ optional double tag_size_m = 5;
+ optional TouchScreenDepthPolicy depth_policy = 6;
+ optional TouchScreenTargetPointMethod target_point_method = 7;
+
+ TouchScreenVec3 hover_target_in_camera = 8;
+ optional double target_rx = 9;
+ optional double target_ry = 10;
+ optional double target_rz = 11;
+
+ optional double ibvs_lambda = 12;
+ optional double ibvs_mu = 13;
+ optional double ibvs_qdot_max = 14;
+ TouchScreenTwist6 ibvs_vmax6 = 15;
+ optional bool enable_joint_limit_avoidance = 16;
+ optional double joint_limit_avoidance_gain = 17;
+ optional double joint_limit_avoidance_margin_ratio = 18;
+ optional double joint_limit_avoidance_max_push = 19;
+
+ TouchScreenMatrix3d r_camera_to_visp = 20;
+ TouchScreenMatrix3d r_camera_to_urdf = 21;
+
+ repeated string control_joint_names = 22;
+
+ optional double align_xy_threshold_m = 23;
+ optional double align_z_threshold_m = 24;
+ optional double align_rot_threshold_rad = 25;
+ optional int32 align_stable_frames = 26;
+ optional double align_timeout_s = 27;
+ optional bool pause_after_align_reached = 28;
+
+ TouchScreenTwist6 touch_twist_base = 29;
+ optional double touch_acceleration = 30;
+ optional double touch_forward_duration_s = 31;
+
+ optional double dwell_time_s = 32;
+ TouchScreenTwist6 retract_twist_base = 33;
+ optional double retract_acceleration = 34;
+ optional double retract_duration_s = 35;
+
+ optional TouchScreenFingerType tactile_finger = 36;
+ optional TouchScreenTactileRegion tactile_region = 37;
+ optional double tactile_pressure_sum_threshold = 38;
+ optional double tactile_pressure_peak_threshold = 39;
+}
diff --git a/requirements.txt b/requirements.txt
index b09854e..11ec90c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,14 +2,14 @@ fastapi==0.124.4
grpcio==1.76.0
grpcio-tools==1.76.0
matplotlib==3.9.4
+numpy==1.26.4
protobuf==6.33.2
pydantic==2.12.5
PyQt5==5.15.11
+pyqtgraph>=0.13.7
requests==2.32.5
yourdfpy
uvicorn==0.38.0
av>=10.0.0
-opencv-python>=4.0.0
-
-numpy==1.26.4
+# Includes base cv2 plus cv2.aruco used by the apriltag scripts.
opencv-contrib-python==4.10.0.84
diff --git a/scripts/__pycache__/generate_apriltag_single.cpython-39.pyc b/scripts/__pycache__/generate_apriltag_single.cpython-39.pyc
new file mode 100644
index 0000000..a40b78d
Binary files /dev/null and b/scripts/__pycache__/generate_apriltag_single.cpython-39.pyc differ
diff --git a/scripts/apriltags_border_20x15cm_A4.pdf b/scripts/apriltags_border_20x15cm_A4.pdf
index d405961..0ce1b88 100644
Binary files a/scripts/apriltags_border_20x15cm_A4.pdf and b/scripts/apriltags_border_20x15cm_A4.pdf differ
diff --git a/scripts/apriltags_border_20x15cm_A4.sync-conflict-20260318-013404-55JW2V4.pdf b/scripts/apriltags_border_20x15cm_A4.sync-conflict-20260318-013404-55JW2V4.pdf
new file mode 100644
index 0000000..3c285f2
Binary files /dev/null and b/scripts/apriltags_border_20x15cm_A4.sync-conflict-20260318-013404-55JW2V4.pdf differ
diff --git a/scripts/generate_apriltag_batch.py b/scripts/generate_apriltag_batch.py
new file mode 100644
index 0000000..6ca61fc
--- /dev/null
+++ b/scripts/generate_apriltag_batch.py
@@ -0,0 +1,162 @@
+#!/usr/bin/env python3
+"""Generate AprilTag batches grouped by final exported size."""
+
+import argparse
+from pathlib import Path
+import sys
+from typing import List
+
+from generate_apriltag_single import (
+ APRILTAG_FAMILIES,
+ build_tag_geometry,
+ save_tag_artwork,
+)
+
+
+SUPPORTED_FORMATS = ("ai", "svg", "pdf", "png")
+
+
+def format_cm_label(value: float) -> str:
+ rounded = round(value)
+ if abs(value - rounded) < 1e-9:
+ return f"{value:.1f}"
+ return f"{value:.3f}".rstrip("0").rstrip(".")
+
+
+def normalize_formats(values: List[str]) -> List[str]:
+ normalized = []
+ seen = set()
+ for value in values:
+ fmt = value.lower().lstrip(".")
+ if fmt == "cdr":
+ raise SystemExit(
+ "Native .cdr export is not supported. Use .ai output and open/import it in CorelDRAW."
+ )
+ if fmt not in SUPPORTED_FORMATS:
+ supported = ", ".join(SUPPORTED_FORMATS + ("cdr",))
+ raise SystemExit(f"Unsupported format '{value}'. Supported: {supported}")
+ if fmt not in seen:
+ normalized.append(fmt)
+ seen.add(fmt)
+ return normalized
+
+
+def parse_args() -> argparse.Namespace:
+ parser = argparse.ArgumentParser(
+ description="Generate AprilTag ids 1-20 in multiple final sizes and separate folders"
+ )
+ parser.add_argument("--id-start", type=int, default=1, help="First tag ID, inclusive")
+ parser.add_argument("--id-end", type=int, default=20, help="Last tag ID, inclusive")
+ parser.add_argument(
+ "--sizes-cm",
+ type=float,
+ nargs="+",
+ default=[2.0, 1.5, 1.0],
+ help="Square sizes in cm, one folder per size",
+ )
+ parser.add_argument(
+ "--size-mode",
+ type=str,
+ default="canvas",
+ choices=("canvas", "marker"),
+ help=(
+ "How to interpret --sizes-cm: canvas means final output size including white border; "
+ "marker means black tag size excluding white border."
+ ),
+ )
+ parser.add_argument(
+ "--family",
+ type=str,
+ default="tag36h11",
+ choices=sorted(APRILTAG_FAMILIES.keys()),
+ help="AprilTag family",
+ )
+ parser.add_argument(
+ "--tag-white-ratio",
+ type=str,
+ default="1:4",
+ help="Total white border to marker ratio when --tag-white-border-cm is not set",
+ )
+ parser.add_argument(
+ "--tag-white-border-cm",
+ type=float,
+ default=None,
+ help="Optional override: white margin on each side in cm",
+ )
+ parser.add_argument(
+ "--margin-cm",
+ type=float,
+ default=0.0,
+ help="Extra white margin around the tag tile inside the final exported square",
+ )
+ parser.add_argument("--dpi", type=int, default=300, help="Output DPI for raster export")
+ parser.add_argument(
+ "--formats",
+ nargs="+",
+ default=["svg"],
+ help="Output formats. Supported: ai, svg, pdf, png",
+ )
+ parser.add_argument(
+ "--output-dir",
+ type=Path,
+ default=Path("generated_apriltags"),
+ help="Root folder where size subfolders will be created",
+ )
+ return parser.parse_args()
+
+
+def main() -> int:
+ args = parse_args()
+
+ if args.id_start < 0:
+ raise SystemExit("id-start must be >= 0")
+ if args.id_end < args.id_start:
+ raise SystemExit("id-end must be >= id-start")
+ if not args.sizes_cm:
+ raise SystemExit("sizes-cm must contain at least one size")
+
+ formats = normalize_formats(args.formats)
+ total_files = 0
+
+ for size_cm in args.sizes_cm:
+ size_label = format_cm_label(size_cm)
+ size_dir = args.output_dir / f"{size_label}x{size_label}cm"
+ size_dir.mkdir(parents=True, exist_ok=True)
+
+ for tag_id in range(args.id_start, args.id_end + 1):
+ geometry, aruco_dict = build_tag_geometry(
+ tag_id=tag_id,
+ size_cm=size_cm,
+ size_mode=args.size_mode,
+ family=args.family,
+ tag_white_ratio_text=args.tag_white_ratio,
+ tag_white_border_cm=args.tag_white_border_cm,
+ margin_cm=args.margin_cm,
+ )
+
+ for fmt in formats:
+ output_path = size_dir / f"{tag_id}.{fmt}"
+ save_tag_artwork(
+ output_path=output_path,
+ geometry=geometry,
+ aruco_dict=aruco_dict,
+ dpi=args.dpi,
+ a4_landscape=False,
+ exact_size_pdf=True,
+ )
+ total_files += 1
+
+ print(
+ f"Saved ids {args.id_start}-{args.id_end} at {size_label}x{size_label} cm -> {size_dir}"
+ )
+
+ print(
+ f"Completed batch export: ids={args.id_start}-{args.id_end}, "
+ f"sizes={len(args.sizes_cm)}, size_mode={args.size_mode}, "
+ f"formats={','.join(formats)}, files={total_files}"
+ )
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/scripts/generate_apriltag_single.py b/scripts/generate_apriltag_single.py
index 8ce3bae..581f264 100644
--- a/scripts/generate_apriltag_single.py
+++ b/scripts/generate_apriltag_single.py
@@ -1,11 +1,12 @@
#!/usr/bin/env python3
-"""Generate a single AprilTag image with configurable id and physical size."""
+"""Generate single AprilTag artwork in raster or vector formats."""
import argparse
+from dataclasses import dataclass
import os
from pathlib import Path
import sys
-from typing import Tuple
+from typing import List, Optional, Sequence, Tuple
try:
import numpy as np
@@ -27,6 +28,7 @@ try:
import matplotlib
matplotlib.use("Agg", force=True)
+ import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
except ImportError as exc:
raise SystemExit(
@@ -43,6 +45,21 @@ APRILTAG_FAMILIES = {
A4_WIDTH_CM = 21.0
A4_HEIGHT_CM = 29.7
+MARKER_BORDER_BITS = 1
+SUPPORTED_SUFFIXES = {".png", ".pdf", ".svg", ".ai"}
+
+
+@dataclass(frozen=True)
+class TagGeometry:
+ tag_id: int
+ family: str
+ marker_size_cm: float
+ tag_white_ratio: float
+ tag_white_border_cm: float
+ tag_tile_size_cm: float
+ margin_cm: float
+ canvas_size_cm: float
+ marker_modules: int
def cm_to_px(cm: float, dpi: int) -> int:
@@ -92,10 +109,190 @@ def make_marker(aruco_dict: "cv2.aruco_Dictionary", marker_id: int, size_px: int
return marker
marker = np.zeros((size_px, size_px), dtype=np.uint8)
- cv2.aruco.drawMarker(aruco_dict, marker_id, size_px, marker, 1)
+ cv2.aruco.drawMarker(aruco_dict, marker_id, size_px, marker, MARKER_BORDER_BITS)
return marker
+def compute_tag_dimensions(
+ size_cm: float,
+ size_mode: str,
+ tag_white_ratio_text: str,
+ tag_white_border_cm: Optional[float],
+ margin_cm: float,
+) -> Tuple[float, float, float, float, float]:
+ if size_cm <= 0:
+ raise SystemExit("size-cm/tag-size-cm must be > 0")
+ if margin_cm < 0:
+ raise SystemExit("margin-cm must be >= 0")
+
+ if tag_white_border_cm is None:
+ try:
+ tag_white_ratio = parse_ratio_text(tag_white_ratio_text)
+ except ValueError as exc:
+ raise SystemExit(f"tag-white-ratio parse error: {exc}") from exc
+
+ if size_mode == "marker":
+ marker_size_cm = size_cm
+ tag_white_border_cm = marker_size_cm * tag_white_ratio / 2.0
+ tag_tile_size_cm = marker_size_cm + 2.0 * tag_white_border_cm
+ canvas_size_cm = tag_tile_size_cm + 2.0 * margin_cm
+ elif size_mode == "canvas":
+ canvas_size_cm = size_cm
+ tag_tile_size_cm = canvas_size_cm - 2.0 * margin_cm
+ if tag_tile_size_cm <= 0:
+ raise SystemExit("canvas size must be larger than 2 * margin-cm")
+ marker_size_cm = tag_tile_size_cm / (1.0 + tag_white_ratio)
+ if marker_size_cm <= 0:
+ raise SystemExit("canvas size is too small for the requested white border ratio")
+ tag_white_border_cm = marker_size_cm * tag_white_ratio / 2.0
+ else:
+ raise SystemExit(f"Unsupported size mode '{size_mode}'")
+ else:
+ if tag_white_border_cm < 0:
+ raise SystemExit("tag-white-border-cm must be >= 0")
+ if size_mode == "marker":
+ marker_size_cm = size_cm
+ tag_tile_size_cm = marker_size_cm + 2.0 * tag_white_border_cm
+ canvas_size_cm = tag_tile_size_cm + 2.0 * margin_cm
+ elif size_mode == "canvas":
+ canvas_size_cm = size_cm
+ tag_tile_size_cm = canvas_size_cm - 2.0 * margin_cm
+ if tag_tile_size_cm <= 0:
+ raise SystemExit("canvas size must be larger than 2 * margin-cm")
+ marker_size_cm = tag_tile_size_cm - 2.0 * tag_white_border_cm
+ if marker_size_cm <= 0:
+ raise SystemExit(
+ "canvas size is too small after subtracting margin-cm and tag-white-border-cm"
+ )
+ else:
+ raise SystemExit(f"Unsupported size mode '{size_mode}'")
+ tag_white_ratio = 2.0 * tag_white_border_cm / marker_size_cm
+
+ return (
+ marker_size_cm,
+ tag_white_ratio,
+ tag_white_border_cm,
+ tag_tile_size_cm,
+ canvas_size_cm,
+ )
+
+
+def build_tag_geometry(
+ tag_id: int,
+ size_cm: float,
+ size_mode: str,
+ family: str,
+ tag_white_ratio_text: str,
+ tag_white_border_cm: Optional[float],
+ margin_cm: float,
+) -> Tuple[TagGeometry, "cv2.aruco_Dictionary"]:
+ if tag_id < 0:
+ raise SystemExit("id must be >= 0")
+
+ aruco_dict, max_tags = resolve_aruco_dict(family)
+ if tag_id >= max_tags:
+ raise SystemExit(
+ f"Tag ID {tag_id} exceeds family max ({max_tags - 1}) for family {family}."
+ )
+
+ (
+ marker_size_cm,
+ tag_white_ratio,
+ resolved_white_border_cm,
+ tag_tile_size_cm,
+ canvas_size_cm,
+ ) = compute_tag_dimensions(
+ size_cm=size_cm,
+ size_mode=size_mode,
+ tag_white_ratio_text=tag_white_ratio_text,
+ tag_white_border_cm=tag_white_border_cm,
+ margin_cm=margin_cm,
+ )
+
+ geometry = TagGeometry(
+ tag_id=tag_id,
+ family=family,
+ marker_size_cm=marker_size_cm,
+ tag_white_ratio=tag_white_ratio,
+ tag_white_border_cm=resolved_white_border_cm,
+ tag_tile_size_cm=tag_tile_size_cm,
+ margin_cm=margin_cm,
+ canvas_size_cm=canvas_size_cm,
+ marker_modules=aruco_dict.markerSize + 2 * MARKER_BORDER_BITS,
+ )
+ return geometry, aruco_dict
+
+
+def make_marker_module_grid(
+ aruco_dict: "cv2.aruco_Dictionary",
+ marker_id: int,
+ marker_modules: int,
+) -> np.ndarray:
+ pixels_per_module = 16
+ marker = make_marker(aruco_dict, marker_id, marker_modules * pixels_per_module)
+ return (
+ marker.reshape(marker_modules, pixels_per_module, marker_modules, pixels_per_module)
+ .mean(axis=(1, 3))
+ < 128
+ )
+
+
+def build_vector_rects(geometry: TagGeometry, module_grid: np.ndarray) -> List[Tuple[float, float, float, float]]:
+ rects = []
+ module_size_cm = geometry.marker_size_cm / float(geometry.marker_modules)
+ origin_cm = geometry.margin_cm + geometry.tag_white_border_cm
+
+ for row_index in range(geometry.marker_modules):
+ start_col = None
+ for col_index in range(geometry.marker_modules + 1):
+ is_black = col_index < geometry.marker_modules and bool(module_grid[row_index, col_index])
+ if is_black and start_col is None:
+ start_col = col_index
+ elif not is_black and start_col is not None:
+ rects.append(
+ (
+ origin_cm + start_col * module_size_cm,
+ origin_cm + row_index * module_size_cm,
+ (col_index - start_col) * module_size_cm,
+ module_size_cm,
+ )
+ )
+ start_col = None
+
+ return rects
+
+
+def render_raster_canvas(
+ geometry: TagGeometry,
+ aruco_dict: "cv2.aruco_Dictionary",
+ dpi: int,
+) -> np.ndarray:
+ canvas_px = cm_to_px(geometry.canvas_size_cm, dpi)
+ tag_tile_px = cm_to_px(geometry.tag_tile_size_cm, dpi)
+ marker_px = cm_to_px(geometry.marker_size_cm, dpi)
+
+ if canvas_px <= 0 or tag_tile_px <= 0 or marker_px <= 0:
+ raise SystemExit("At current DPI, tag size is too small to draw")
+ if canvas_px < tag_tile_px:
+ raise SystemExit("Canvas size is smaller than the tag tile")
+
+ canvas = np.full((canvas_px, canvas_px), 255, dtype=np.uint8)
+
+ tile_x_px = cm_to_px(geometry.margin_cm, dpi)
+ tile_y_px = cm_to_px(geometry.margin_cm, dpi)
+ tile_x_px = min(max(tile_x_px, 0), canvas_px - tag_tile_px)
+ tile_y_px = min(max(tile_y_px, 0), canvas_px - tag_tile_px)
+
+ marker_x_px = cm_to_px(geometry.margin_cm + geometry.tag_white_border_cm, dpi)
+ marker_y_px = cm_to_px(geometry.margin_cm + geometry.tag_white_border_cm, dpi)
+ marker_x_px = min(max(marker_x_px, tile_x_px), tile_x_px + tag_tile_px - marker_px)
+ marker_y_px = min(max(marker_y_px, tile_y_px), tile_y_px + tag_tile_px - marker_px)
+
+ marker = make_marker(aruco_dict, geometry.tag_id, marker_px)
+ canvas[marker_y_px : marker_y_px + marker_px, marker_x_px : marker_x_px + marker_px] = marker
+ return canvas
+
+
def save_printable_png(output_path: Path, canvas: np.ndarray, width_cm: float, height_cm: float, dpi: int) -> None:
fig = plt.figure(figsize=(width_cm / 2.54, height_cm / 2.54), dpi=dpi, frameon=False)
ax = fig.add_axes([0, 0, 1, 1])
@@ -105,14 +302,15 @@ def save_printable_png(output_path: Path, canvas: np.ndarray, width_cm: float, h
plt.close(fig)
-def save_a4_pdf(
- output_path: Path,
- canvas: np.ndarray,
- width_cm: float,
- height_cm: float,
- dpi: int,
+def resolve_page_dimensions(
+ canvas_width_cm: float,
+ canvas_height_cm: float,
+ center_on_a4: bool,
landscape: bool,
-) -> None:
+) -> Tuple[float, float, float, float]:
+ if not center_on_a4:
+ return canvas_width_cm, canvas_height_cm, 0.0, 0.0
+
if landscape:
page_w_cm = A4_HEIGHT_CM
page_h_cm = A4_WIDTH_CM
@@ -120,34 +318,160 @@ def save_a4_pdf(
page_w_cm = A4_WIDTH_CM
page_h_cm = A4_HEIGHT_CM
- if width_cm > page_w_cm + 1e-9 or height_cm > page_h_cm + 1e-9:
+ if canvas_width_cm > page_w_cm + 1e-9 or canvas_height_cm > page_h_cm + 1e-9:
raise SystemExit(
- f"Tag size {width_cm}x{height_cm} cm does not fit A4 "
+ f"Tag size {canvas_width_cm}x{canvas_height_cm} cm does not fit A4 "
f"({'landscape' if landscape else 'portrait'}) {page_w_cm}x{page_h_cm} cm."
)
+ return (
+ page_w_cm,
+ page_h_cm,
+ (page_w_cm - canvas_width_cm) / 2.0,
+ (page_h_cm - canvas_height_cm) / 2.0,
+ )
+
+
+def save_vector_art(
+ output_path: Path,
+ geometry: TagGeometry,
+ rects_cm: Sequence[Tuple[float, float, float, float]],
+ output_format: str,
+ dpi: int,
+ center_on_a4: bool,
+ landscape: bool,
+) -> None:
+ page_w_cm, page_h_cm, offset_x_cm, offset_y_cm = resolve_page_dimensions(
+ geometry.canvas_size_cm,
+ geometry.canvas_size_cm,
+ center_on_a4=center_on_a4,
+ landscape=landscape,
+ )
+
fig = plt.figure(figsize=(page_w_cm / 2.54, page_h_cm / 2.54), dpi=dpi, frameon=False)
- x0 = (page_w_cm - width_cm) / 2.0 / page_w_cm
- y0 = (page_h_cm - height_cm) / 2.0 / page_h_cm
- w = width_cm / page_w_cm
- h = height_cm / page_h_cm
- ax = fig.add_axes([x0, y0, w, h])
- ax.imshow(canvas, cmap="gray", vmin=0, vmax=255, interpolation="nearest")
+ ax = fig.add_axes([0, 0, 1, 1])
+ ax.set_xlim(0, page_w_cm)
+ ax.set_ylim(page_h_cm, 0)
+ ax.set_aspect("equal")
ax.axis("off")
- fig.savefig(output_path, format="pdf", dpi=dpi, pad_inches=0)
+
+ ax.add_patch(
+ mpatches.Rectangle((0.0, 0.0), page_w_cm, page_h_cm, facecolor="white", edgecolor="none")
+ )
+
+ for x_cm, y_cm, width_cm, height_cm in rects_cm:
+ ax.add_patch(
+ mpatches.Rectangle(
+ (offset_x_cm + x_cm, offset_y_cm + y_cm),
+ width_cm,
+ height_cm,
+ facecolor="black",
+ edgecolor="none",
+ )
+ )
+
+ fig.savefig(output_path, format=output_format, dpi=dpi, pad_inches=0)
plt.close(fig)
+def normalize_output_suffix(output_path: Path) -> str:
+ suffix = output_path.suffix.lower()
+ if suffix == ".cdr":
+ raise SystemExit(
+ "Native .cdr export is not supported in this script. "
+ "Use .ai or .svg output and open/import it in CorelDRAW."
+ )
+ if suffix not in SUPPORTED_SUFFIXES:
+ supported = ", ".join(sorted(SUPPORTED_SUFFIXES | {".cdr"}))
+ raise SystemExit(f"Unsupported output suffix '{suffix}'. Supported: {supported}")
+ return suffix
+
+
+def save_tag_artwork(
+ output_path: Path,
+ geometry: TagGeometry,
+ aruco_dict: "cv2.aruco_Dictionary",
+ dpi: int,
+ a4_landscape: bool,
+ exact_size_pdf: bool,
+) -> None:
+ suffix = normalize_output_suffix(output_path)
+ output_path.parent.mkdir(parents=True, exist_ok=True)
+
+ if suffix == ".png":
+ canvas = render_raster_canvas(geometry, aruco_dict, dpi)
+ save_printable_png(output_path, canvas, geometry.canvas_size_cm, geometry.canvas_size_cm, dpi)
+ return
+
+ module_grid = make_marker_module_grid(aruco_dict, geometry.tag_id, geometry.marker_modules)
+ rects_cm = build_vector_rects(geometry, module_grid)
+
+ if suffix == ".svg":
+ save_vector_art(
+ output_path,
+ geometry,
+ rects_cm,
+ output_format="svg",
+ dpi=dpi,
+ center_on_a4=False,
+ landscape=False,
+ )
+ return
+
+ if suffix == ".ai":
+ save_vector_art(
+ output_path,
+ geometry,
+ rects_cm,
+ output_format="pdf",
+ dpi=dpi,
+ center_on_a4=False,
+ landscape=False,
+ )
+ return
+
+ save_vector_art(
+ output_path,
+ geometry,
+ rects_cm,
+ output_format="pdf",
+ dpi=dpi,
+ center_on_a4=not exact_size_pdf,
+ landscape=a4_landscape,
+ )
+
+
+def describe_geometry(geometry: TagGeometry) -> str:
+ return (
+ f"family={geometry.family}, id={geometry.tag_id}, marker={round(geometry.marker_size_cm, 6)}cm, "
+ f"tag_white_ratio={round(geometry.tag_white_ratio, 6)}, "
+ f"tag_white_border={round(geometry.tag_white_border_cm, 6)}cm, "
+ f"tag_tile={round(geometry.tag_tile_size_cm, 6)}cm, "
+ f"margin={round(geometry.margin_cm, 6)}cm, "
+ f"canvas={round(geometry.canvas_size_cm, 6)}cm"
+ )
+
+
def parse_args() -> argparse.Namespace:
- parser = argparse.ArgumentParser(description="Generate single AprilTag image")
+ parser = argparse.ArgumentParser(description="Generate single AprilTag artwork")
parser.add_argument("--id", type=int, default=0, help="AprilTag ID (default: 0)")
parser.add_argument(
"--size-cm",
"--tag-size-cm",
- dest="tag_size_cm",
+ dest="size_cm",
type=float,
- default=15,
- help="AprilTag marker side length in cm (excluding per-tag white border)",
+ default=15.0,
+ help=(
+ "Base size in cm. Use --size-mode marker to treat it as the black tag size, "
+ "or --size-mode canvas to treat it as the final exported square size."
+ ),
+ )
+ parser.add_argument(
+ "--size-mode",
+ type=str,
+ default="marker",
+ choices=("marker", "canvas"),
+ help="Interpret --size-cm as marker size or final canvas size.",
)
parser.add_argument(
"--family",
@@ -178,93 +502,48 @@ def parse_args() -> argparse.Namespace:
parser.add_argument(
"--a4-landscape",
action="store_true",
- help="When output is PDF, use A4 landscape instead of portrait",
+ help="When output is PDF and --exact-size-pdf is not set, use A4 landscape instead of portrait",
+ )
+ parser.add_argument(
+ "--exact-size-pdf",
+ action="store_true",
+ help="For .pdf output, save an exact-size vector page instead of centering on A4",
)
parser.add_argument(
"--output",
type=Path,
default=Path("apriltag_id0_A4.pdf"),
- help="Output path (.pdf for centered A4 page, .png for exact-size image)",
+ help=(
+ "Output path. Supported suffixes: .png, .pdf, .svg, .ai. "
+ ".ai is written as a PDF-based Illustrator-compatible vector file."
+ ),
)
return parser.parse_args()
def main() -> int:
args = parse_args()
+ geometry, aruco_dict = build_tag_geometry(
+ tag_id=args.id,
+ size_cm=args.size_cm,
+ size_mode=args.size_mode,
+ family=args.family,
+ tag_white_ratio_text=args.tag_white_ratio,
+ tag_white_border_cm=args.tag_white_border_cm,
+ margin_cm=args.margin_cm,
+ )
- if args.id < 0:
- raise SystemExit("id must be >= 0")
- if args.tag_size_cm <= 0:
- raise SystemExit("size-cm/tag-size-cm must be > 0")
- if args.margin_cm < 0:
- raise SystemExit("margin-cm must be >= 0")
-
- if args.tag_white_border_cm is None:
- try:
- tag_white_ratio = parse_ratio_text(args.tag_white_ratio)
- except ValueError as exc:
- raise SystemExit(f"tag-white-ratio parse error: {exc}") from exc
- tag_white_border_cm = args.tag_size_cm * tag_white_ratio / 2.0
- else:
- if args.tag_white_border_cm < 0:
- raise SystemExit("tag-white-border-cm must be >= 0")
- tag_white_border_cm = args.tag_white_border_cm
- tag_white_ratio = 2.0 * tag_white_border_cm / args.tag_size_cm
-
- tag_tile_size_cm = args.tag_size_cm + 2.0 * tag_white_border_cm
- canvas_size_cm = tag_tile_size_cm + 2.0 * args.margin_cm
-
- aruco_dict, max_tags = resolve_aruco_dict(args.family)
- if args.id >= max_tags:
- raise SystemExit(
- f"Tag ID {args.id} exceeds family max ({max_tags - 1}) for family {args.family}."
- )
-
- canvas_px = cm_to_px(canvas_size_cm, args.dpi)
- tag_tile_px = cm_to_px(tag_tile_size_cm, args.dpi)
- marker_px = cm_to_px(args.tag_size_cm, args.dpi)
-
- if canvas_px <= 0 or tag_tile_px <= 0 or marker_px <= 0:
- raise SystemExit("At current DPI, tag size is too small to draw")
- if canvas_px < tag_tile_px:
- raise SystemExit("Canvas size is smaller than the tag tile")
-
- canvas = np.full((canvas_px, canvas_px), 255, dtype=np.uint8)
-
- tile_x_px = cm_to_px(args.margin_cm, args.dpi)
- tile_y_px = cm_to_px(args.margin_cm, args.dpi)
- tile_x_px = min(max(tile_x_px, 0), canvas_px - tag_tile_px)
- tile_y_px = min(max(tile_y_px, 0), canvas_px - tag_tile_px)
-
- marker_x_px = cm_to_px(args.margin_cm + tag_white_border_cm, args.dpi)
- marker_y_px = cm_to_px(args.margin_cm + tag_white_border_cm, args.dpi)
- marker_x_px = min(max(marker_x_px, tile_x_px), tile_x_px + tag_tile_px - marker_px)
- marker_y_px = min(max(marker_y_px, tile_y_px), tile_y_px + tag_tile_px - marker_px)
-
- marker = make_marker(aruco_dict, args.id, marker_px)
- canvas[marker_y_px : marker_y_px + marker_px, marker_x_px : marker_x_px + marker_px] = marker
-
- args.output.parent.mkdir(parents=True, exist_ok=True)
- if args.output.suffix.lower() == ".pdf":
- save_a4_pdf(
- args.output,
- canvas,
- canvas_size_cm,
- canvas_size_cm,
- args.dpi,
- landscape=args.a4_landscape,
- )
- else:
- save_printable_png(args.output, canvas, canvas_size_cm, canvas_size_cm, args.dpi)
+ save_tag_artwork(
+ output_path=args.output,
+ geometry=geometry,
+ aruco_dict=aruco_dict,
+ dpi=args.dpi,
+ a4_landscape=args.a4_landscape,
+ exact_size_pdf=args.exact_size_pdf,
+ )
print(f"Saved single tag image: {args.output}")
- print(
- f"family={args.family}, id={args.id}, marker={args.tag_size_cm}cm, "
- f"tag_white_ratio={round(tag_white_ratio, 6)}, "
- f"tag_white_border={round(tag_white_border_cm, 6)}cm, "
- f"tag_tile={round(tag_tile_size_cm, 6)}cm, "
- f"margin={args.margin_cm}cm, canvas={round(canvas_size_cm, 6)}cm"
- )
+ print(describe_geometry(geometry))
return 0
diff --git a/scripts/generate_apriltags_border.py b/scripts/generate_apriltags_border.py
index d754c30..191a39f 100644
--- a/scripts/generate_apriltags_border.py
+++ b/scripts/generate_apriltags_border.py
@@ -43,9 +43,9 @@ APRILTAG_FAMILIES = {
}
-A4_WIDTH_CM = 29.7
-A4_HEIGHT_CM = 21.0
-# A4_HEIGHT_CM = 29.7
+A4_WIDTH_CM = 21.0
+
+A4_HEIGHT_CM = 29.7
def cm_to_px(cm: float, dpi: int) -> int:
@@ -174,8 +174,8 @@ def write_layout_csv(path: Path, rows: List[Tuple[int, float, float, int, int]])
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Generate AprilTag border image")
- parser.add_argument("--width-cm", type=float, default=26.0, help="Rectangle width in cm")
- parser.add_argument("--height-cm", type=float, default=17.0, help="Rectangle height in cm")
+ parser.add_argument("--width-cm", type=float, default=20.0, help="Rectangle width in cm")
+ parser.add_argument("--height-cm", type=float, default=15.0, help="Rectangle height in cm")
parser.add_argument(
"--tag-size-cm",
type=float,
diff --git a/scripts/generate_apriltags_border.sync-conflict-20260318-013404-55JW2V4.py b/scripts/generate_apriltags_border.sync-conflict-20260318-013404-55JW2V4.py
new file mode 100644
index 0000000..9514cfe
--- /dev/null
+++ b/scripts/generate_apriltags_border.sync-conflict-20260318-013404-55JW2V4.py
@@ -0,0 +1,384 @@
+#!/usr/bin/env python3
+"""Generate an AprilTag border around a rectangle and save a printable image."""
+
+import argparse
+import csv
+import math
+import os
+from pathlib import Path
+import sys
+from typing import List, Tuple
+
+try:
+ import numpy as np
+except ImportError as exc:
+ raise SystemExit(
+ "Missing dependency 'numpy'. Install it with: pip install numpy"
+ ) from exc
+
+try:
+ import cv2
+except ImportError as exc:
+ raise SystemExit(
+ "Missing dependency 'opencv-contrib-python'. Install it with: pip install opencv-contrib-python"
+ ) from exc
+
+# Force non-GUI backend to avoid Qt/xcb plugin issues in headless environments.
+os.environ.setdefault("MPLBACKEND", "Agg")
+try:
+ import matplotlib
+ matplotlib.use("Agg", force=True)
+ import matplotlib.pyplot as plt
+except ImportError as exc:
+ raise SystemExit(
+ "Missing dependency 'matplotlib'. Install it with: pip install matplotlib"
+ ) from exc
+
+
+APRILTAG_FAMILIES = {
+ "tag16h5": ("DICT_APRILTAG_16h5", 30),
+ "tag25h9": ("DICT_APRILTAG_25h9", 35),
+ "tag36h10": ("DICT_APRILTAG_36h10", 2320),
+ "tag36h11": ("DICT_APRILTAG_36h11", 587),
+}
+
+
+A4_WIDTH_CM = 29.7
+A4_HEIGHT_CM = 21.0
+# A4_HEIGHT_CM = 29.7
+
+
+def cm_to_px(cm: float, dpi: int) -> int:
+ return int(round(cm / 2.54 * dpi))
+
+
+def parse_ratio_text(value: str) -> float:
+ text = value.strip()
+ if not text:
+ raise ValueError("empty ratio")
+ if ":" in text:
+ parts = text.split(":")
+ if len(parts) != 2:
+ raise ValueError(f"invalid ratio '{value}', expected like 1:4")
+ left = float(parts[0].strip())
+ right = float(parts[1].strip())
+ if left < 0 or right <= 0:
+ raise ValueError(f"invalid ratio '{value}', left must be >= 0 and right > 0")
+ return left / right
+ ratio = float(text)
+ if ratio < 0:
+ raise ValueError(f"invalid ratio '{value}', must be >= 0")
+ return ratio
+
+
+def border_positions(total_cm: float, tag_cm: float, pitch_cm: float) -> List[float]:
+ if total_cm + 1e-9 < tag_cm:
+ raise ValueError("rectangle side is smaller than one tag")
+
+ count = int(math.floor((total_cm - tag_cm) / pitch_cm)) + 1
+ count = max(1, count)
+ used_cm = tag_cm + (count - 1) * pitch_cm
+ margin_cm = (total_cm - used_cm) / 2.0
+ return [margin_cm + i * pitch_cm for i in range(count)]
+
+
+def side_positions_excluding_corners(total_cm: float, tag_cm: float, pitch_cm: float) -> List[float]:
+ inner_cm = total_cm - 2.0 * tag_cm
+ if inner_cm + 1e-9 < tag_cm:
+ return []
+
+ count = int(math.floor((inner_cm - tag_cm) / pitch_cm)) + 1
+ count = max(1, count)
+ used_cm = tag_cm + (count - 1) * pitch_cm
+ margin_cm = (inner_cm - used_cm) / 2.0
+ start_cm = tag_cm + margin_cm
+ return [start_cm + i * pitch_cm for i in range(count)]
+
+
+def resolve_aruco_dict(family: str) -> Tuple["cv2.aruco_Dictionary", int]:
+ if family not in APRILTAG_FAMILIES:
+ supported = ", ".join(sorted(APRILTAG_FAMILIES))
+ raise ValueError(f"Unsupported family '{family}'. Supported: {supported}")
+
+ dict_name, max_tags = APRILTAG_FAMILIES[family]
+ dict_id = getattr(cv2.aruco, dict_name, None)
+ if dict_id is None:
+ raise RuntimeError(
+ f"OpenCV build does not include {dict_name}. "
+ "Please install opencv-contrib-python."
+ )
+
+ return cv2.aruco.getPredefinedDictionary(dict_id), max_tags
+
+
+def make_marker(aruco_dict: "cv2.aruco_Dictionary", marker_id: int, size_px: int) -> np.ndarray:
+ if hasattr(cv2.aruco, "generateImageMarker"):
+ marker = cv2.aruco.generateImageMarker(aruco_dict, marker_id, size_px)
+ if marker is not None:
+ return marker
+
+ marker = np.zeros((size_px, size_px), dtype=np.uint8)
+ cv2.aruco.drawMarker(aruco_dict, marker_id, size_px, marker, 1)
+ return marker
+
+
+def save_printable_png(output_path: Path, canvas: np.ndarray, width_cm: float, height_cm: float, dpi: int) -> None:
+ fig = plt.figure(figsize=(width_cm / 2.54, height_cm / 2.54), dpi=dpi, frameon=False)
+ ax = fig.add_axes([0, 0, 1, 1])
+ ax.imshow(canvas, cmap="gray", vmin=0, vmax=255, interpolation="nearest")
+ ax.axis("off")
+ fig.savefig(output_path, dpi=dpi, pad_inches=0)
+ plt.close(fig)
+
+
+def save_a4_pdf(
+ output_path: Path,
+ canvas: np.ndarray,
+ width_cm: float,
+ height_cm: float,
+ dpi: int,
+ landscape: bool,
+) -> None:
+ if landscape:
+ page_w_cm = A4_HEIGHT_CM
+ page_h_cm = A4_WIDTH_CM
+ else:
+ page_w_cm = A4_WIDTH_CM
+ page_h_cm = A4_HEIGHT_CM
+
+ if width_cm > page_w_cm + 1e-9 or height_cm > page_h_cm + 1e-9:
+ raise SystemExit(
+ f"Pattern size {width_cm}x{height_cm} cm does not fit A4 "
+ f"({'landscape' if landscape else 'portrait'}) {page_w_cm}x{page_h_cm} cm."
+ )
+
+ fig = plt.figure(figsize=(page_w_cm / 2.54, page_h_cm / 2.54), dpi=dpi, frameon=False)
+ x0 = (page_w_cm - width_cm) / 2.0 / page_w_cm
+ y0 = (page_h_cm - height_cm) / 2.0 / page_h_cm
+ w = width_cm / page_w_cm
+ h = height_cm / page_h_cm
+ ax = fig.add_axes([x0, y0, w, h])
+ ax.imshow(canvas, cmap="gray", vmin=0, vmax=255, interpolation="nearest")
+ ax.axis("off")
+ fig.savefig(output_path, format="pdf", dpi=dpi, pad_inches=0)
+ plt.close(fig)
+
+
+def write_layout_csv(path: Path, rows: List[Tuple[int, float, float, int, int]]) -> None:
+ with path.open("w", newline="", encoding="utf-8") as f:
+ writer = csv.writer(f)
+ writer.writerow(["tag_id", "x_cm", "y_cm", "x_px", "y_px"])
+ for row in rows:
+ writer.writerow(row)
+
+
+def parse_args() -> argparse.Namespace:
+ parser = argparse.ArgumentParser(description="Generate AprilTag border image")
+ parser.add_argument("--width-cm", type=float, default=27.0, help="Rectangle width in cm")
+ parser.add_argument("--height-cm", type=float, default=18.0, help="Rectangle height in cm")
+ parser.add_argument(
+ "--tag-size-cm",
+ type=float,
+ default=2.0,
+ help="AprilTag marker side length in cm (excluding per-tag white border)",
+ )
+ parser.add_argument(
+ "--pitch-cm",
+ type=float,
+ default=None,
+ help="Distance between adjacent tag tile top-left positions in cm (default: auto = tag tile size)",
+ )
+ parser.add_argument(
+ "--family",
+ type=str,
+ default="tag36h11",
+ choices=sorted(APRILTAG_FAMILIES.keys()),
+ help="AprilTag family",
+ )
+ parser.add_argument("--start-id", type=int, default=0, help="First tag ID")
+ parser.add_argument(
+ "--wrap-ids",
+ action="store_true",
+ help="Wrap around if IDs exceed family max",
+ )
+ parser.add_argument(
+ "--white-border-cm",
+ type=float,
+ default=0.0,
+ help="Keep this much white margin on all four sides (cm)",
+ )
+ parser.add_argument(
+ "--tag-white-ratio",
+ type=str,
+ default="1:4",
+ help="Total white border to marker ratio (e.g. 1:4 means total white width is 1/4 of marker size)",
+ )
+ parser.add_argument(
+ "--tag-white-border-cm",
+ type=float,
+ default=None,
+ help="Optional override: white margin on each side in cm; if omitted, auto-computed from --tag-white-ratio",
+ )
+ parser.add_argument("--dpi", type=int, default=300, help="Output DPI")
+ parser.add_argument(
+ "--a4-landscape",
+ action="store_true",
+ help="When output is PDF, use A4 landscape instead of portrait",
+ )
+ parser.add_argument(
+ "--output",
+ type=Path,
+ default=Path("apriltags_border_20x15cm_A4.pdf"),
+ help="Output path (.pdf for A4 page, .png for exact-size image)",
+ )
+ parser.add_argument(
+ "--layout-csv",
+ type=Path,
+ default=None,
+ help="Optional CSV path to save tag placements",
+ )
+ return parser.parse_args()
+
+
+def main() -> int:
+ args = parse_args()
+
+ if args.width_cm <= 0 or args.height_cm <= 0 or args.tag_size_cm <= 0:
+ raise SystemExit("width/height/tag-size must all be > 0")
+ if args.white_border_cm < 0:
+ raise SystemExit("white-border-cm must be >= 0")
+
+ if args.tag_white_border_cm is None:
+ try:
+ tag_white_ratio = parse_ratio_text(args.tag_white_ratio)
+ except ValueError as exc:
+ raise SystemExit(f"tag-white-ratio parse error: {exc}") from exc
+ tag_white_border_cm = args.tag_size_cm * tag_white_ratio / 2.0
+ else:
+ if args.tag_white_border_cm < 0:
+ raise SystemExit("tag-white-border-cm must be >= 0")
+ tag_white_border_cm = args.tag_white_border_cm
+ tag_white_ratio = 2.0 * tag_white_border_cm / args.tag_size_cm
+
+ tag_tile_size_cm = args.tag_size_cm + 2.0 * tag_white_border_cm
+ pitch_cm = tag_tile_size_cm if args.pitch_cm is None else args.pitch_cm
+ if pitch_cm <= 0:
+ raise SystemExit("pitch-cm must be > 0")
+ if pitch_cm + 1e-9 < tag_tile_size_cm:
+ raise SystemExit(
+ f"pitch-cm ({pitch_cm}) is smaller than tag-size-cm + 2*tag-white-border-cm "
+ f"({tag_tile_size_cm}). Increase pitch-cm to keep white border around each tag."
+ )
+
+ inner_width_cm = args.width_cm - 2.0 * args.white_border_cm
+ inner_height_cm = args.height_cm - 2.0 * args.white_border_cm
+
+ if inner_width_cm + 1e-9 < tag_tile_size_cm or inner_height_cm + 1e-9 < tag_tile_size_cm:
+ raise SystemExit(
+ "After white border is reserved, inner area must still fit at least one full tag tile in both dimensions"
+ )
+
+ aruco_dict, max_tags = resolve_aruco_dict(args.family)
+
+ width_px = cm_to_px(args.width_cm, args.dpi)
+ height_px = cm_to_px(args.height_cm, args.dpi)
+ tag_tile_px = cm_to_px(tag_tile_size_cm, args.dpi)
+ marker_px = cm_to_px(args.tag_size_cm, args.dpi)
+
+ if width_px < tag_tile_px or height_px < tag_tile_px:
+ raise SystemExit("At current DPI, tag tile is larger than the canvas")
+ if marker_px <= 0:
+ raise SystemExit("At current DPI, tag-size-cm leaves no drawable marker pixels")
+
+ canvas = np.full((height_px, width_px), 255, dtype=np.uint8)
+
+ top_x_local = border_positions(inner_width_cm, tag_tile_size_cm, pitch_cm)
+ side_y_local = side_positions_excluding_corners(inner_height_cm, tag_tile_size_cm, pitch_cm)
+
+ placements_cm: List[Tuple[float, float]] = []
+
+ # Clockwise: top -> right -> bottom -> left
+ top_y = args.white_border_cm
+ left_x = args.white_border_cm
+ right_x = args.white_border_cm + inner_width_cm - tag_tile_size_cm
+ bottom_y = args.white_border_cm + inner_height_cm - tag_tile_size_cm
+
+ for x_local in top_x_local:
+ placements_cm.append((args.white_border_cm + x_local, top_y))
+ for y_local in side_y_local:
+ placements_cm.append((right_x, args.white_border_cm + y_local))
+ for x_local in reversed(top_x_local):
+ placements_cm.append((args.white_border_cm + x_local, bottom_y))
+ for y_local in reversed(side_y_local):
+ placements_cm.append((left_x, args.white_border_cm + y_local))
+
+ placements_csv: List[Tuple[int, float, float, int, int]] = []
+ current_id = args.start_id
+
+ for x_cm, y_cm in placements_cm:
+ if current_id >= max_tags:
+ if args.wrap_ids:
+ tag_id = current_id % max_tags
+ else:
+ raise SystemExit(
+ f"Tag ID {current_id} exceeds family max ({max_tags - 1}). "
+ "Use --wrap-ids or lower --start-id."
+ )
+ else:
+ tag_id = current_id
+
+ x_px = cm_to_px(x_cm, args.dpi)
+ y_px = cm_to_px(y_cm, args.dpi)
+
+ # Clamp to keep full tag tile in canvas despite rounding.
+ x_px = min(max(x_px, 0), width_px - tag_tile_px)
+ y_px = min(max(y_px, 0), height_px - tag_tile_px)
+
+ marker = make_marker(aruco_dict, tag_id, marker_px)
+
+ marker_x_px = cm_to_px(x_cm + tag_white_border_cm, args.dpi)
+ marker_y_px = cm_to_px(y_cm + tag_white_border_cm, args.dpi)
+
+ # Keep marker fully inside its tag tile despite rounding.
+ marker_x_px = min(max(marker_x_px, x_px), x_px + tag_tile_px - marker_px)
+ marker_y_px = min(max(marker_y_px, y_px), y_px + tag_tile_px - marker_px)
+
+ canvas[marker_y_px : marker_y_px + marker_px, marker_x_px : marker_x_px + marker_px] = marker
+ placements_csv.append((tag_id, round(x_cm, 4), round(y_cm, 4), x_px, y_px))
+
+ current_id += 1
+
+ args.output.parent.mkdir(parents=True, exist_ok=True)
+ if args.output.suffix.lower() == ".pdf":
+ save_a4_pdf(
+ args.output,
+ canvas,
+ args.width_cm,
+ args.height_cm,
+ args.dpi,
+ landscape=args.a4_landscape,
+ )
+ else:
+ save_printable_png(args.output, canvas, args.width_cm, args.height_cm, args.dpi)
+
+ if args.layout_csv is not None:
+ args.layout_csv.parent.mkdir(parents=True, exist_ok=True)
+ write_layout_csv(args.layout_csv, placements_csv)
+
+ print(f"Saved border image: {args.output}")
+ print(
+ f"family={args.family}, tags={len(placements_csv)}, "
+ f"size={args.width_cm}cm x {args.height_cm}cm, tag_marker={args.tag_size_cm}cm, "
+ f"pitch={round(pitch_cm, 6)}cm, white_border={args.white_border_cm}cm, "
+ f"tag_white_ratio={round(tag_white_ratio, 6)}, "
+ f"tag_white_border={round(tag_white_border_cm, 6)}cm, "
+ f"tag_tile={round(tag_tile_size_cm, 6)}cm, output={args.output.suffix.lower()}"
+ )
+ if args.layout_csv is not None:
+ print(f"Saved layout CSV: {args.layout_csv}")
+
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/scripts/generated_apriltags.zip b/scripts/generated_apriltags.zip
new file mode 100644
index 0000000..76fd1f9
Binary files /dev/null and b/scripts/generated_apriltags.zip differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/1.ai b/scripts/generated_apriltags/1.0x1.0cm/1.ai
new file mode 100644
index 0000000..77c847b
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/1.ai differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/1.png b/scripts/generated_apriltags/1.0x1.0cm/1.png
new file mode 100644
index 0000000..77073d9
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/1.png differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/10.ai b/scripts/generated_apriltags/1.0x1.0cm/10.ai
new file mode 100644
index 0000000..a6d3df7
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/10.ai differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/10.png b/scripts/generated_apriltags/1.0x1.0cm/10.png
new file mode 100644
index 0000000..029e85f
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/10.png differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/11.ai b/scripts/generated_apriltags/1.0x1.0cm/11.ai
new file mode 100644
index 0000000..bfdeafc
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/11.ai differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/11.png b/scripts/generated_apriltags/1.0x1.0cm/11.png
new file mode 100644
index 0000000..cbb8b66
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/11.png differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/12.ai b/scripts/generated_apriltags/1.0x1.0cm/12.ai
new file mode 100644
index 0000000..d483420
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/12.ai differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/12.png b/scripts/generated_apriltags/1.0x1.0cm/12.png
new file mode 100644
index 0000000..f334d99
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/12.png differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/13.ai b/scripts/generated_apriltags/1.0x1.0cm/13.ai
new file mode 100644
index 0000000..5b6f38f
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/13.ai differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/13.png b/scripts/generated_apriltags/1.0x1.0cm/13.png
new file mode 100644
index 0000000..4735652
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/13.png differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/14.ai b/scripts/generated_apriltags/1.0x1.0cm/14.ai
new file mode 100644
index 0000000..684b8a3
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/14.ai differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/14.png b/scripts/generated_apriltags/1.0x1.0cm/14.png
new file mode 100644
index 0000000..57f6425
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/14.png differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/15.ai b/scripts/generated_apriltags/1.0x1.0cm/15.ai
new file mode 100644
index 0000000..380bc3b
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/15.ai differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/15.png b/scripts/generated_apriltags/1.0x1.0cm/15.png
new file mode 100644
index 0000000..b9dc22c
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/15.png differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/16.ai b/scripts/generated_apriltags/1.0x1.0cm/16.ai
new file mode 100644
index 0000000..e98e6fa
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/16.ai differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/16.png b/scripts/generated_apriltags/1.0x1.0cm/16.png
new file mode 100644
index 0000000..a3c40b4
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/16.png differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/17.ai b/scripts/generated_apriltags/1.0x1.0cm/17.ai
new file mode 100644
index 0000000..9c96684
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/17.ai differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/17.png b/scripts/generated_apriltags/1.0x1.0cm/17.png
new file mode 100644
index 0000000..a076e9c
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/17.png differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/18.ai b/scripts/generated_apriltags/1.0x1.0cm/18.ai
new file mode 100644
index 0000000..29447eb
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/18.ai differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/18.png b/scripts/generated_apriltags/1.0x1.0cm/18.png
new file mode 100644
index 0000000..a17f1ae
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/18.png differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/19.ai b/scripts/generated_apriltags/1.0x1.0cm/19.ai
new file mode 100644
index 0000000..01861c9
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/19.ai differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/19.png b/scripts/generated_apriltags/1.0x1.0cm/19.png
new file mode 100644
index 0000000..707cb6a
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/19.png differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/2.ai b/scripts/generated_apriltags/1.0x1.0cm/2.ai
new file mode 100644
index 0000000..1066cf4
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/2.ai differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/2.png b/scripts/generated_apriltags/1.0x1.0cm/2.png
new file mode 100644
index 0000000..e0b22dc
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/2.png differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/20.ai b/scripts/generated_apriltags/1.0x1.0cm/20.ai
new file mode 100644
index 0000000..72c0358
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/20.ai differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/20.png b/scripts/generated_apriltags/1.0x1.0cm/20.png
new file mode 100644
index 0000000..771c1a0
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/20.png differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/3.ai b/scripts/generated_apriltags/1.0x1.0cm/3.ai
new file mode 100644
index 0000000..fe1dae3
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/3.ai differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/3.png b/scripts/generated_apriltags/1.0x1.0cm/3.png
new file mode 100644
index 0000000..2fc87e8
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/3.png differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/4.ai b/scripts/generated_apriltags/1.0x1.0cm/4.ai
new file mode 100644
index 0000000..d201a05
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/4.ai differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/4.png b/scripts/generated_apriltags/1.0x1.0cm/4.png
new file mode 100644
index 0000000..78c9ca1
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/4.png differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/5.ai b/scripts/generated_apriltags/1.0x1.0cm/5.ai
new file mode 100644
index 0000000..f52b0ea
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/5.ai differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/5.png b/scripts/generated_apriltags/1.0x1.0cm/5.png
new file mode 100644
index 0000000..bbf096d
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/5.png differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/6.ai b/scripts/generated_apriltags/1.0x1.0cm/6.ai
new file mode 100644
index 0000000..dd303e9
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/6.ai differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/6.png b/scripts/generated_apriltags/1.0x1.0cm/6.png
new file mode 100644
index 0000000..a31b3f5
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/6.png differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/7.ai b/scripts/generated_apriltags/1.0x1.0cm/7.ai
new file mode 100644
index 0000000..9eaf03c
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/7.ai differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/7.png b/scripts/generated_apriltags/1.0x1.0cm/7.png
new file mode 100644
index 0000000..2c8221d
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/7.png differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/8.ai b/scripts/generated_apriltags/1.0x1.0cm/8.ai
new file mode 100644
index 0000000..aa4dbea
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/8.ai differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/8.png b/scripts/generated_apriltags/1.0x1.0cm/8.png
new file mode 100644
index 0000000..5551e8c
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/8.png differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/9.ai b/scripts/generated_apriltags/1.0x1.0cm/9.ai
new file mode 100644
index 0000000..53b121e
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/9.ai differ
diff --git a/scripts/generated_apriltags/1.0x1.0cm/9.png b/scripts/generated_apriltags/1.0x1.0cm/9.png
new file mode 100644
index 0000000..bfc32f9
Binary files /dev/null and b/scripts/generated_apriltags/1.0x1.0cm/9.png differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/1.ai b/scripts/generated_apriltags/1.5x1.5cm/1.ai
new file mode 100644
index 0000000..0694037
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/1.ai differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/1.png b/scripts/generated_apriltags/1.5x1.5cm/1.png
new file mode 100644
index 0000000..1cfb0b3
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/1.png differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/10.ai b/scripts/generated_apriltags/1.5x1.5cm/10.ai
new file mode 100644
index 0000000..042ee0b
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/10.ai differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/10.png b/scripts/generated_apriltags/1.5x1.5cm/10.png
new file mode 100644
index 0000000..5fd0036
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/10.png differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/11.ai b/scripts/generated_apriltags/1.5x1.5cm/11.ai
new file mode 100644
index 0000000..013d3de
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/11.ai differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/11.png b/scripts/generated_apriltags/1.5x1.5cm/11.png
new file mode 100644
index 0000000..88549ae
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/11.png differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/12.ai b/scripts/generated_apriltags/1.5x1.5cm/12.ai
new file mode 100644
index 0000000..f2c6274
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/12.ai differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/12.png b/scripts/generated_apriltags/1.5x1.5cm/12.png
new file mode 100644
index 0000000..2a3512d
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/12.png differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/13.ai b/scripts/generated_apriltags/1.5x1.5cm/13.ai
new file mode 100644
index 0000000..8f90ee7
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/13.ai differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/13.png b/scripts/generated_apriltags/1.5x1.5cm/13.png
new file mode 100644
index 0000000..0165ffb
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/13.png differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/14.ai b/scripts/generated_apriltags/1.5x1.5cm/14.ai
new file mode 100644
index 0000000..474c625
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/14.ai differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/14.png b/scripts/generated_apriltags/1.5x1.5cm/14.png
new file mode 100644
index 0000000..07729f6
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/14.png differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/15.ai b/scripts/generated_apriltags/1.5x1.5cm/15.ai
new file mode 100644
index 0000000..159aa80
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/15.ai differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/15.png b/scripts/generated_apriltags/1.5x1.5cm/15.png
new file mode 100644
index 0000000..905b71e
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/15.png differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/16.ai b/scripts/generated_apriltags/1.5x1.5cm/16.ai
new file mode 100644
index 0000000..bb645a4
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/16.ai differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/16.png b/scripts/generated_apriltags/1.5x1.5cm/16.png
new file mode 100644
index 0000000..63b36a5
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/16.png differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/17.ai b/scripts/generated_apriltags/1.5x1.5cm/17.ai
new file mode 100644
index 0000000..5fb9313
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/17.ai differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/17.png b/scripts/generated_apriltags/1.5x1.5cm/17.png
new file mode 100644
index 0000000..7ff6911
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/17.png differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/18.ai b/scripts/generated_apriltags/1.5x1.5cm/18.ai
new file mode 100644
index 0000000..bf81c1e
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/18.ai differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/18.png b/scripts/generated_apriltags/1.5x1.5cm/18.png
new file mode 100644
index 0000000..fcd17f2
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/18.png differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/19.ai b/scripts/generated_apriltags/1.5x1.5cm/19.ai
new file mode 100644
index 0000000..797be26
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/19.ai differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/19.png b/scripts/generated_apriltags/1.5x1.5cm/19.png
new file mode 100644
index 0000000..bcc86f0
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/19.png differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/2.ai b/scripts/generated_apriltags/1.5x1.5cm/2.ai
new file mode 100644
index 0000000..1fb14c7
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/2.ai differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/2.png b/scripts/generated_apriltags/1.5x1.5cm/2.png
new file mode 100644
index 0000000..c26d0a6
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/2.png differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/20.ai b/scripts/generated_apriltags/1.5x1.5cm/20.ai
new file mode 100644
index 0000000..398c1ac
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/20.ai differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/20.png b/scripts/generated_apriltags/1.5x1.5cm/20.png
new file mode 100644
index 0000000..b306256
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/20.png differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/3.ai b/scripts/generated_apriltags/1.5x1.5cm/3.ai
new file mode 100644
index 0000000..2b95435
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/3.ai differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/3.png b/scripts/generated_apriltags/1.5x1.5cm/3.png
new file mode 100644
index 0000000..25bf983
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/3.png differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/4.ai b/scripts/generated_apriltags/1.5x1.5cm/4.ai
new file mode 100644
index 0000000..5d19c4c
--- /dev/null
+++ b/scripts/generated_apriltags/1.5x1.5cm/4.ai
@@ -0,0 +1,71 @@
+%PDF-1.4
+%
+1 0 obj
+<< /Type /Catalog /Pages 2 0 R >>
+endobj
+8 0 obj
+<< /Font 3 0 R /XObject 7 0 R /ExtGState 4 0 R /Pattern 5 0 R
+/Shading 6 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >>
+endobj
+11 0 obj
+<< /Type /Page /Parent 2 0 R /Resources 8 0 R
+/MediaBox [ 0 0 42.5196850394 42.5196850394 ] /Contents 9 0 R
+/Annots 10 0 R >>
+endobj
+9 0 obj
+<< /Length 12 0 R /Filter /FlateDecode >>
+stream
+xN0ENY|@UFJ8 |oZgĨjdeff:\?w
nxs7'tvӘO7l.y<(|](dIAn_܉B},*84X,X1aܸ(M6}\O?s1ŰدBRAد=LPJV
O/KPn Pa!\/QS
+uvQ
ePr;l(ccPYKh͙RI{x-CF2lmA˱1Le9"+l(c eqkCM$Ȃ ( akFym((TXCh~s*
+endstream
+endobj
+12 0 obj
+404
+endobj
+10 0 obj
+[ ]
+endobj
+3 0 obj
+<< >>
+endobj
+4 0 obj
+<< /A1 << /Type /ExtGState /CA 0 /ca 1 >> >>
+endobj
+5 0 obj
+<< >>
+endobj
+6 0 obj
+<< >>
+endobj
+7 0 obj
+<< >>
+endobj
+2 0 obj
+<< /Type /Pages /Kids [ 11 0 R ] /Count 1 >>
+endobj
+13 0 obj
+<< /Creator (Matplotlib v3.9.4, https://matplotlib.org)
+/Producer (Matplotlib pdf backend v3.9.4)
+/CreationDate (D:20260318172825+08'00') >>
+endobj
+xref
+0 14
+0000000000 65535 f
+0000000016 00000 n
+0000001013 00000 n
+0000000869 00000 n
+0000000890 00000 n
+0000000950 00000 n
+0000000971 00000 n
+0000000992 00000 n
+0000000065 00000 n
+0000000350 00000 n
+0000000849 00000 n
+0000000208 00000 n
+0000000829 00000 n
+0000001073 00000 n
+trailer
+<< /Size 14 /Root 1 0 R /Info 13 0 R >>
+startxref
+1230
+%%EOF
diff --git a/scripts/generated_apriltags/1.5x1.5cm/4.png b/scripts/generated_apriltags/1.5x1.5cm/4.png
new file mode 100644
index 0000000..d9f6973
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/4.png differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/5.ai b/scripts/generated_apriltags/1.5x1.5cm/5.ai
new file mode 100644
index 0000000..e42bd80
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/5.ai differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/5.png b/scripts/generated_apriltags/1.5x1.5cm/5.png
new file mode 100644
index 0000000..f7414ae
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/5.png differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/6.ai b/scripts/generated_apriltags/1.5x1.5cm/6.ai
new file mode 100644
index 0000000..1e79700
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/6.ai differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/6.png b/scripts/generated_apriltags/1.5x1.5cm/6.png
new file mode 100644
index 0000000..253745e
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/6.png differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/7.ai b/scripts/generated_apriltags/1.5x1.5cm/7.ai
new file mode 100644
index 0000000..2260352
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/7.ai differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/7.png b/scripts/generated_apriltags/1.5x1.5cm/7.png
new file mode 100644
index 0000000..44c2c1e
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/7.png differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/8.ai b/scripts/generated_apriltags/1.5x1.5cm/8.ai
new file mode 100644
index 0000000..6f2185a
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/8.ai differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/8.png b/scripts/generated_apriltags/1.5x1.5cm/8.png
new file mode 100644
index 0000000..963041f
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/8.png differ
diff --git a/scripts/generated_apriltags/1.5x1.5cm/9.ai b/scripts/generated_apriltags/1.5x1.5cm/9.ai
new file mode 100644
index 0000000..6bb7472
--- /dev/null
+++ b/scripts/generated_apriltags/1.5x1.5cm/9.ai
@@ -0,0 +1,72 @@
+%PDF-1.4
+%
+1 0 obj
+<< /Type /Catalog /Pages 2 0 R >>
+endobj
+8 0 obj
+<< /Font 3 0 R /XObject 7 0 R /ExtGState 4 0 R /Pattern 5 0 R
+/Shading 6 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >>
+endobj
+11 0 obj
+<< /Type /Page /Parent 2 0 R /Resources 8 0 R
+/MediaBox [ 0 0 42.5196850394 42.5196850394 ] /Contents 9 0 R
+/Annots 10 0 R >>
+endobj
+9 0 obj
+<< /Length 12 0 R /Filter /FlateDecode >>
+stream
+xj0E
+}"ٲe/.!}߯܂U2^23>h^wy0vG|X_ع!4~'Ͻ}vsіGyPro|
bDoD}Y1OW(1uKlucJ!ĈޘFÐu K0,h74$vIiJR&L!4$J0`C${
&xU[I\(_ɰ*I@RAТYDCɚ7=&d(QA%Ca'Vv9Ȃͪ1 r+˭]eHG-#6(R<uk(P%G+CW!P4Gym@Ɇ*(9
+填4Ĕ^P2WY)ɠylXS8
+endstream
+endobj
+12 0 obj
+438
+endobj
+10 0 obj
+[ ]
+endobj
+3 0 obj
+<< >>
+endobj
+4 0 obj
+<< /A1 << /Type /ExtGState /CA 0 /ca 1 >> >>
+endobj
+5 0 obj
+<< >>
+endobj
+6 0 obj
+<< >>
+endobj
+7 0 obj
+<< >>
+endobj
+2 0 obj
+<< /Type /Pages /Kids [ 11 0 R ] /Count 1 >>
+endobj
+13 0 obj
+<< /Creator (Matplotlib v3.9.4, https://matplotlib.org)
+/Producer (Matplotlib pdf backend v3.9.4)
+/CreationDate (D:20260318172825+08'00') >>
+endobj
+xref
+0 14
+0000000000 65535 f
+0000000016 00000 n
+0000001047 00000 n
+0000000903 00000 n
+0000000924 00000 n
+0000000984 00000 n
+0000001005 00000 n
+0000001026 00000 n
+0000000065 00000 n
+0000000350 00000 n
+0000000883 00000 n
+0000000208 00000 n
+0000000863 00000 n
+0000001107 00000 n
+trailer
+<< /Size 14 /Root 1 0 R /Info 13 0 R >>
+startxref
+1264
+%%EOF
diff --git a/scripts/generated_apriltags/1.5x1.5cm/9.png b/scripts/generated_apriltags/1.5x1.5cm/9.png
new file mode 100644
index 0000000..b716737
Binary files /dev/null and b/scripts/generated_apriltags/1.5x1.5cm/9.png differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/1.ai b/scripts/generated_apriltags/2.0x2.0cm/1.ai
new file mode 100644
index 0000000..c53dd0e
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/1.ai differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/1.png b/scripts/generated_apriltags/2.0x2.0cm/1.png
new file mode 100644
index 0000000..c57ce4a
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/1.png differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/10.ai b/scripts/generated_apriltags/2.0x2.0cm/10.ai
new file mode 100644
index 0000000..0a9a63f
--- /dev/null
+++ b/scripts/generated_apriltags/2.0x2.0cm/10.ai
@@ -0,0 +1,72 @@
+%PDF-1.4
+%
+1 0 obj
+<< /Type /Catalog /Pages 2 0 R >>
+endobj
+8 0 obj
+<< /Font 3 0 R /XObject 7 0 R /ExtGState 4 0 R /Pattern 5 0 R
+/Shading 6 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >>
+endobj
+11 0 obj
+<< /Type /Page /Parent 2 0 R /Resources 8 0 R
+/MediaBox [ 0 0 56.6929133858 56.6929133858 ] /Contents 9 0 R
+/Annots 10 0 R >>
+endobj
+9 0 obj
+<< /Length 12 0 R /Filter /FlateDecode >>
+stream
+xMj0F:NH%;@m=@HBThE"،1m}ow[}V{mh[;0^vYՍӯGO|csZ+\Z+{>)B^+L<_3N*S-Ƈ{wMe`ơm+ԯMk.i؍L2F:r,!UЏ).i(}(5Aa%I:3
:4r,v϶*7 &\ҰtJzRF:r,aPHVRe4b%
qKE
+th(O$˙93mJHۛy thXBt8E6oF*
+thX)U2
:4r,*2ֽ\/Nx
+endstream
+endobj
+12 0 obj
+407
+endobj
+10 0 obj
+[ ]
+endobj
+3 0 obj
+<< >>
+endobj
+4 0 obj
+<< /A1 << /Type /ExtGState /CA 0 /ca 1 >> >>
+endobj
+5 0 obj
+<< >>
+endobj
+6 0 obj
+<< >>
+endobj
+7 0 obj
+<< >>
+endobj
+2 0 obj
+<< /Type /Pages /Kids [ 11 0 R ] /Count 1 >>
+endobj
+13 0 obj
+<< /Creator (Matplotlib v3.9.4, https://matplotlib.org)
+/Producer (Matplotlib pdf backend v3.9.4)
+/CreationDate (D:20260318172825+08'00') >>
+endobj
+xref
+0 14
+0000000000 65535 f
+0000000016 00000 n
+0000001016 00000 n
+0000000872 00000 n
+0000000893 00000 n
+0000000953 00000 n
+0000000974 00000 n
+0000000995 00000 n
+0000000065 00000 n
+0000000350 00000 n
+0000000852 00000 n
+0000000208 00000 n
+0000000832 00000 n
+0000001076 00000 n
+trailer
+<< /Size 14 /Root 1 0 R /Info 13 0 R >>
+startxref
+1233
+%%EOF
diff --git a/scripts/generated_apriltags/2.0x2.0cm/10.png b/scripts/generated_apriltags/2.0x2.0cm/10.png
new file mode 100644
index 0000000..003679f
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/10.png differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/11.ai b/scripts/generated_apriltags/2.0x2.0cm/11.ai
new file mode 100644
index 0000000..21ac18b
--- /dev/null
+++ b/scripts/generated_apriltags/2.0x2.0cm/11.ai
@@ -0,0 +1,74 @@
+%PDF-1.4
+%
+1 0 obj
+<< /Type /Catalog /Pages 2 0 R >>
+endobj
+8 0 obj
+<< /Font 3 0 R /XObject 7 0 R /ExtGState 4 0 R /Pattern 5 0 R
+/Shading 6 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >>
+endobj
+11 0 obj
+<< /Type /Page /Parent 2 0 R /Resources 8 0 R
+/MediaBox [ 0 0 56.6929133858 56.6929133858 ] /Contents 9 0 R
+/Annots 10 0 R >>
+endobj
+9 0 obj
+<< /Length 12 0 R /Filter /FlateDecode >>
+stream
+xKj0@:NH%~ 붋 @{:)<yQlFx峺~o[{dVL;iJB}/V_[l?&׃ow3^y[A1/W_(kc˧l
+717Qe%ڃPP6(&wJg
+}q!5
C)6]r)
+drlzܦn[C~(6AQqa&J+Tt*(𪝮LXY)(lAQ:IzOAaJFTl+T :Mi(@Q(m$cOeBi#+涠Si@o*ֻ
+dPr:6ȠX0wA%0*4*Uz dr,tXEڙ>X(lAɱR PD??kCh
+endstream
+endobj
+12 0 obj
+434
+endobj
+10 0 obj
+[ ]
+endobj
+3 0 obj
+<< >>
+endobj
+4 0 obj
+<< /A1 << /Type /ExtGState /CA 0 /ca 1 >> >>
+endobj
+5 0 obj
+<< >>
+endobj
+6 0 obj
+<< >>
+endobj
+7 0 obj
+<< >>
+endobj
+2 0 obj
+<< /Type /Pages /Kids [ 11 0 R ] /Count 1 >>
+endobj
+13 0 obj
+<< /Creator (Matplotlib v3.9.4, https://matplotlib.org)
+/Producer (Matplotlib pdf backend v3.9.4)
+/CreationDate (D:20260318172825+08'00') >>
+endobj
+xref
+0 14
+0000000000 65535 f
+0000000016 00000 n
+0000001043 00000 n
+0000000899 00000 n
+0000000920 00000 n
+0000000980 00000 n
+0000001001 00000 n
+0000001022 00000 n
+0000000065 00000 n
+0000000350 00000 n
+0000000879 00000 n
+0000000208 00000 n
+0000000859 00000 n
+0000001103 00000 n
+trailer
+<< /Size 14 /Root 1 0 R /Info 13 0 R >>
+startxref
+1260
+%%EOF
diff --git a/scripts/generated_apriltags/2.0x2.0cm/11.png b/scripts/generated_apriltags/2.0x2.0cm/11.png
new file mode 100644
index 0000000..0f9ea8f
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/11.png differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/12.ai b/scripts/generated_apriltags/2.0x2.0cm/12.ai
new file mode 100644
index 0000000..e2a7590
--- /dev/null
+++ b/scripts/generated_apriltags/2.0x2.0cm/12.ai
@@ -0,0 +1,71 @@
+%PDF-1.4
+%
+1 0 obj
+<< /Type /Catalog /Pages 2 0 R >>
+endobj
+8 0 obj
+<< /Font 3 0 R /XObject 7 0 R /ExtGState 4 0 R /Pattern 5 0 R
+/Shading 6 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >>
+endobj
+11 0 obj
+<< /Type /Page /Parent 2 0 R /Resources 8 0 R
+/MediaBox [ 0 0 56.6929133858 56.6929133858 ] /Contents 9 0 R
+/Annots 10 0 R >>
+endobj
+9 0 obj
+<< /Length 12 0 R /Filter /FlateDecode >>
+stream
+xj0E
+}"Y/{dv#D6~^iQlF{t;Yo7{R+6N}j[?14
~k^8zTZ(ԨDm]M`/ZhOpTkT{!H
b\61Ŷ~2iOl`R.Ņ0Ry6 刾؉tlZʱTdJ4b"i2Ib'SPBˎL
urlUv/@)iBIb{2$vlRJ'S8i4īQ/2% NDɆ:96*р<Ӭmp#~ٱ)(۠c#(~5D秛&(fd
urltd&"??B
+endstream
+endobj
+12 0 obj
+416
+endobj
+10 0 obj
+[ ]
+endobj
+3 0 obj
+<< >>
+endobj
+4 0 obj
+<< /A1 << /Type /ExtGState /CA 0 /ca 1 >> >>
+endobj
+5 0 obj
+<< >>
+endobj
+6 0 obj
+<< >>
+endobj
+7 0 obj
+<< >>
+endobj
+2 0 obj
+<< /Type /Pages /Kids [ 11 0 R ] /Count 1 >>
+endobj
+13 0 obj
+<< /Creator (Matplotlib v3.9.4, https://matplotlib.org)
+/Producer (Matplotlib pdf backend v3.9.4)
+/CreationDate (D:20260318172825+08'00') >>
+endobj
+xref
+0 14
+0000000000 65535 f
+0000000016 00000 n
+0000001025 00000 n
+0000000881 00000 n
+0000000902 00000 n
+0000000962 00000 n
+0000000983 00000 n
+0000001004 00000 n
+0000000065 00000 n
+0000000350 00000 n
+0000000861 00000 n
+0000000208 00000 n
+0000000841 00000 n
+0000001085 00000 n
+trailer
+<< /Size 14 /Root 1 0 R /Info 13 0 R >>
+startxref
+1242
+%%EOF
diff --git a/scripts/generated_apriltags/2.0x2.0cm/12.png b/scripts/generated_apriltags/2.0x2.0cm/12.png
new file mode 100644
index 0000000..a204de0
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/12.png differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/13.ai b/scripts/generated_apriltags/2.0x2.0cm/13.ai
new file mode 100644
index 0000000..fe231d8
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/13.ai differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/13.png b/scripts/generated_apriltags/2.0x2.0cm/13.png
new file mode 100644
index 0000000..bc328ef
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/13.png differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/14.ai b/scripts/generated_apriltags/2.0x2.0cm/14.ai
new file mode 100644
index 0000000..3b74021
--- /dev/null
+++ b/scripts/generated_apriltags/2.0x2.0cm/14.ai
@@ -0,0 +1,73 @@
+%PDF-1.4
+%
+1 0 obj
+<< /Type /Catalog /Pages 2 0 R >>
+endobj
+8 0 obj
+<< /Font 3 0 R /XObject 7 0 R /ExtGState 4 0 R /Pattern 5 0 R
+/Shading 6 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >>
+endobj
+11 0 obj
+<< /Type /Page /Parent 2 0 R /Resources 8 0 R
+/MediaBox [ 0 0 56.6929133858 56.6929133858 ] /Contents 9 0 R
+/Annots 10 0 R >>
+endobj
+9 0 obj
+<< /Length 12 0 R /Filter /FlateDecode >>
+stream
+xKj0@:NH%~ 붋 @{:)<yQlF{Ќd~o[{dV2L;iKB}/F_[l?&׃oql-Fb~͘`/Ʊ16盘2AbjcGzN@b(;%i>8Аsw}6)
+dr!um?s(6AQvTP;MzFb-ZܡPH-Z/JɠXiwSn3
2(9:,6ȠX)TYK(4؟#H
+endstream
+endobj
+12 0 obj
+448
+endobj
+10 0 obj
+[ ]
+endobj
+3 0 obj
+<< >>
+endobj
+4 0 obj
+<< /A1 << /Type /ExtGState /CA 0 /ca 1 >> >>
+endobj
+5 0 obj
+<< >>
+endobj
+6 0 obj
+<< >>
+endobj
+7 0 obj
+<< >>
+endobj
+2 0 obj
+<< /Type /Pages /Kids [ 11 0 R ] /Count 1 >>
+endobj
+13 0 obj
+<< /Creator (Matplotlib v3.9.4, https://matplotlib.org)
+/Producer (Matplotlib pdf backend v3.9.4)
+/CreationDate (D:20260318172825+08'00') >>
+endobj
+xref
+0 14
+0000000000 65535 f
+0000000016 00000 n
+0000001057 00000 n
+0000000913 00000 n
+0000000934 00000 n
+0000000994 00000 n
+0000001015 00000 n
+0000001036 00000 n
+0000000065 00000 n
+0000000350 00000 n
+0000000893 00000 n
+0000000208 00000 n
+0000000873 00000 n
+0000001117 00000 n
+trailer
+<< /Size 14 /Root 1 0 R /Info 13 0 R >>
+startxref
+1274
+%%EOF
diff --git a/scripts/generated_apriltags/2.0x2.0cm/14.png b/scripts/generated_apriltags/2.0x2.0cm/14.png
new file mode 100644
index 0000000..f9ecbe2
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/14.png differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/15.ai b/scripts/generated_apriltags/2.0x2.0cm/15.ai
new file mode 100644
index 0000000..2bdfe27
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/15.ai differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/15.png b/scripts/generated_apriltags/2.0x2.0cm/15.png
new file mode 100644
index 0000000..44e982b
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/15.png differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/16.ai b/scripts/generated_apriltags/2.0x2.0cm/16.ai
new file mode 100644
index 0000000..b3a7505
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/16.ai differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/16.png b/scripts/generated_apriltags/2.0x2.0cm/16.png
new file mode 100644
index 0000000..6c779e7
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/16.png differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/17.ai b/scripts/generated_apriltags/2.0x2.0cm/17.ai
new file mode 100644
index 0000000..e2e2e1b
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/17.ai differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/17.png b/scripts/generated_apriltags/2.0x2.0cm/17.png
new file mode 100644
index 0000000..4b28c46
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/17.png differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/18.ai b/scripts/generated_apriltags/2.0x2.0cm/18.ai
new file mode 100644
index 0000000..56b817d
--- /dev/null
+++ b/scripts/generated_apriltags/2.0x2.0cm/18.ai
@@ -0,0 +1,73 @@
+%PDF-1.4
+%
+1 0 obj
+<< /Type /Catalog /Pages 2 0 R >>
+endobj
+8 0 obj
+<< /Font 3 0 R /XObject 7 0 R /ExtGState 4 0 R /Pattern 5 0 R
+/Shading 6 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >>
+endobj
+11 0 obj
+<< /Type /Page /Parent 2 0 R /Resources 8 0 R
+/MediaBox [ 0 0 56.6929133858 56.6929133858 ] /Contents 9 0 R
+/Annots 10 0 R >>
+endobj
+9 0 obj
+<< /Length 12 0 R /Filter /FlateDecode >>
+stream
+xKj0EZV Kȸ'D6nNJ<(X?ޕvwOޫOm_L&
c5g_zߏxmNsuV2Ԏ3{)8W*t~@GglS۞T!D/Y9glY~04$\uڮLc]B?SJPQq!҉FƵAeƇb7[tj(\B@a
+MyB"ᚆ25Qȸ:3JP˱Tƫ˂&uKq4IOu("(lPciLAa
+ALI9/T,K(mB-BB@w$XPQv Aa
+eӱ)(lPc! oCO x
+endstream
+endobj
+12 0 obj
+412
+endobj
+10 0 obj
+[ ]
+endobj
+3 0 obj
+<< >>
+endobj
+4 0 obj
+<< /A1 << /Type /ExtGState /CA 0 /ca 1 >> >>
+endobj
+5 0 obj
+<< >>
+endobj
+6 0 obj
+<< >>
+endobj
+7 0 obj
+<< >>
+endobj
+2 0 obj
+<< /Type /Pages /Kids [ 11 0 R ] /Count 1 >>
+endobj
+13 0 obj
+<< /Creator (Matplotlib v3.9.4, https://matplotlib.org)
+/Producer (Matplotlib pdf backend v3.9.4)
+/CreationDate (D:20260318172825+08'00') >>
+endobj
+xref
+0 14
+0000000000 65535 f
+0000000016 00000 n
+0000001021 00000 n
+0000000877 00000 n
+0000000898 00000 n
+0000000958 00000 n
+0000000979 00000 n
+0000001000 00000 n
+0000000065 00000 n
+0000000350 00000 n
+0000000857 00000 n
+0000000208 00000 n
+0000000837 00000 n
+0000001081 00000 n
+trailer
+<< /Size 14 /Root 1 0 R /Info 13 0 R >>
+startxref
+1238
+%%EOF
diff --git a/scripts/generated_apriltags/2.0x2.0cm/18.png b/scripts/generated_apriltags/2.0x2.0cm/18.png
new file mode 100644
index 0000000..6acba4c
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/18.png differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/19.ai b/scripts/generated_apriltags/2.0x2.0cm/19.ai
new file mode 100644
index 0000000..36987f5
--- /dev/null
+++ b/scripts/generated_apriltags/2.0x2.0cm/19.ai
@@ -0,0 +1,71 @@
+%PDF-1.4
+%
+1 0 obj
+<< /Type /Catalog /Pages 2 0 R >>
+endobj
+8 0 obj
+<< /Font 3 0 R /XObject 7 0 R /ExtGState 4 0 R /Pattern 5 0 R
+/Shading 6 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >>
+endobj
+11 0 obj
+<< /Type /Page /Parent 2 0 R /Resources 8 0 R
+/MediaBox [ 0 0 56.6929133858 56.6929133858 ] /Contents 9 0 R
+/Annots 10 0 R >>
+endobj
+9 0 obj
+<< /Length 12 0 R /Filter /FlateDecode >>
+stream
+xj0E
+}"Y/{dv#D6~^iQl=:̌V=frz>Ly?bCn~=zSq}]:R/AW^(ԹRѤ::c21
wGzP岱69,Ic~bC4rv).BEPA)Gu1qmAi
rl4SFgNbYs t#+vl(cNSSdC*#4ëiSRlmAʱ/,ѹ9i+b5V$J6Aȱ`SSdC*"uTi2'#+ EPR8ر]SPA)FJDWȊ|% HBKDb~<@
+endstream
+endobj
+12 0 obj
+424
+endobj
+10 0 obj
+[ ]
+endobj
+3 0 obj
+<< >>
+endobj
+4 0 obj
+<< /A1 << /Type /ExtGState /CA 0 /ca 1 >> >>
+endobj
+5 0 obj
+<< >>
+endobj
+6 0 obj
+<< >>
+endobj
+7 0 obj
+<< >>
+endobj
+2 0 obj
+<< /Type /Pages /Kids [ 11 0 R ] /Count 1 >>
+endobj
+13 0 obj
+<< /Creator (Matplotlib v3.9.4, https://matplotlib.org)
+/Producer (Matplotlib pdf backend v3.9.4)
+/CreationDate (D:20260318172825+08'00') >>
+endobj
+xref
+0 14
+0000000000 65535 f
+0000000016 00000 n
+0000001033 00000 n
+0000000889 00000 n
+0000000910 00000 n
+0000000970 00000 n
+0000000991 00000 n
+0000001012 00000 n
+0000000065 00000 n
+0000000350 00000 n
+0000000869 00000 n
+0000000208 00000 n
+0000000849 00000 n
+0000001093 00000 n
+trailer
+<< /Size 14 /Root 1 0 R /Info 13 0 R >>
+startxref
+1250
+%%EOF
diff --git a/scripts/generated_apriltags/2.0x2.0cm/19.png b/scripts/generated_apriltags/2.0x2.0cm/19.png
new file mode 100644
index 0000000..54ce8a4
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/19.png differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/2.ai b/scripts/generated_apriltags/2.0x2.0cm/2.ai
new file mode 100644
index 0000000..226779e
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/2.ai differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/2.png b/scripts/generated_apriltags/2.0x2.0cm/2.png
new file mode 100644
index 0000000..6d7fa0f
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/2.png differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/20.ai b/scripts/generated_apriltags/2.0x2.0cm/20.ai
new file mode 100644
index 0000000..27d7e60
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/20.ai differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/20.png b/scripts/generated_apriltags/2.0x2.0cm/20.png
new file mode 100644
index 0000000..86b0b50
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/20.png differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/3.ai b/scripts/generated_apriltags/2.0x2.0cm/3.ai
new file mode 100644
index 0000000..f9447e7
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/3.ai differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/3.png b/scripts/generated_apriltags/2.0x2.0cm/3.png
new file mode 100644
index 0000000..1618672
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/3.png differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/4.ai b/scripts/generated_apriltags/2.0x2.0cm/4.ai
new file mode 100644
index 0000000..fabe51d
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/4.ai differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/4.png b/scripts/generated_apriltags/2.0x2.0cm/4.png
new file mode 100644
index 0000000..a01597e
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/4.png differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/5.ai b/scripts/generated_apriltags/2.0x2.0cm/5.ai
new file mode 100644
index 0000000..ed47b86
--- /dev/null
+++ b/scripts/generated_apriltags/2.0x2.0cm/5.ai
@@ -0,0 +1,73 @@
+%PDF-1.4
+%
+1 0 obj
+<< /Type /Catalog /Pages 2 0 R >>
+endobj
+8 0 obj
+<< /Font 3 0 R /XObject 7 0 R /ExtGState 4 0 R /Pattern 5 0 R
+/Shading 6 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >>
+endobj
+11 0 obj
+<< /Type /Page /Parent 2 0 R /Resources 8 0 R
+/MediaBox [ 0 0 56.6929133858 56.6929133858 ] /Contents 9 0 R
+/Annots 10 0 R >>
+endobj
+9 0 obj
+<< /Length 12 0 R /Filter /FlateDecode >>
+stream
+xMj0F:N K%@m=@HBThE"$H{1߷ͭ{RG^}j?!8~knn~=OnN{uR=)B+L<ߠ3mO*ZV.:݉^
+V1I}bG`HC5
}g}7F*r:.D:QA%zcm?ݬ-4:&
X'i*
+TXB3~Vh;."ᚆxL(ݨ(6Rc)'>ӳL'J#H8ϝPHZgmAxyW=
*Tr,2
*Tr,*#i5
K,-d*6X)-d*6Rc!(-93E*
+TXBZ&O
+endstream
+endobj
+12 0 obj
+425
+endobj
+10 0 obj
+[ ]
+endobj
+3 0 obj
+<< >>
+endobj
+4 0 obj
+<< /A1 << /Type /ExtGState /CA 0 /ca 1 >> >>
+endobj
+5 0 obj
+<< >>
+endobj
+6 0 obj
+<< >>
+endobj
+7 0 obj
+<< >>
+endobj
+2 0 obj
+<< /Type /Pages /Kids [ 11 0 R ] /Count 1 >>
+endobj
+13 0 obj
+<< /Creator (Matplotlib v3.9.4, https://matplotlib.org)
+/Producer (Matplotlib pdf backend v3.9.4)
+/CreationDate (D:20260318172825+08'00') >>
+endobj
+xref
+0 14
+0000000000 65535 f
+0000000016 00000 n
+0000001034 00000 n
+0000000890 00000 n
+0000000911 00000 n
+0000000971 00000 n
+0000000992 00000 n
+0000001013 00000 n
+0000000065 00000 n
+0000000350 00000 n
+0000000870 00000 n
+0000000208 00000 n
+0000000850 00000 n
+0000001094 00000 n
+trailer
+<< /Size 14 /Root 1 0 R /Info 13 0 R >>
+startxref
+1251
+%%EOF
diff --git a/scripts/generated_apriltags/2.0x2.0cm/5.png b/scripts/generated_apriltags/2.0x2.0cm/5.png
new file mode 100644
index 0000000..cca1396
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/5.png differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/6.ai b/scripts/generated_apriltags/2.0x2.0cm/6.ai
new file mode 100644
index 0000000..d2f7a14
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/6.ai differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/6.png b/scripts/generated_apriltags/2.0x2.0cm/6.png
new file mode 100644
index 0000000..466bef4
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/6.png differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/7.ai b/scripts/generated_apriltags/2.0x2.0cm/7.ai
new file mode 100644
index 0000000..8f47aa2
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/7.ai differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/7.png b/scripts/generated_apriltags/2.0x2.0cm/7.png
new file mode 100644
index 0000000..d0c45c1
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/7.png differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/8.ai b/scripts/generated_apriltags/2.0x2.0cm/8.ai
new file mode 100644
index 0000000..0f4cf84
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/8.ai differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/8.png b/scripts/generated_apriltags/2.0x2.0cm/8.png
new file mode 100644
index 0000000..77c4af0
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/8.png differ
diff --git a/scripts/generated_apriltags/2.0x2.0cm/9.ai b/scripts/generated_apriltags/2.0x2.0cm/9.ai
new file mode 100644
index 0000000..d10aaed
--- /dev/null
+++ b/scripts/generated_apriltags/2.0x2.0cm/9.ai
@@ -0,0 +1,75 @@
+%PDF-1.4
+%
+1 0 obj
+<< /Type /Catalog /Pages 2 0 R >>
+endobj
+8 0 obj
+<< /Font 3 0 R /XObject 7 0 R /ExtGState 4 0 R /Pattern 5 0 R
+/Shading 6 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >>
+endobj
+11 0 obj
+<< /Type /Page /Parent 2 0 R /Resources 8 0 R
+/MediaBox [ 0 0 56.6929133858 56.6929133858 ] /Contents 9 0 R
+/Annots 10 0 R >>
+endobj
+9 0 obj
+<< /Length 12 0 R /Filter /FlateDecode >>
+stream
+xKj0@:NHϲȺ'D6^NJ/ZCmAQSԙTA%RJ@aJ2{X;P cr8D7Lo*;LbHX
+rfJɠX\;Zfʕ#v#N7R\=P L6:k
+
+dPr,*Wd
+
g~z
+endstream
+endobj
+12 0 obj
+442
+endobj
+10 0 obj
+[ ]
+endobj
+3 0 obj
+<< >>
+endobj
+4 0 obj
+<< /A1 << /Type /ExtGState /CA 0 /ca 1 >> >>
+endobj
+5 0 obj
+<< >>
+endobj
+6 0 obj
+<< >>
+endobj
+7 0 obj
+<< >>
+endobj
+2 0 obj
+<< /Type /Pages /Kids [ 11 0 R ] /Count 1 >>
+endobj
+13 0 obj
+<< /Creator (Matplotlib v3.9.4, https://matplotlib.org)
+/Producer (Matplotlib pdf backend v3.9.4)
+/CreationDate (D:20260318172825+08'00') >>
+endobj
+xref
+0 14
+0000000000 65535 f
+0000000016 00000 n
+0000001051 00000 n
+0000000907 00000 n
+0000000928 00000 n
+0000000988 00000 n
+0000001009 00000 n
+0000001030 00000 n
+0000000065 00000 n
+0000000350 00000 n
+0000000887 00000 n
+0000000208 00000 n
+0000000867 00000 n
+0000001111 00000 n
+trailer
+<< /Size 14 /Root 1 0 R /Info 13 0 R >>
+startxref
+1268
+%%EOF
diff --git a/scripts/generated_apriltags/2.0x2.0cm/9.png b/scripts/generated_apriltags/2.0x2.0cm/9.png
new file mode 100644
index 0000000..cfee9db
Binary files /dev/null and b/scripts/generated_apriltags/2.0x2.0cm/9.png differ
diff --git a/ui/__pycache__/__init__.cpython-39.pyc b/ui/__pycache__/__init__.cpython-39.pyc
index 39c7159..d6bc60f 100644
Binary files a/ui/__pycache__/__init__.cpython-39.pyc and b/ui/__pycache__/__init__.cpython-39.pyc differ
diff --git a/ui/__pycache__/app.cpython-39.pyc b/ui/__pycache__/app.cpython-39.pyc
index 501cdc5..84c281f 100644
Binary files a/ui/__pycache__/app.cpython-39.pyc and b/ui/__pycache__/app.cpython-39.pyc differ
diff --git a/ui/__pycache__/camera_page.cpython-39.pyc b/ui/__pycache__/camera_page.cpython-39.pyc
new file mode 100644
index 0000000..d72dbbe
Binary files /dev/null and b/ui/__pycache__/camera_page.cpython-39.pyc differ
diff --git a/ui/__pycache__/plot_page.cpython-39.pyc b/ui/__pycache__/plot_page.cpython-39.pyc
new file mode 100644
index 0000000..185db7b
Binary files /dev/null and b/ui/__pycache__/plot_page.cpython-39.pyc differ
diff --git a/ui/__pycache__/rgb_image_page.cpython-39.pyc b/ui/__pycache__/rgb_image_page.cpython-39.pyc
new file mode 100644
index 0000000..b309d48
Binary files /dev/null and b/ui/__pycache__/rgb_image_page.cpython-39.pyc differ
diff --git a/ui/__pycache__/run_action.cpython-39.pyc b/ui/__pycache__/run_action.cpython-39.pyc
index a6249a1..6c5a8ae 100644
Binary files a/ui/__pycache__/run_action.cpython-39.pyc and b/ui/__pycache__/run_action.cpython-39.pyc differ
diff --git a/ui/app.py b/ui/app.py
index 50e24b0..4ba59c3 100644
--- a/ui/app.py
+++ b/ui/app.py
@@ -1,15 +1,10 @@
import sys
import time
-from collections import deque
from pathlib import Path
-from typing import Deque, Dict, Optional, Tuple
+from typing import Dict, Optional, Tuple
import grpc
-import numpy as np
-from PyQt5 import QtCore, QtWidgets
-from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
-from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
-from matplotlib.figure import Figure
+from PyQt5 import QtCore, QtGui, QtWidgets
try:
from yourdfpy import URDF
except Exception: # pragma: no cover - fallback if yourdfpy not available
@@ -20,6 +15,9 @@ from clients._path_setup import ensure_paths
ensure_paths()
from google.protobuf import timestamp_pb2
+from ui.camera_page import CameraPage
+from ui.plot_page import PlotPage
+from ui.rgb_image_page import RGBImagePage
from cmvr.api import humanoid_robot_command_pb2 as pb
from cmvr.api import humanoid_robot_service_pb2_grpc as rpc
@@ -31,6 +29,7 @@ DEFAULT_JOINT_ORDER = [
"R_ELBOW_R", "R_WRIST_P", "R_WRIST_Y", "R_WRIST_R",
"HEAD_P", "HEAD_Y", "HEAD_R", "WAIST_Y", "WAIST_R",
]
+STATUS_PREVIEW_JOINTS = 4
URDF_PATH = Path(__file__).resolve().parents[1] / "robot_model" / "xiaoyan_description" / "dual_arm.urdf"
@@ -177,6 +176,9 @@ class MainWindow(QtWidgets.QMainWindow):
self.urdf_error: Optional[str] = None
self.joint_names: list[str] = DEFAULT_JOINT_ORDER.copy()
self.link_names: list[str] = []
+ self.camera_page: Optional[CameraPage] = None
+ self.plot_page: Optional[PlotPage] = None
+ self.rgb_image_page: Optional[RGBImagePage] = None
self._warned_no_joint_match = False
self._load_urdf()
@@ -184,6 +186,14 @@ class MainWindow(QtWidgets.QMainWindow):
central = self._build_center_panel()
self.setCentralWidget(central)
self._build_docks()
+ if self.camera_page is not None:
+ self.host_edit.textChanged.connect(self.camera_page.refresh_address_hint)
+ self.port_edit.valueChanged.connect(self.camera_page.refresh_address_hint)
+ self.camera_page.refresh_address_hint()
+ if self.rgb_image_page is not None:
+ self.host_edit.textChanged.connect(self.rgb_image_page.refresh_address_hint)
+ self.port_edit.valueChanged.connect(self.rgb_image_page.refresh_address_hint)
+ self.rgb_image_page.refresh_address_hint()
self.statusBar().showMessage("Ready")
def _load_urdf(self) -> None:
@@ -223,7 +233,12 @@ class MainWindow(QtWidgets.QMainWindow):
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
self.center_tabs = QtWidgets.QTabWidget()
- self.center_tabs.addTab(self._build_live_panel(), "Plot")
+ self.plot_page = PlotPage(self.joint_names, parent=self)
+ self.center_tabs.addTab(self.plot_page, "Plot")
+ self.camera_page = CameraPage(address_provider=self._grpc_address, parent=self)
+ self.center_tabs.addTab(self.camera_page, "Camera")
+ self.rgb_image_page = RGBImagePage(address_provider=self._grpc_address, parent=self)
+ self.center_tabs.addTab(self.rgb_image_page, "RGB Image")
self.center_tabs.setCurrentIndex(0)
self.center_tabs.currentChanged.connect(self._on_center_tab_changed)
layout.addWidget(self.center_tabs)
@@ -242,13 +257,6 @@ class MainWindow(QtWidgets.QMainWindow):
status.setFeatures(QtWidgets.QDockWidget.AllDockWidgetFeatures)
self.addDockWidget(QtCore.Qt.RightDockWidgetArea, status)
- log_dock = QtWidgets.QDockWidget("Log")
- log_dock.setWidget(self._build_log_panel())
- log_dock.setMinimumHeight(140)
- log_dock.setMaximumHeight(220)
- log_dock.setFeatures(QtWidgets.QDockWidget.AllDockWidgetFeatures)
- self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, log_dock)
-
def _build_status_panel(self) -> QtWidgets.QWidget:
panel = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout(panel)
@@ -365,25 +373,6 @@ class MainWindow(QtWidgets.QMainWindow):
return form
- def _build_log(self) -> QtWidgets.QWidget:
- self.log = QtWidgets.QTextEdit()
- self.log.setReadOnly(True)
- return self.log
-
- def _build_log_panel(self) -> QtWidgets.QWidget:
- panel = QtWidgets.QWidget()
- layout = QtWidgets.QVBoxLayout(panel)
- header = QtWidgets.QHBoxLayout()
- header.addWidget(QtWidgets.QLabel("Runtime logs"))
- header.addStretch(1)
- log_widget = self._build_log()
- self.clear_log_btn = QtWidgets.QPushButton("Clear")
- self.clear_log_btn.clicked.connect(log_widget.clear)
- header.addWidget(self.clear_log_btn)
- layout.addLayout(header)
- layout.addWidget(log_widget)
- return panel
-
def _build_pose_panel(self) -> QtWidgets.QGroupBox:
group = QtWidgets.QGroupBox("GetPose")
layout = QtWidgets.QVBoxLayout(group)
@@ -418,38 +407,6 @@ class MainWindow(QtWidgets.QMainWindow):
layout.addLayout(grid)
return group
- def _build_live_panel(self) -> QtWidgets.QWidget:
- tab = QtWidgets.QWidget()
- layout = QtWidgets.QVBoxLayout(tab)
-
- layout.setContentsMargins(0, 0, 0, 0)
- layout.setSpacing(4)
-
- self.figure = Figure(figsize=(5, 4))
- self.canvas = FigureCanvas(self.figure)
- self.ax_pos = self.figure.add_subplot(2, 1, 1)
- self.ax_vel = self.figure.add_subplot(2, 1, 2)
- self.ax_pos.set_title("Position (rad)")
- self.ax_vel.set_title("Velocity (rad/s)")
- self.ax_pos.grid(True, alpha=0.3)
- self.ax_vel.grid(True, alpha=0.3)
- self.pos_lines: Dict[str, object] = {}
- self.vel_lines: Dict[str, object] = {}
-
- self.time_hist: Deque[float] = deque(maxlen=300)
- self._last_plot_time: Optional[float] = None
- self.pos_hist: Dict[str, Deque[float]] = {name: deque(maxlen=300) for name in self.joint_names}
- self.vel_hist: Dict[str, Deque[float]] = {name: deque(maxlen=300) for name in self.joint_names}
- self._plot_start = time.time()
-
- self.canvas.setSizePolicy(
- QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding
- )
- self.toolbar = NavigationToolbar(self.canvas, self)
- layout.addWidget(self.toolbar)
- layout.addWidget(self.canvas)
- return tab
-
def _build_table(self) -> QtWidgets.QWidget:
self.table = QtWidgets.QTableWidget(len(self.joint_names), 4)
self.table.setHorizontalHeaderLabels(
@@ -497,6 +454,9 @@ class MainWindow(QtWidgets.QMainWindow):
if hasattr(self, "log") and self.log is not None:
self.log.append(text.rstrip())
+ def _grpc_address(self) -> str:
+ return f"{self.host_edit.text().strip()}:{self.port_edit.value()}"
+
def _base_args(self) -> list[str]:
return [
"-m",
@@ -561,8 +521,7 @@ class MainWindow(QtWidgets.QMainWindow):
def _on_stop_pose(self) -> None:
if self._joint_worker:
self._append_log("[info] Stopping JointState...")
- self._joint_worker.stop()
- self._joint_worker = None
+ self._stop_jointstate_worker()
def _on_torque_on(self) -> None:
args = self._base_args() + ["torque_on"]
@@ -588,67 +547,6 @@ class MainWindow(QtWidgets.QMainWindow):
self._last_data = data
self._render_joint_table_and_plot()
- def _append_plot(self, name: str, pos: float, vel: float) -> None:
- t = time.time() - self._plot_start
- if self._last_plot_time is None or t > self._last_plot_time:
- self.time_hist.append(t)
- self._last_plot_time = t
- self.pos_hist[name].append(pos)
- self.vel_hist[name].append(vel)
-
- def _refresh_plot(self, selected: set) -> None:
- for name in list(self.pos_lines.keys()):
- if name not in selected:
- self.pos_lines[name].remove()
- self.vel_lines[name].remove()
- del self.pos_lines[name]
- del self.vel_lines[name]
-
- for name in selected:
- if name not in self.pos_lines:
- (pos_line,) = self.ax_pos.plot([], [], label=name)
- (vel_line,) = self.ax_vel.plot([], [], label=name)
- self.pos_lines[name] = pos_line
- self.vel_lines[name] = vel_line
- x_data = list(self.time_hist)
- y_pos_full = list(self.pos_hist[name])
- y_vel_full = list(self.vel_hist[name])
- n_pos = min(len(x_data), len(y_pos_full))
- n_vel = min(len(x_data), len(y_vel_full))
- if n_pos:
- self.pos_lines[name].set_data(x_data[-n_pos:], y_pos_full[-n_pos:])
- else:
- self.pos_lines[name].set_data([], [])
- if n_vel:
- self.vel_lines[name].set_data(x_data[-n_vel:], y_vel_full[-n_vel:])
- else:
- self.vel_lines[name].set_data([], [])
-
- if len(self.time_hist) > 1 and selected:
- self.ax_pos.set_xlim(self.time_hist[0], self.time_hist[-1])
- self.ax_vel.set_xlim(self.time_hist[0], self.time_hist[-1])
- self.ax_pos.relim()
- self.ax_pos.autoscale_view(scalex=False, scaley=True)
- self.ax_vel.relim()
- self.ax_vel.autoscale_view(scalex=False, scaley=True)
- if selected:
- self.ax_pos.legend(loc="upper right", fontsize=8, ncol=2)
- self.ax_vel.legend(loc="upper right", fontsize=8, ncol=2)
- self.canvas.draw_idle()
-
- def _reset_plot_data(self) -> None:
- self.time_hist.clear()
- for name in self.joint_names:
- self.pos_hist[name].clear()
- self.vel_hist[name].clear()
- self._plot_start = time.time()
- self._last_plot_time = None
- for line in self.pos_lines.values():
- line.set_data([], [])
- for line in self.vel_lines.values():
- line.set_data([], [])
- self.canvas.draw_idle()
-
def _encode_joint_list(self, joint_list: list[dict]) -> str:
return ";".join(f"{j['joint_name']}:{j['rad']}" for j in joint_list)
@@ -728,8 +626,7 @@ class MainWindow(QtWidgets.QMainWindow):
def _on_stop_get_pose(self) -> None:
if self._pose_worker:
self._append_log("[info] Stopping GetPose...")
- self._pose_worker.stop()
- self._pose_worker = None
+ self._stop_pose_worker()
def _update_pose(self, data: Dict[str, float]) -> None:
for key, value in data.items():
@@ -751,25 +648,57 @@ class MainWindow(QtWidgets.QMainWindow):
if name in selected:
selected_lines.append(f"{name}: {pos:.4f} rad, {vel:.4f} rad/s")
- self._append_plot(name, pos, vel)
if selected_lines:
+ preview = selected_lines[:STATUS_PREVIEW_JOINTS]
+ hidden_count = max(0, len(selected_lines) - STATUS_PREVIEW_JOINTS)
+ suffix = f" | +{hidden_count} more" if hidden_count else ""
self.statusBar().showMessage(
- f"Selected joints: {len(selected)} | " + " | ".join(selected_lines)
+ f"Selected joints: {len(selected)} | " + " | ".join(preview) + suffix
)
else:
self.statusBar().showMessage(f"Selected joints: {len(selected)}")
- if self._is_plot_visible():
- self._refresh_plot(selected)
+ if self.plot_page is not None:
+ self.plot_page.update_data(
+ self._last_data,
+ selected,
+ refresh=self._is_plot_visible(),
+ )
return
def _is_plot_visible(self) -> bool:
return hasattr(self, "center_tabs") and self.center_tabs.currentIndex() == 0
def _on_center_tab_changed(self, index: int) -> None:
- if index == 0:
- self._render_joint_table_and_plot()
+ if index == 0 and self.plot_page is not None:
+ self.plot_page.refresh(self._plot_selected)
+
+ def _stop_jointstate_worker(self) -> None:
+ worker = self._joint_worker
+ self._joint_worker = None
+ if worker:
+ worker.stop()
+ worker.wait(2000)
+
+ def _stop_pose_worker(self) -> None:
+ worker = self._pose_worker
+ self._pose_worker = None
+ if worker:
+ worker.stop()
+ worker.wait(2000)
+
+ def closeEvent(self, event: QtGui.QCloseEvent) -> None:
+ self._stop_jointstate_worker()
+ self._stop_pose_worker()
+ if self._pose_process and self._pose_process.state() != QtCore.QProcess.NotRunning:
+ self._pose_process.terminate()
+ self._pose_process.waitForFinished(1000)
+ if self.camera_page is not None:
+ self.camera_page.shutdown()
+ if self.rgb_image_page is not None:
+ self.rgb_image_page.shutdown()
+ super().closeEvent(event)
def main() -> int:
diff --git a/ui/camera_page.py b/ui/camera_page.py
new file mode 100644
index 0000000..f5981ee
--- /dev/null
+++ b/ui/camera_page.py
@@ -0,0 +1,605 @@
+import time
+from datetime import datetime
+from pathlib import Path
+from typing import Callable, Optional
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+try:
+ import av
+except Exception as exc:
+ av = None
+ AV_IMPORT_ERROR = exc
+else:
+ AV_IMPORT_ERROR = None
+
+try:
+ import cv2
+except Exception as exc:
+ cv2 = None
+ CV2_IMPORT_ERROR = exc
+else:
+ CV2_IMPORT_ERROR = None
+
+from clients._path_setup import ensure_paths
+
+ensure_paths()
+
+from clients.camera_client import CameraClient
+
+
+class CameraStreamThread(QtCore.QThread):
+ frame_received = QtCore.pyqtSignal(object, int, int)
+ status_update = QtCore.pyqtSignal(dict)
+ error_occurred = QtCore.pyqtSignal(str)
+ keyframe_received = QtCore.pyqtSignal()
+
+ def __init__(self, client, device_id, codec="h264", wait_for_keyframe=True):
+ super().__init__()
+ self.client = client
+ self.device_id = device_id
+ self.codec_name = codec
+ self.wait_for_keyframe = wait_for_keyframe
+ self.streaming = False
+ self.decoder = None
+ self.decoder_error = None
+ self.frame_count = 0
+ self.fps_counter = 0
+ self.fps_time = time.time()
+ self.keyframe_count = 0
+ self._init_decoder()
+
+ def _init_decoder(self):
+ if av is None:
+ self.decoder = None
+ self.decoder_error = f"PyAV unavailable: {AV_IMPORT_ERROR}"
+ return
+ try:
+ self.decoder = av.CodecContext.create(self.codec_name, "r")
+ self.decoder.thread_count = 1
+ if hasattr(self.decoder, "flags"):
+ try:
+ self.decoder.flags |= av.codec.context.AV_CODEC_FLAG_LOW_DELAY
+ except Exception:
+ pass
+ except Exception as exc:
+ self.decoder = None
+ self.decoder_error = f"Decoder init failed: {exc}"
+
+ def _decode(self, frame_data):
+ if self.decoder is None or not frame_data:
+ return None
+
+ packet = av.Packet(frame_data)
+ frames = self.decoder.decode(packet)
+ if not frames:
+ return None
+
+ return frames[0].to_ndarray(format="rgb24")
+
+ def _start_camera(self):
+ response = self.client.start_camera(self.device_id)
+ if not response.header.success:
+ self.error_occurred.emit(response.header.error_message or "StartCamera failed")
+ return False
+ return True
+
+ def _stop_camera(self):
+ try:
+ self.client.stop_camera(self.device_id)
+ except Exception:
+ pass
+
+ def run(self):
+ if self.decoder is None:
+ if self.decoder_error:
+ self.error_occurred.emit(self.decoder_error)
+ return
+
+ try:
+ if not self._start_camera():
+ return
+
+ self.streaming = True
+ self.frame_count = 0
+ self.fps_counter = 0
+ self.fps_time = time.time()
+ self.keyframe_count = 0
+ keyframe_seen = False
+
+ responses = self.client.get_rgb_image_stream(
+ device_id=self.device_id,
+ stop_requested=lambda: not self.streaming,
+ )
+
+ for response in responses:
+ if not self.streaming:
+ break
+
+ if not response.header.success:
+ raise RuntimeError(response.header.error_message or "GetRGBImageStream failed")
+
+ frame_data = response.color_frame.data
+ is_keyframe = response.color_frame.is_key_frame
+
+ if self.wait_for_keyframe and not keyframe_seen and not is_keyframe:
+ continue
+
+ if is_keyframe and not keyframe_seen:
+ keyframe_seen = True
+ self.keyframe_count += 1
+ self.keyframe_received.emit()
+
+ image = self._decode(frame_data)
+ if image is None:
+ continue
+
+ self.fps_counter += 1
+ self.frame_count += 1
+
+ now = time.time()
+ if now - self.fps_time >= 1.0:
+ self.status_update.emit(
+ {
+ "fps": self.fps_counter,
+ "frames": self.frame_count,
+ "keyframes": self.keyframe_count,
+ }
+ )
+ self.fps_counter = 0
+ self.fps_time = now
+
+ height, width = image.shape[:2]
+ self.frame_received.emit(image, width, height)
+ except Exception as exc:
+ self.error_occurred.emit(f"Stream error: {exc}")
+ finally:
+ self.streaming = False
+ self._stop_camera()
+
+ def stop(self):
+ self.streaming = False
+
+
+class CameraPage(QtWidgets.QWidget):
+ CODEC_OPTIONS = {
+ "H.264 (AVC)": "h264",
+ "H.265 (HEVC)": "hevc",
+ }
+
+ def __init__(
+ self,
+ address_provider: Optional[Callable[[], str]] = None,
+ default_device_id: str = "right_hand_cam",
+ parent=None,
+ ):
+ super().__init__(parent)
+ self.address_provider = address_provider
+ self.connected = False
+ self.streaming = False
+ self.recording = False
+ self.stream_client = None
+ self.stream_thread = None
+ self.video_writer = None
+ self.current_frame = None
+ self.frame_width = 0
+ self.frame_height = 0
+ self.record_path = Path("./recordings")
+ self.record_path.mkdir(exist_ok=True)
+
+ self._create_ui(default_device_id)
+
+ self.display_timer = QtCore.QTimer(self)
+ self.display_timer.timeout.connect(self._update_display)
+ self.display_timer.start(30)
+
+ def _create_ui(self, default_device_id: str):
+ layout = QtWidgets.QVBoxLayout(self)
+
+ conn_group = QtWidgets.QGroupBox("Camera")
+ conn_layout = QtWidgets.QGridLayout(conn_group)
+
+ conn_layout.addWidget(QtWidgets.QLabel("Device ID:"), 0, 0)
+ self.device_id = QtWidgets.QLineEdit(default_device_id)
+ conn_layout.addWidget(self.device_id, 0, 1)
+
+ conn_layout.addWidget(QtWidgets.QLabel("Codec:"), 0, 2)
+ self.codec_combo = QtWidgets.QComboBox()
+ self.codec_combo.addItems(list(self.CODEC_OPTIONS))
+ self.codec_combo.setCurrentText("H.264 (AVC)")
+ conn_layout.addWidget(self.codec_combo, 0, 3)
+
+ self.wait_keyframe = QtWidgets.QCheckBox("Wait for first keyframe")
+ self.wait_keyframe.setChecked(True)
+ conn_layout.addWidget(self.wait_keyframe, 0, 4, 1, 2)
+
+ if self.address_provider is None:
+ conn_layout.addWidget(QtWidgets.QLabel("Server IP:"), 1, 0)
+ self.server_ip = QtWidgets.QLineEdit("192.168.0.222")
+ conn_layout.addWidget(self.server_ip, 1, 1)
+
+ conn_layout.addWidget(QtWidgets.QLabel("Port:"), 1, 2)
+ self.server_port = QtWidgets.QLineEdit("50052")
+ self.server_port.setMaximumWidth(100)
+ conn_layout.addWidget(self.server_port, 1, 3)
+ else:
+ conn_layout.addWidget(QtWidgets.QLabel("Server:"), 1, 0)
+ self.address_label = QtWidgets.QLabel("")
+ self.address_label.setStyleSheet("font-weight: bold;")
+ conn_layout.addWidget(self.address_label, 1, 1, 1, 3)
+
+ conn_layout.addWidget(QtWidgets.QLabel("Timeout (s):"), 1, 4)
+ self.timeout_spin = QtWidgets.QSpinBox()
+ self.timeout_spin.setRange(1, 10)
+ self.timeout_spin.setValue(3)
+ conn_layout.addWidget(self.timeout_spin, 1, 5)
+
+ conn_layout.addWidget(QtWidgets.QLabel("Record Dir:"), 2, 0)
+ self.record_dir = QtWidgets.QLineEdit(str(self.record_path))
+ conn_layout.addWidget(self.record_dir, 2, 1, 1, 4)
+
+ self.browse_btn = QtWidgets.QPushButton("Browse")
+ self.browse_btn.clicked.connect(self._browse_dir)
+ conn_layout.addWidget(self.browse_btn, 2, 5)
+
+ button_layout = QtWidgets.QHBoxLayout()
+ self.connect_btn = QtWidgets.QPushButton("Connect")
+ self.connect_btn.clicked.connect(self._connect)
+ button_layout.addWidget(self.connect_btn)
+
+ self.preview_btn = QtWidgets.QPushButton("Preview")
+ self.preview_btn.clicked.connect(self._toggle_preview)
+ self.preview_btn.setEnabled(False)
+ button_layout.addWidget(self.preview_btn)
+
+ self.record_btn = QtWidgets.QPushButton("Record")
+ self.record_btn.clicked.connect(self._toggle_recording)
+ self.record_btn.setEnabled(False)
+ button_layout.addWidget(self.record_btn)
+
+ self.disconnect_btn = QtWidgets.QPushButton("Disconnect")
+ self.disconnect_btn.clicked.connect(self._disconnect)
+ self.disconnect_btn.setEnabled(False)
+ button_layout.addWidget(self.disconnect_btn)
+ button_layout.addStretch()
+ conn_layout.addLayout(button_layout, 3, 0, 1, 6)
+
+ layout.addWidget(conn_group)
+
+ status_group = QtWidgets.QGroupBox("Stream Status")
+ status_layout = QtWidgets.QHBoxLayout(status_group)
+
+ self.conn_status = QtWidgets.QLabel("Disconnected")
+ self.conn_status.setStyleSheet("color: red;")
+ status_layout.addWidget(self.conn_status)
+
+ status_layout.addWidget(QtWidgets.QLabel("|"))
+ self.codec_status = QtWidgets.QLabel("Codec: H.264 (AVC)")
+ status_layout.addWidget(self.codec_status)
+
+ status_layout.addWidget(QtWidgets.QLabel("|"))
+ self.fps_status = QtWidgets.QLabel("FPS: 0")
+ status_layout.addWidget(self.fps_status)
+
+ status_layout.addWidget(QtWidgets.QLabel("|"))
+ self.frame_status = QtWidgets.QLabel("Frames: 0")
+ status_layout.addWidget(self.frame_status)
+
+ status_layout.addWidget(QtWidgets.QLabel("|"))
+ self.resolution_status = QtWidgets.QLabel("Resolution: -")
+ status_layout.addWidget(self.resolution_status)
+
+ status_layout.addWidget(QtWidgets.QLabel("|"))
+ self.keyframe_status = QtWidgets.QLabel("Keyframe: waiting")
+ self.keyframe_status.setStyleSheet("color: orange;")
+ status_layout.addWidget(self.keyframe_status)
+
+ status_layout.addWidget(QtWidgets.QLabel("|"))
+ self.record_status = QtWidgets.QLabel("Record: idle")
+ self.record_status.setStyleSheet("color: gray;")
+ status_layout.addWidget(self.record_status)
+ status_layout.addStretch()
+
+ layout.addWidget(status_group)
+
+ video_group = QtWidgets.QGroupBox("Preview")
+ video_layout = QtWidgets.QVBoxLayout(video_group)
+
+ self.video_label = QtWidgets.QLabel()
+ self.video_label.setAlignment(QtCore.Qt.AlignCenter)
+ self.video_label.setMinimumSize(640, 480)
+ self.video_label.setStyleSheet("background-color: black; border: 1px solid gray;")
+ video_layout.addWidget(self.video_label)
+
+ layout.addWidget(video_group, 1)
+
+ info_frame = QtWidgets.QFrame()
+ info_frame.setFrameStyle(QtWidgets.QFrame.StyledPanel | QtWidgets.QFrame.Sunken)
+ info_layout = QtWidgets.QHBoxLayout(info_frame)
+ info_label = QtWidgets.QLabel(
+ "Connect to the camera service, start preview, then optionally record decoded frames."
+ )
+ info_label.setStyleSheet("color: gray; padding: 5px;")
+ info_layout.addWidget(info_label)
+ layout.addWidget(info_frame)
+
+ self.status_bar = QtWidgets.QStatusBar()
+ self.status_bar.showMessage("Idle")
+ layout.addWidget(self.status_bar)
+
+ def refresh_address_hint(self):
+ if self.address_provider is None or not hasattr(self, "address_label"):
+ return
+ try:
+ self.address_label.setText(self.address_provider())
+ except Exception:
+ self.address_label.setText("(app settings unavailable)")
+
+ def _browse_dir(self):
+ directory = QtWidgets.QFileDialog.getExistingDirectory(
+ self, "Select record directory", self.record_dir.text()
+ )
+ if directory:
+ self.record_dir.setText(directory)
+ self.record_path = Path(directory)
+ self.record_path.mkdir(exist_ok=True)
+
+ def _get_server_addr(self):
+ if self.address_provider is not None:
+ return self.address_provider()
+ return f"{self.server_ip.text().strip()}:{self.server_port.text().strip()}"
+
+ def _get_codec(self):
+ return self.CODEC_OPTIONS[self.codec_combo.currentText()]
+
+ def _connect(self):
+ if self.connected:
+ return
+
+ self.refresh_address_hint()
+ self.status_bar.showMessage("Connecting to camera service...")
+ self.codec_status.setText(f"Codec: {self.codec_combo.currentText()}")
+
+ try:
+ self.stream_client = CameraClient(
+ address=self._get_server_addr(),
+ timeout=self.timeout_spin.value(),
+ )
+ except Exception as exc:
+ self.stream_client = None
+ self._on_error(f"Connect failed: {exc}")
+ return
+
+ self.connected = True
+ self.conn_status.setText("Connected")
+ self.conn_status.setStyleSheet("color: green;")
+ self.connect_btn.setEnabled(False)
+ self.preview_btn.setEnabled(True)
+ self.disconnect_btn.setEnabled(True)
+ self.codec_combo.setEnabled(False)
+ self.wait_keyframe.setEnabled(False)
+ self.timeout_spin.setEnabled(False)
+ if self.address_provider is None:
+ self.server_ip.setEnabled(False)
+ self.server_port.setEnabled(False)
+ self.status_bar.showMessage("Connected to camera service", 3000)
+
+ def _disconnect(self):
+ if self.streaming:
+ self._stop_preview()
+
+ if self.recording:
+ self._stop_recording()
+
+ if self.stream_client:
+ self.stream_client.close()
+ self.stream_client = None
+
+ self.connected = False
+ self.stream_thread = None
+ self.conn_status.setText("Disconnected")
+ self.conn_status.setStyleSheet("color: red;")
+ self.connect_btn.setEnabled(True)
+ self.preview_btn.setEnabled(False)
+ self.preview_btn.setText("Preview")
+ self.record_btn.setEnabled(False)
+ self.record_btn.setText("Record")
+ self.disconnect_btn.setEnabled(False)
+ self.codec_combo.setEnabled(True)
+ self.wait_keyframe.setEnabled(True)
+ self.timeout_spin.setEnabled(True)
+ if self.address_provider is None:
+ self.server_ip.setEnabled(True)
+ self.server_port.setEnabled(True)
+ self._clear_preview()
+ self._reset_status_labels()
+ self.status_bar.showMessage("Disconnected", 3000)
+
+ def _toggle_preview(self):
+ if self.streaming:
+ self._stop_preview()
+ else:
+ self._start_preview()
+
+ def _start_preview(self):
+ if not self.connected or self.stream_client is None or self.streaming:
+ return
+
+ self.stream_thread = CameraStreamThread(
+ client=self.stream_client,
+ device_id=self.device_id.text().strip(),
+ codec=self._get_codec(),
+ wait_for_keyframe=self.wait_keyframe.isChecked(),
+ )
+ self.stream_thread.frame_received.connect(self._on_frame_received)
+ self.stream_thread.status_update.connect(self._on_status_update)
+ self.stream_thread.error_occurred.connect(self._on_error)
+ self.stream_thread.keyframe_received.connect(self._on_keyframe_received)
+ self.stream_thread.finished.connect(self._on_stream_finished)
+
+ self.streaming = True
+ self.preview_btn.setText("Stop")
+ self.record_btn.setEnabled(True)
+
+ if self.wait_keyframe.isChecked():
+ self.keyframe_status.setText("Keyframe: waiting")
+ self.keyframe_status.setStyleSheet("color: orange;")
+ else:
+ self.keyframe_status.setText("Keyframe: not required")
+ self.keyframe_status.setStyleSheet("color: gray;")
+
+ self.status_bar.showMessage("Starting preview...")
+ self.stream_thread.start()
+
+ def _stop_preview(self):
+ if self.recording:
+ self._stop_recording()
+
+ thread = self.stream_thread
+ if thread is not None:
+ thread.stop()
+ thread.wait(2000)
+ if self.stream_thread is thread:
+ self._on_stream_finished()
+ else:
+ self._on_stream_finished()
+
+ def _on_stream_finished(self):
+ if self.recording:
+ self._stop_recording()
+ self.streaming = False
+ self.stream_thread = None
+ self.preview_btn.setText("Preview")
+ self.record_btn.setEnabled(False)
+ self.keyframe_status.setText("Keyframe: waiting")
+ self.keyframe_status.setStyleSheet("color: orange;")
+ self.status_bar.showMessage("Preview stopped", 3000)
+
+ def _toggle_recording(self):
+ if self.recording:
+ self._stop_recording()
+ else:
+ self._start_recording()
+
+ def _start_recording(self):
+ if not self.streaming:
+ QtWidgets.QMessageBox.warning(self, "Warning", "Start preview before recording.")
+ return
+ if cv2 is None:
+ QtWidgets.QMessageBox.critical(self, "Error", f"OpenCV unavailable: {CV2_IMPORT_ERROR}")
+ return
+
+ self.record_path = Path(self.record_dir.text())
+ self.record_path.mkdir(parents=True, exist_ok=True)
+
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
+ codec_name = self.codec_combo.currentText().replace(" ", "_")
+ filename = self.record_path / f"rec_{self.device_id.text()}_{codec_name}_{timestamp}.mp4"
+
+ if self.current_frame is not None:
+ height, width = self.current_frame.shape[:2]
+ else:
+ width, height = 640, 480
+
+ fourcc = cv2.VideoWriter_fourcc(*"mp4v")
+ self.video_writer = cv2.VideoWriter(str(filename), fourcc, 30.0, (width, height))
+
+ if not self.video_writer.isOpened():
+ self.video_writer = None
+ QtWidgets.QMessageBox.critical(self, "Error", "Failed to create the output video file.")
+ return
+
+ self.recording = True
+ self.record_btn.setText("Stop Recording")
+ self.record_status.setText("Record: active")
+ self.record_status.setStyleSheet("color: red;")
+ self.status_bar.showMessage(f"Recording to {filename.name}", 3000)
+
+ def _stop_recording(self):
+ if self.video_writer:
+ self.video_writer.release()
+ self.video_writer = None
+
+ self.recording = False
+ self.record_btn.setText("Record")
+ self.record_status.setText("Record: idle")
+ self.record_status.setStyleSheet("color: gray;")
+ self.status_bar.showMessage("Recording stopped", 3000)
+
+ def _on_frame_received(self, frame, width, height):
+ self.current_frame = frame
+ self.frame_width = width
+ self.frame_height = height
+
+ if self.recording and self.video_writer and cv2 is not None:
+ self.video_writer.write(cv2.cvtColor(frame, cv2.COLOR_RGB2BGR))
+
+ def _update_display(self):
+ if self.current_frame is None:
+ return
+
+ bytes_per_line = 3 * self.frame_width
+ qimage = QtGui.QImage(
+ self.current_frame.data,
+ self.frame_width,
+ self.frame_height,
+ bytes_per_line,
+ QtGui.QImage.Format_RGB888,
+ )
+
+ pixmap = QtGui.QPixmap.fromImage(qimage).scaled(
+ self.video_label.size(),
+ QtCore.Qt.KeepAspectRatio,
+ QtCore.Qt.SmoothTransformation,
+ )
+ self.video_label.setPixmap(pixmap)
+ self.resolution_status.setText(f"Resolution: {self.frame_width}x{self.frame_height}")
+
+ def _on_status_update(self, stats):
+ self.fps_status.setText(f"FPS: {stats['fps']}")
+ self.frame_status.setText(f"Frames: {stats['frames']}")
+
+ def _on_keyframe_received(self):
+ if self.wait_keyframe.isChecked():
+ self.keyframe_status.setText("Keyframe: received")
+ self.keyframe_status.setStyleSheet("color: green;")
+
+ def _on_error(self, message):
+ QtWidgets.QMessageBox.critical(self, "Error", message)
+ self.status_bar.showMessage(message, 5000)
+
+ def _clear_preview(self):
+ self.video_label.clear()
+ self.current_frame = None
+ self.frame_width = 0
+ self.frame_height = 0
+
+ def _reset_status_labels(self):
+ self.fps_status.setText("FPS: 0")
+ self.frame_status.setText("Frames: 0")
+ self.resolution_status.setText("Resolution: -")
+ self.keyframe_status.setText("Keyframe: waiting")
+ self.keyframe_status.setStyleSheet("color: orange;")
+ self.record_status.setText("Record: idle")
+ self.record_status.setStyleSheet("color: gray;")
+
+ def shutdown(self):
+ if self.streaming:
+ self._stop_preview()
+ if self.recording:
+ self._stop_recording()
+ if self.connected:
+ self._disconnect()
+
+
+class CameraWindow(QtWidgets.QMainWindow):
+ def __init__(self):
+ super().__init__()
+ self.setWindowTitle("Camera Stream Player")
+ self.resize(1000, 800)
+ self.camera_page = CameraPage(parent=self)
+ self.setCentralWidget(self.camera_page)
+
+ def closeEvent(self, event):
+ self.camera_page.shutdown()
+ super().closeEvent(event)
diff --git a/ui/plot_page.py b/ui/plot_page.py
new file mode 100644
index 0000000..15f23ae
--- /dev/null
+++ b/ui/plot_page.py
@@ -0,0 +1,396 @@
+from __future__ import annotations
+
+import time
+from dataclasses import dataclass, field
+from typing import Dict, Optional, Tuple
+
+import numpy as np
+from PyQt5 import QtCore, QtWidgets
+
+try:
+ import pyqtgraph as pg
+except Exception as exc:
+ pg = None
+ PYQTGRAPH_IMPORT_ERROR = exc
+else:
+ PYQTGRAPH_IMPORT_ERROR = None
+
+
+if pg is None:
+ InteractivePlotViewBox = None
+else:
+ class InteractivePlotViewBox(pg.ViewBox):
+ def wheelEvent(self, ev, axis=None):
+ modifiers = QtWidgets.QApplication.keyboardModifiers()
+ target_axis = 0 if modifiers & QtCore.Qt.ControlModifier else 1
+ super().wheelEvent(ev, axis=target_axis)
+
+
+MAX_RENDER_POINTS = 600
+REFRESH_INTERVAL_MS = 80
+LEGEND_LIMIT = 8
+POSITION_Y_RANGE = (-3.141592653589793, 3.141592653589793)
+VELOCITY_Y_RANGE = (-5.0, 5.0)
+TIME_WINDOW_OPTIONS = (
+ ("1 min", 60.0),
+ ("5 min", 300.0),
+ ("All", None),
+)
+
+
+@dataclass
+class MultiResolutionSeries:
+ levels: list[list[tuple[float, float]]] = field(default_factory=lambda: [[]])
+ carry: list[list[tuple[float, float]]] = field(default_factory=lambda: [[]])
+
+ def clear(self) -> None:
+ self.levels = [[]]
+ self.carry = [[]]
+
+ def append(self, t_value: float, y_value: float) -> None:
+ self._append_level(0, (t_value, y_value))
+
+ def sample(self, max_points: int, min_time: Optional[float] = None) -> tuple[np.ndarray, np.ndarray]:
+ raw_points = self.levels[0]
+ if not raw_points:
+ return np.empty(0, dtype=float), np.empty(0, dtype=float)
+
+ points = self._slice_points(raw_points, min_time)
+ if not points:
+ return np.empty(0, dtype=float), np.empty(0, dtype=float)
+
+ for level_points in self.levels:
+ sliced_points = self._slice_points(level_points, min_time)
+ if not sliced_points:
+ continue
+ points = sliced_points
+ if len(sliced_points) <= max_points:
+ break
+
+ if len(points) > max_points:
+ step = max(1, len(points) // max_points)
+ points = points[::step]
+
+ sampled = list(points)
+ first_raw = self._slice_points(raw_points, min_time)[0]
+ last_raw = raw_points[-1]
+ if sampled[0][0] > first_raw[0]:
+ sampled.insert(0, first_raw)
+ if sampled[-1][0] < last_raw[0]:
+ sampled.append(last_raw)
+
+ x_values = np.fromiter((point[0] for point in sampled), dtype=float, count=len(sampled))
+ y_values = np.fromiter((point[1] for point in sampled), dtype=float, count=len(sampled))
+ return x_values, y_values
+
+ def _append_level(self, level: int, point: tuple[float, float]) -> None:
+ while len(self.levels) <= level:
+ self.levels.append([])
+ self.carry.append([])
+
+ self.levels[level].append(point)
+ bucket = self.carry[level]
+ bucket.append(point)
+ if len(bucket) < 2:
+ return
+
+ point_a, point_b = bucket
+ bucket.clear()
+ merged = (
+ (point_a[0] + point_b[0]) * 0.5,
+ (point_a[1] + point_b[1]) * 0.5,
+ )
+ self._append_level(level + 1, merged)
+
+ def _slice_points(
+ self,
+ points: list[tuple[float, float]],
+ min_time: Optional[float],
+ ) -> list[tuple[float, float]]:
+ if not points:
+ return []
+ if min_time is None or min_time <= points[0][0]:
+ return points
+
+ left = 0
+ right = len(points)
+ while left < right:
+ mid = (left + right) // 2
+ if points[mid][0] < min_time:
+ left = mid + 1
+ else:
+ right = mid
+
+ start = max(0, left - 1)
+ return points[start:]
+
+
+class PlotPage(QtWidgets.QWidget):
+ def __init__(self, joint_names: list[str], parent=None) -> None:
+ super().__init__(parent)
+ self.joint_names = joint_names
+ self.pos_lines: Dict[str, object] = {}
+ self.vel_lines: Dict[str, object] = {}
+ self.pos_hist: Dict[str, MultiResolutionSeries] = {
+ name: MultiResolutionSeries() for name in joint_names
+ }
+ self.vel_hist: Dict[str, MultiResolutionSeries] = {
+ name: MultiResolutionSeries() for name in joint_names
+ }
+ self._plot_start = time.time()
+ self._elapsed_time = 0.0
+ self._last_plot_time = None
+ self._pending_selected: set[str] = set()
+ self._dirty = False
+ self._last_legend_key: tuple[str, ...] = ()
+ self._color_map = {}
+ self._auto_follow_x = True
+ self._applying_x_range = False
+ self._build_ui()
+ self._refresh_timer = QtCore.QTimer(self)
+ self._refresh_timer.setSingleShot(True)
+ self._refresh_timer.setInterval(REFRESH_INTERVAL_MS)
+ self._refresh_timer.timeout.connect(self._flush_refresh)
+
+ def _build_ui(self) -> None:
+ layout = QtWidgets.QVBoxLayout(self)
+ layout.setContentsMargins(0, 0, 0, 0)
+ layout.setSpacing(4)
+
+ controls = QtWidgets.QHBoxLayout()
+ controls.addWidget(QtWidgets.QLabel("Time Window:"))
+ self.window_combo = QtWidgets.QComboBox()
+ for label, seconds in TIME_WINDOW_OPTIONS:
+ self.window_combo.addItem(label, seconds)
+ self.window_combo.setCurrentIndex(0)
+ self.window_combo.currentIndexChanged.connect(self._on_window_changed)
+ controls.addWidget(self.window_combo)
+ controls.addStretch(1)
+ self.clear_btn = QtWidgets.QPushButton("Clear Plot")
+ self.clear_btn.clicked.connect(self.reset_data)
+ controls.addWidget(self.clear_btn)
+ self.reset_view_btn = QtWidgets.QPushButton("Reset View")
+ self.reset_view_btn.clicked.connect(self._reset_view)
+ controls.addWidget(self.reset_view_btn)
+ layout.addLayout(controls)
+
+ if pg is None:
+ message = QtWidgets.QLabel(f"pyqtgraph unavailable: {PYQTGRAPH_IMPORT_ERROR}")
+ message.setStyleSheet("color: red; padding: 12px;")
+ layout.addWidget(message)
+ return
+
+ pg.setConfigOptions(antialias=False)
+ self.plot_splitter = QtWidgets.QSplitter(QtCore.Qt.Vertical)
+ self.plot_splitter.setChildrenCollapsible(False)
+ self.plot_splitter.setHandleWidth(8)
+ layout.addWidget(self.plot_splitter, 1)
+
+ self.pos_plot_widget = pg.PlotWidget(viewBox=InteractivePlotViewBox())
+ self.pos_plot = self.pos_plot_widget.getPlotItem()
+ self.pos_plot.setTitle("Position (rad)")
+ self.plot_splitter.addWidget(self.pos_plot_widget)
+
+ self.vel_plot_widget = pg.PlotWidget(viewBox=InteractivePlotViewBox())
+ self.vel_plot = self.vel_plot_widget.getPlotItem()
+ self.vel_plot.setTitle("Velocity (rad/s)")
+ self.plot_splitter.addWidget(self.vel_plot_widget)
+ self.plot_splitter.setStretchFactor(0, 1)
+ self.plot_splitter.setStretchFactor(1, 1)
+
+ self.vel_plot.setXLink(self.pos_plot)
+ self.pos_plot.getViewBox().sigXRangeChanged.connect(self._on_plot_x_range_changed)
+ self.vel_plot.getViewBox().sigXRangeChanged.connect(self._on_plot_x_range_changed)
+
+ self._configure_plot(self.pos_plot, "Position", "rad", POSITION_Y_RANGE, show_bottom=False)
+ self._configure_plot(self.vel_plot, "Velocity", "rad/s", VELOCITY_Y_RANGE, show_bottom=True)
+
+ self.pos_legend = self.pos_plot.addLegend(offset=(-10, 10))
+ self.vel_legend = self.vel_plot.addLegend(offset=(-10, 10))
+ self.pos_legend.setVisible(False)
+ self.vel_legend.setVisible(False)
+ self._apply_time_window()
+
+ def _configure_plot(self, plot, label: str, units: str, y_range: tuple[float, float], show_bottom: bool) -> None:
+ plot.showGrid(x=True, y=True, alpha=0.25)
+ plot.setLabel("left", label, units=units)
+ plot.setMouseEnabled(x=True, y=True)
+ plot.setMenuEnabled(False)
+ plot.setYRange(y_range[0], y_range[1], padding=0.0)
+ plot.getAxis("bottom").setStyle(showValues=show_bottom)
+ if show_bottom:
+ plot.setLabel("bottom", "Time", units="s")
+
+ def update_data(
+ self,
+ data: Dict[str, Tuple[float, float]],
+ selected: set[str],
+ refresh: bool = True,
+ ) -> None:
+ sample_time = time.time() - self._plot_start
+ if self._last_plot_time is not None and sample_time <= self._last_plot_time:
+ sample_time = self._last_plot_time + 1e-6
+ self._last_plot_time = sample_time
+ self._elapsed_time = max(self._elapsed_time, sample_time)
+
+ for name in selected:
+ pos, vel = data.get(name, (0.0, 0.0))
+ self.pos_hist[name].append(sample_time, pos)
+ self.vel_hist[name].append(sample_time, vel)
+
+ if refresh:
+ self.refresh(selected)
+
+ def refresh(self, selected: set[str]) -> None:
+ self._pending_selected = set(selected)
+ self._dirty = True
+ if not self._refresh_timer.isActive():
+ self._refresh_timer.start()
+
+ def reset_data(self) -> None:
+ for name in self.joint_names:
+ self.pos_hist[name].clear()
+ self.vel_hist[name].clear()
+ self._plot_start = time.time()
+ self._elapsed_time = 0.0
+ self._last_plot_time = None
+ self._pending_selected.clear()
+ self._dirty = False
+ self._last_legend_key = ()
+ self._auto_follow_x = True
+
+ if pg is None:
+ return
+
+ for line in self.pos_lines.values():
+ line.setData([], [])
+ for line in self.vel_lines.values():
+ line.setData([], [])
+
+ self._apply_time_window()
+ self.pos_plot.setYRange(POSITION_Y_RANGE[0], POSITION_Y_RANGE[1], padding=0.0)
+ self.vel_plot.setYRange(VELOCITY_Y_RANGE[0], VELOCITY_Y_RANGE[1], padding=0.0)
+ self._sync_legends(set())
+
+ def _flush_refresh(self) -> None:
+ if not self._dirty:
+ return
+ self._dirty = False
+ self._refresh_plot(self._pending_selected)
+
+ def _refresh_plot(self, selected: set[str]) -> None:
+ if pg is None:
+ return
+
+ min_time = self._window_min_time()
+
+ for name in list(self.pos_lines.keys()):
+ if name not in selected:
+ self.pos_plot.removeItem(self.pos_lines[name])
+ self.vel_plot.removeItem(self.vel_lines[name])
+ del self.pos_lines[name]
+ del self.vel_lines[name]
+
+ for name in selected:
+ if name not in self.pos_lines:
+ color = self._color_for_joint(name)
+ self.pos_lines[name] = self._create_curve(self.pos_plot, color)
+ self.vel_lines[name] = self._create_curve(self.vel_plot, color)
+
+ pos_x, pos_y = self.pos_hist[name].sample(MAX_RENDER_POINTS, min_time=min_time)
+ vel_x, vel_y = self.vel_hist[name].sample(MAX_RENDER_POINTS, min_time=min_time)
+ self.pos_lines[name].setData(pos_x, pos_y)
+ self.vel_lines[name].setData(vel_x, vel_y)
+
+ self._apply_time_window()
+
+ self._sync_legends(selected)
+
+ def _create_curve(self, plot, color):
+ curve = plot.plot([], [], pen=pg.mkPen(color=color, width=1.6), antialias=False)
+ curve.setClipToView(True)
+ curve.setDownsampling(auto=True, method="peak")
+ return curve
+
+ def _color_for_joint(self, name: str):
+ if name not in self._color_map:
+ index = len(self._color_map)
+ self._color_map[name] = pg.intColor(index, hues=max(8, len(self.joint_names)))
+ return self._color_map[name]
+
+ def _sync_legends(self, selected: set[str]) -> None:
+ if pg is None:
+ return
+
+ selection_key = tuple(sorted(selected))
+ if selection_key == self._last_legend_key:
+ return
+
+ self.pos_legend.clear()
+ self.vel_legend.clear()
+
+ show_legend = bool(selected) and len(selected) <= LEGEND_LIMIT
+ self.pos_legend.setVisible(show_legend)
+ self.vel_legend.setVisible(show_legend)
+ if show_legend:
+ for name in selection_key:
+ pos_line = self.pos_lines.get(name)
+ vel_line = self.vel_lines.get(name)
+ if pos_line is not None:
+ self.pos_legend.addItem(pos_line, name)
+ if vel_line is not None:
+ self.vel_legend.addItem(vel_line, name)
+ self._last_legend_key = selection_key
+
+ def _window_min_time(self) -> Optional[float]:
+ window_seconds = self.window_combo.currentData() if hasattr(self, "window_combo") else None
+ if window_seconds is None:
+ return None
+ return max(0.0, self._elapsed_time - float(window_seconds))
+
+ def _apply_time_window(self) -> None:
+ if pg is None or not hasattr(self, "window_combo"):
+ return
+ if not self._auto_follow_x:
+ return
+
+ window_seconds = self.window_combo.currentData()
+ if window_seconds is None:
+ min_time = 0.0
+ max_time = max(1.0, self._elapsed_time)
+ else:
+ duration = float(window_seconds)
+ min_time = max(0.0, self._elapsed_time - duration)
+ max_time = duration if self._elapsed_time < duration else self._elapsed_time
+ max_time = max(min_time + 1.0, max_time)
+
+ self._set_x_range(min_time, max_time)
+
+ def _set_x_range(self, min_time: float, max_time: float) -> None:
+ self._applying_x_range = True
+ try:
+ self.pos_plot.setXRange(min_time, max_time, padding=0.0)
+ self.vel_plot.setXRange(min_time, max_time, padding=0.0)
+ finally:
+ self._applying_x_range = False
+
+ def _on_plot_x_range_changed(self, *_args) -> None:
+ if self._applying_x_range:
+ return
+ self._auto_follow_x = False
+
+ def _reset_view(self) -> None:
+ if pg is None:
+ return
+ self._auto_follow_x = True
+ self._apply_time_window()
+ self.pos_plot.setYRange(POSITION_Y_RANGE[0], POSITION_Y_RANGE[1], padding=0.0)
+ self.vel_plot.setYRange(VELOCITY_Y_RANGE[0], VELOCITY_Y_RANGE[1], padding=0.0)
+
+ def _on_window_changed(self, _index: int) -> None:
+ self._auto_follow_x = True
+ active = self._pending_selected or set(self.pos_lines.keys())
+ if active:
+ self.refresh(active)
+ else:
+ self._apply_time_window()
diff --git a/ui/rgb_image_page.py b/ui/rgb_image_page.py
new file mode 100644
index 0000000..2f5e715
--- /dev/null
+++ b/ui/rgb_image_page.py
@@ -0,0 +1,710 @@
+import time
+from pathlib import Path
+from typing import Callable, Optional
+
+import grpc
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+try:
+ import av
+except Exception as exc:
+ av = None
+ AV_IMPORT_ERROR = exc
+else:
+ AV_IMPORT_ERROR = None
+
+from clients._path_setup import ensure_paths
+
+ensure_paths()
+
+from clients.camera_client import CameraClient
+from clients.hlc_client import HlcClient
+from cmvr.api import camera_command_pb2 as pb
+
+TOUCH_TIMEOUT_SECONDS = 30.0
+
+
+class ImagePreviewLabel(QtWidgets.QLabel):
+ uv_changed = QtCore.pyqtSignal(object)
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self._image = None
+ self._selected_uv = None
+
+ def set_image(self, image: QtGui.QImage):
+ self._image = image
+ self._selected_uv = None
+ self._refresh_pixmap()
+
+ def clear_image(self):
+ self._image = None
+ self._selected_uv = None
+ self.clear()
+ self.uv_changed.emit(None)
+
+ def resizeEvent(self, event):
+ super().resizeEvent(event)
+ if self._image is not None:
+ self._refresh_pixmap()
+
+ def mousePressEvent(self, event):
+ super().mousePressEvent(event)
+ if event.button() != QtCore.Qt.LeftButton:
+ return
+
+ uv = self._map_to_image_uv(event.pos())
+ self._selected_uv = uv
+ self._refresh_pixmap()
+ self.uv_changed.emit(uv)
+
+ def _map_to_image_uv(self, pos):
+ if self._image is None:
+ return None
+
+ pixmap = self.pixmap()
+ if pixmap is None or pixmap.isNull():
+ return None
+
+ x_offset = (self.width() - pixmap.width()) / 2.0
+ y_offset = (self.height() - pixmap.height()) / 2.0
+ x = pos.x() - x_offset
+ y = pos.y() - y_offset
+
+ if x < 0 or y < 0 or x >= pixmap.width() or y >= pixmap.height():
+ return None
+
+ u = int(x * self._image.width() / pixmap.width())
+ v = int(y * self._image.height() / pixmap.height())
+ u = max(0, min(u, self._image.width() - 1))
+ v = max(0, min(v, self._image.height() - 1))
+ return (u, v)
+
+ def _refresh_pixmap(self):
+ if self._image is None:
+ return
+ pixmap = QtGui.QPixmap.fromImage(self._image).scaled(
+ self.size(),
+ QtCore.Qt.KeepAspectRatio,
+ QtCore.Qt.SmoothTransformation,
+ )
+ if self._selected_uv is not None and not pixmap.isNull():
+ self._draw_crosshair(pixmap, self._selected_uv)
+ self.setPixmap(pixmap)
+
+ def _draw_crosshair(self, pixmap: QtGui.QPixmap, uv):
+ if self._image is None:
+ return
+
+ u, v = uv
+ x = int((u + 0.5) * pixmap.width() / self._image.width())
+ y = int((v + 0.5) * pixmap.height() / self._image.height())
+ x = max(0, min(x, pixmap.width() - 1))
+ y = max(0, min(y, pixmap.height() - 1))
+ arm = max(4, min(pixmap.width(), pixmap.height()) // 48)
+
+ painter = QtGui.QPainter(pixmap)
+ painter.setRenderHint(QtGui.QPainter.Antialiasing, False)
+
+ outline_pen = QtGui.QPen(QtGui.QColor("white"), 2)
+ painter.setPen(outline_pen)
+ painter.drawLine(max(0, x - arm), y, min(pixmap.width() - 1, x + arm), y)
+ painter.drawLine(x, max(0, y - arm), x, min(pixmap.height() - 1, y + arm))
+
+ cross_pen = QtGui.QPen(QtGui.QColor("red"), 1)
+ painter.setPen(cross_pen)
+ painter.drawLine(max(0, x - arm), y, min(pixmap.width() - 1, x + arm), y)
+ painter.drawLine(x, max(0, y - arm), x, min(pixmap.height() - 1, y + arm))
+ painter.end()
+
+
+class ToggleSwitch(QtWidgets.QCheckBox):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.setCursor(QtCore.Qt.PointingHandCursor)
+ self.setFixedSize(56, 30)
+
+ def sizeHint(self):
+ return QtCore.QSize(56, 30)
+
+ def paintEvent(self, event):
+ del event
+ radius = (self.height() - 2) / 2.0
+ track_rect = QtCore.QRectF(1, 1, self.width() - 2, self.height() - 2)
+ knob_diameter = track_rect.height() - 6
+ knob_y = track_rect.top() + 3
+ knob_x = track_rect.left() + 3
+ if self.isChecked():
+ knob_x = track_rect.right() - knob_diameter - 3
+
+ if self.isEnabled():
+ track_color = QtGui.QColor("#2ca36a") if self.isChecked() else QtGui.QColor("#9aa4b2")
+ border_color = QtGui.QColor("#1c7a4c") if self.isChecked() else QtGui.QColor("#6c7685")
+ knob_color = QtGui.QColor("#ffffff")
+ else:
+ track_color = QtGui.QColor("#c7cdd6")
+ border_color = QtGui.QColor("#afb7c2")
+ knob_color = QtGui.QColor("#f5f6f8")
+
+ painter = QtGui.QPainter(self)
+ painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
+ painter.setPen(QtGui.QPen(border_color, 1))
+ painter.setBrush(track_color)
+ painter.drawRoundedRect(track_rect, radius, radius)
+
+ knob_rect = QtCore.QRectF(knob_x, knob_y, knob_diameter, knob_diameter)
+ painter.setPen(QtGui.QPen(QtGui.QColor(0, 0, 0, 25), 1))
+ painter.setBrush(knob_color)
+ painter.drawEllipse(knob_rect)
+
+
+class CameraRpcWorker(QtCore.QThread):
+ image_ready = QtCore.pyqtSignal(object, dict)
+ command_done = QtCore.pyqtSignal(str)
+ error_occurred = QtCore.pyqtSignal(str)
+
+ def __init__(
+ self,
+ address: str,
+ timeout: float,
+ device_id: str,
+ action: str,
+ u: Optional[int] = None,
+ v: Optional[int] = None,
+ max_force: Optional[float] = None,
+ ):
+ super().__init__()
+ self.address = address
+ self.timeout = timeout
+ self.device_id = device_id
+ self.action = action
+ self.u = u
+ self.v = v
+ self.max_force = max_force
+
+ def run(self):
+ client = None
+ try:
+ if self.action == "touch":
+ if self.u is None or self.v is None:
+ raise RuntimeError("No UV selected for touch")
+ client = HlcClient(address=self.address, timeout=self.timeout)
+ rpc_timeout = max(float(self.timeout), TOUCH_TIMEOUT_SECONDS)
+ max_force = -1.0 if self.max_force is None else float(self.max_force)
+ response = client.touch(
+ u=self.u,
+ v=self.v,
+ max_force=max_force,
+ timeout=rpc_timeout,
+ )
+ if not response.header.success:
+ raise RuntimeError(response.header.error_message or "touch failed")
+ self.command_done.emit("touch")
+ return
+
+ client = CameraClient(address=self.address, timeout=self.timeout)
+
+ if self.action == "start":
+ start_resp = client.start_camera(self.device_id)
+ if not start_resp.header.success:
+ raise RuntimeError(start_resp.header.error_message or "StartCamera failed")
+ self.command_done.emit("start")
+ return
+
+ if self.action == "stop":
+ stop_resp = client.stop_camera(self.device_id)
+ if hasattr(stop_resp, "header") and not stop_resp.header.success:
+ raise RuntimeError(stop_resp.header.error_message or "StopCamera failed")
+ self.command_done.emit("stop")
+ return
+
+ response = client.get_rgb_image(self.device_id)
+ if not response.header.success:
+ raise RuntimeError(response.header.error_message or "GetRGBImage failed")
+
+ image = self._decode_to_qimage(response.color_frame)
+ codec_value = (response.color_frame.codec or "").strip()
+ if codec_value.lower() in {"", "none", "raw"}:
+ codec_value = ""
+ metadata = {
+ "width": response.color_frame.width,
+ "height": response.color_frame.height,
+ "codec": codec_value,
+ "frame_type": pb.FrameData.FrameType.Name(response.color_frame.type),
+ "is_key_frame": response.color_frame.is_key_frame,
+ "data_size": len(response.color_frame.data),
+ }
+ self.image_ready.emit(image, metadata)
+ except grpc.RpcError as exc:
+ if exc.code() == grpc.StatusCode.DEADLINE_EXCEEDED:
+ timeout_value = max(float(self.timeout), TOUCH_TIMEOUT_SECONDS) if self.action == "touch" else self.timeout
+ self.error_occurred.emit(f"{self.action} timed out after {timeout_value:g}s")
+ else:
+ self.error_occurred.emit(f"{self.action} RPC failed: {exc.code().name}")
+ except Exception as exc:
+ self.error_occurred.emit(str(exc))
+ finally:
+ if client is not None:
+ client.close()
+
+ def _decode_to_qimage(self, frame) -> QtGui.QImage:
+ if not frame.data:
+ raise RuntimeError("Empty RGB image payload")
+
+ raw_image = self._decode_raw_qimage(frame)
+ if raw_image is not None:
+ return raw_image
+
+ file_image = QtGui.QImage.fromData(frame.data)
+ if not file_image.isNull():
+ return file_image
+
+ if frame.codec:
+ decoded_image = self._decode_with_av(frame)
+ if decoded_image is not None:
+ return decoded_image
+
+ raise RuntimeError(
+ "Failed to decode RGB image payload: "
+ f"codec={frame.codec or '-'}, "
+ f"type={pb.FrameData.FrameType.Name(frame.type)}, "
+ f"width={frame.width}, height={frame.height}, bytes={len(frame.data)}"
+ )
+
+ def _decode_raw_qimage(self, frame) -> Optional[QtGui.QImage]:
+ if frame.width <= 0 or frame.height <= 0:
+ return None
+
+ if frame.type == pb.FrameData.U8C3:
+ expected_size = frame.width * frame.height * 3
+ if len(frame.data) != expected_size:
+ return None
+ # The current C++ service returns raw cv::Mat bytes, which are BGR for CV_8UC3.
+ image = QtGui.QImage(
+ frame.data,
+ frame.width,
+ frame.height,
+ 3 * frame.width,
+ QtGui.QImage.Format_RGB888,
+ )
+ return image.rgbSwapped().copy()
+
+ if frame.type == pb.FrameData.U8C1:
+ expected_size = frame.width * frame.height
+ if len(frame.data) != expected_size:
+ return None
+ return QtGui.QImage(
+ frame.data,
+ frame.width,
+ frame.height,
+ frame.width,
+ QtGui.QImage.Format_Grayscale8,
+ ).copy()
+
+ return None
+
+ def _decode_with_av(self, frame) -> Optional[QtGui.QImage]:
+ if av is None:
+ raise RuntimeError(f"PyAV unavailable: {AV_IMPORT_ERROR}")
+
+ codec_names = self._codec_candidates(frame.codec)
+ packet = av.Packet(frame.data)
+ last_error = None
+
+ for codec_name in codec_names:
+ try:
+ decoder = av.CodecContext.create(codec_name, "r")
+ frames = decoder.decode(packet)
+ if not frames:
+ continue
+
+ image = frames[0].to_ndarray(format="rgb24")
+ return QtGui.QImage(
+ image.data,
+ image.shape[1],
+ image.shape[0],
+ 3 * image.shape[1],
+ QtGui.QImage.Format_RGB888,
+ ).copy()
+ except Exception as exc:
+ last_error = exc
+
+ if last_error is not None:
+ raise RuntimeError(
+ f"PyAV decode failed for codec={frame.codec!r}: {last_error}"
+ )
+ return None
+
+ def _codec_candidates(self, codec: str) -> list[str]:
+ normalized = (codec or "").strip().lower()
+ if normalized in {"", "none", "raw"}:
+ return []
+ aliases = {
+ "h265": ["hevc", "h265"],
+ "hevc": ["hevc", "h265"],
+ "h.265": ["hevc", "h265"],
+ "h264": ["h264"],
+ "avc": ["h264"],
+ "h.264": ["h264"],
+ "jpeg": ["mjpeg", "jpeg"],
+ "jpg": ["mjpeg", "jpeg"],
+ "mjpeg": ["mjpeg", "jpeg"],
+ }
+ return aliases.get(normalized, [normalized] if normalized else [])
+
+
+class RGBImagePage(QtWidgets.QWidget):
+ def __init__(
+ self,
+ address_provider: Optional[Callable[[], str]] = None,
+ default_device_id: str = "right_hand_cam",
+ parent=None,
+ ):
+ super().__init__(parent)
+ self.address_provider = address_provider
+ self.worker: Optional[CameraRpcWorker] = None
+ self.pending_action = None
+ self.camera_started = False
+ self.current_image: Optional[QtGui.QImage] = None
+ self.current_uv = None
+ self._touch_started_at: Optional[float] = None
+ self.save_dir = Path("./captured_images")
+ self.save_dir.mkdir(exist_ok=True)
+ self._create_ui(default_device_id)
+
+ def _create_ui(self, default_device_id: str):
+ layout = QtWidgets.QVBoxLayout(self)
+
+ settings_group = QtWidgets.QGroupBox("RGB Image")
+ settings_layout = QtWidgets.QGridLayout(settings_group)
+
+ settings_layout.addWidget(QtWidgets.QLabel("Device ID:"), 0, 0)
+ self.device_id = QtWidgets.QLineEdit(default_device_id)
+ settings_layout.addWidget(self.device_id, 0, 1)
+
+ if self.address_provider is None:
+ settings_layout.addWidget(QtWidgets.QLabel("Server IP:"), 0, 2)
+ self.server_ip = QtWidgets.QLineEdit("192.168.0.222")
+ settings_layout.addWidget(self.server_ip, 0, 3)
+
+ settings_layout.addWidget(QtWidgets.QLabel("Port:"), 0, 4)
+ self.server_port = QtWidgets.QLineEdit("50052")
+ self.server_port.setMaximumWidth(100)
+ settings_layout.addWidget(self.server_port, 0, 5)
+ else:
+ settings_layout.addWidget(QtWidgets.QLabel("Server:"), 0, 2)
+ self.address_label = QtWidgets.QLabel("")
+ self.address_label.setStyleSheet("font-weight: bold;")
+ settings_layout.addWidget(self.address_label, 0, 3, 1, 3)
+
+ settings_layout.addWidget(QtWidgets.QLabel("Timeout (s):"), 1, 0)
+ self.timeout_spin = QtWidgets.QSpinBox()
+ self.timeout_spin.setRange(1, 10)
+ self.timeout_spin.setValue(10)
+ settings_layout.addWidget(self.timeout_spin, 1, 1)
+
+ settings_layout.addWidget(QtWidgets.QLabel("Save Dir:"), 2, 0)
+ self.save_dir_edit = QtWidgets.QLineEdit(str(self.save_dir))
+ settings_layout.addWidget(self.save_dir_edit, 2, 1, 1, 4)
+
+ self.browse_btn = QtWidgets.QPushButton("Browse")
+ self.browse_btn.clicked.connect(self._browse_dir)
+ settings_layout.addWidget(self.browse_btn, 2, 5)
+
+ button_layout = QtWidgets.QHBoxLayout()
+ button_layout.addWidget(QtWidgets.QLabel("Camera:"))
+ self.camera_switch = ToggleSwitch()
+ self.camera_switch.toggled.connect(self._on_camera_switch_toggled)
+ button_layout.addWidget(self.camera_switch)
+ button_layout.addSpacing(12)
+
+ self.fetch_btn = QtWidgets.QPushButton("Get RGB Image")
+ self.fetch_btn.clicked.connect(self._fetch_image)
+ button_layout.addWidget(self.fetch_btn)
+
+ self.save_btn = QtWidgets.QPushButton("Save Image")
+ self.save_btn.clicked.connect(self._save_image)
+ self.save_btn.setEnabled(False)
+ button_layout.addWidget(self.save_btn)
+ button_layout.addStretch()
+
+ self.touch_btn = QtWidgets.QPushButton("Touch")
+ self.touch_btn.clicked.connect(self._touch_uv)
+ self.touch_btn.setEnabled(False)
+ button_layout.addWidget(self.touch_btn)
+
+ self.uv_label = QtWidgets.QLabel("UV: -")
+ button_layout.addWidget(self.uv_label)
+
+ button_layout.addWidget(QtWidgets.QLabel("max_force:"))
+ self.max_force_spin = QtWidgets.QDoubleSpinBox()
+ self.max_force_spin.setRange(-1.0, 1000.0)
+ self.max_force_spin.setDecimals(4)
+ self.max_force_spin.setSingleStep(0.0001)
+ self.max_force_spin.setValue(-1.0)
+ self.max_force_spin.setMaximumWidth(110)
+ button_layout.addWidget(self.max_force_spin)
+ settings_layout.addLayout(button_layout, 3, 0, 1, 6)
+
+ layout.addWidget(settings_group)
+
+ status_group = QtWidgets.QGroupBox("Image Status")
+ status_layout = QtWidgets.QHBoxLayout(status_group)
+ self.status_label = QtWidgets.QLabel("Idle")
+ self.status_label.setStyleSheet("color: gray;")
+ status_layout.addWidget(self.status_label)
+
+ status_layout.addWidget(QtWidgets.QLabel("|"))
+ self.camera_state_label = QtWidgets.QLabel("Camera: unknown")
+ status_layout.addWidget(self.camera_state_label)
+
+ status_layout.addWidget(QtWidgets.QLabel("|"))
+ self.size_label = QtWidgets.QLabel("Resolution: -")
+ status_layout.addWidget(self.size_label)
+
+ status_layout.addWidget(QtWidgets.QLabel("|"))
+ self.codec_label = QtWidgets.QLabel("Codec: -")
+ status_layout.addWidget(self.codec_label)
+
+ status_layout.addWidget(QtWidgets.QLabel("|"))
+ self.type_label = QtWidgets.QLabel("Type: -")
+ status_layout.addWidget(self.type_label)
+
+ status_layout.addWidget(QtWidgets.QLabel("|"))
+ self.data_size_label = QtWidgets.QLabel("Bytes: -")
+ status_layout.addWidget(self.data_size_label)
+ status_layout.addStretch()
+ layout.addWidget(status_group)
+
+ preview_group = QtWidgets.QGroupBox("RGB Image")
+ preview_layout = QtWidgets.QVBoxLayout(preview_group)
+ self.image_label = ImagePreviewLabel()
+ self.image_label.setAlignment(QtCore.Qt.AlignCenter)
+ self.image_label.setMinimumSize(640, 480)
+ self.image_label.setStyleSheet("background-color: black; border: 1px solid gray;")
+ self.image_label.uv_changed.connect(self._on_uv_changed)
+ preview_layout.addWidget(self.image_label)
+ layout.addWidget(preview_group, 1)
+
+ info_frame = QtWidgets.QFrame()
+ info_frame.setFrameStyle(QtWidgets.QFrame.StyledPanel | QtWidgets.QFrame.Sunken)
+ info_layout = QtWidgets.QHBoxLayout(info_frame)
+ info_label = QtWidgets.QLabel("Fetch a single RGB image through CameraService.GetRGBImage.")
+ info_label.setStyleSheet("color: gray; padding: 5px;")
+ info_layout.addWidget(info_label)
+ layout.addWidget(info_frame)
+
+ self.status_bar = QtWidgets.QStatusBar()
+ self.status_bar.showMessage("Ready")
+ layout.addWidget(self.status_bar)
+
+ def refresh_address_hint(self):
+ if self.address_provider is None or not hasattr(self, "address_label"):
+ return
+ try:
+ self.address_label.setText(self.address_provider())
+ except Exception:
+ self.address_label.setText("(app settings unavailable)")
+
+ def _browse_dir(self):
+ directory = QtWidgets.QFileDialog.getExistingDirectory(self, "Select save directory", self.save_dir_edit.text())
+ if directory:
+ self.save_dir_edit.setText(directory)
+ self.save_dir = Path(directory)
+ self.save_dir.mkdir(exist_ok=True)
+
+ def _get_server_addr(self) -> str:
+ if self.address_provider is not None:
+ return self.address_provider()
+ return f"{self.server_ip.text().strip()}:{self.server_port.text().strip()}"
+
+ def _fetch_image(self):
+ self._start_worker("get")
+
+ def _on_camera_switch_toggled(self, checked: bool):
+ self._start_worker("start" if checked else "stop")
+
+ def _touch_uv(self):
+ if self.current_uv is None:
+ QtWidgets.QMessageBox.warning(self, "Touch", "Please click the image to select UV first.")
+ return
+ self._start_worker("touch")
+
+ def _start_touch_timer(self):
+ self._touch_started_at = time.perf_counter()
+
+ def _finish_touch_timer(self) -> Optional[float]:
+ if self._touch_started_at is None:
+ return None
+ elapsed = time.perf_counter() - self._touch_started_at
+ self._touch_started_at = None
+ return elapsed
+
+ def _format_touch_elapsed(self, elapsed: Optional[float]) -> str:
+ if elapsed is None:
+ return ""
+ return f" ({elapsed:.3f}s)"
+
+ def _start_worker(self, action: str):
+ if self.worker is not None and self.worker.isRunning():
+ return
+
+ self.refresh_address_hint()
+ self.pending_action = action
+ self.camera_switch.setEnabled(False)
+ self.fetch_btn.setEnabled(False)
+ self.touch_btn.setEnabled(False)
+ self.save_btn.setEnabled(False)
+
+ if action == "start":
+ self.status_label.setText("Starting")
+ self.status_label.setStyleSheet("color: orange;")
+ self.status_bar.showMessage("Calling StartCamera...")
+ elif action == "stop":
+ self.status_label.setText("Stopping")
+ self.status_label.setStyleSheet("color: orange;")
+ self.status_bar.showMessage("Calling StopCamera...")
+ elif action == "touch":
+ self._start_touch_timer()
+ self.status_label.setText("Touching")
+ self.status_label.setStyleSheet("color: orange;")
+ if self.current_uv is None:
+ self.status_bar.showMessage("No UV selected")
+ else:
+ self.status_bar.showMessage(
+ f"Calling touch(u={self.current_uv[0]}, v={self.current_uv[1]}, max_force={self.max_force_spin.value():.4f}, timeout={max(float(self.timeout_spin.value()), TOUCH_TIMEOUT_SECONDS):g}s)..."
+ )
+ else:
+ self.status_label.setText("Fetching")
+ self.status_label.setStyleSheet("color: orange;")
+ self.status_bar.showMessage("Calling GetRGBImage...")
+
+ u = None
+ v = None
+ max_force = None
+ if action == "touch" and self.current_uv is not None:
+ u, v = self.current_uv
+ max_force = float(self.max_force_spin.value())
+
+ self.worker = CameraRpcWorker(
+ address=self._get_server_addr(),
+ timeout=self.timeout_spin.value(),
+ device_id=self.device_id.text().strip(),
+ action=action,
+ u=u,
+ v=v,
+ max_force=max_force,
+ )
+ self.worker.image_ready.connect(self._on_image_ready)
+ self.worker.command_done.connect(self._on_command_done)
+ self.worker.error_occurred.connect(self._on_error)
+ self.worker.finished.connect(self._on_worker_finished)
+ self.worker.start()
+
+ def _on_image_ready(self, image: QtGui.QImage, metadata: dict):
+ self.current_image = image
+ self.current_uv = None
+ self.image_label.set_image(image)
+ self.uv_label.setText("UV: -")
+ self.status_label.setText("Fetched")
+ self.status_label.setStyleSheet("color: green;")
+ self.size_label.setText(f"Resolution: {metadata['width']}x{metadata['height']}")
+ self.codec_label.setText(f"Codec: {metadata['codec'] or '-'}")
+ self.type_label.setText(f"Type: {metadata['frame_type']}")
+ self.data_size_label.setText(f"Bytes: {metadata['data_size']}")
+ self.status_bar.showMessage("GetRGBImage completed", 3000)
+ self.save_btn.setEnabled(True)
+
+ def _on_uv_changed(self, uv):
+ self.current_uv = uv
+ if self.worker is None:
+ self.touch_btn.setEnabled(uv is not None)
+ if uv is None:
+ self.uv_label.setText("UV: -")
+ return
+ u, v = uv
+ self.uv_label.setText(f"UV: ({u}, {v})")
+
+ def _on_command_done(self, action: str):
+ if action == "start":
+ self.camera_started = True
+ self._set_camera_switch_checked(True)
+ self.camera_state_label.setText("Camera: started")
+ self.status_label.setText("Started")
+ self.status_label.setStyleSheet("color: green;")
+ self.status_bar.showMessage("StartCamera completed", 3000)
+ return
+
+ if action == "stop":
+ self.camera_started = False
+ self._set_camera_switch_checked(False)
+ self.camera_state_label.setText("Camera: stopped")
+ self.status_label.setText("Stopped")
+ self.status_label.setStyleSheet("color: gray;")
+ self.status_bar.showMessage("StopCamera completed", 3000)
+ return
+
+ if action == "touch":
+ elapsed = self._finish_touch_timer()
+ elapsed_text = self._format_touch_elapsed(elapsed)
+ self.status_label.setText(f"Touched{elapsed_text}")
+ self.status_label.setStyleSheet("color: green;")
+ if self.current_uv is None:
+ self.status_bar.showMessage(f"touch completed{elapsed_text}", 3000)
+ else:
+ self.status_bar.showMessage(
+ f"touch completed at UV ({self.current_uv[0]}, {self.current_uv[1]}){elapsed_text}",
+ 3000,
+ )
+
+ def _on_error(self, message: str):
+ if self.pending_action == "start":
+ self.camera_started = False
+ self._set_camera_switch_checked(False)
+ self.camera_state_label.setText("Camera: stopped")
+ elif self.pending_action == "stop":
+ self.camera_started = True
+ self._set_camera_switch_checked(True)
+ self.camera_state_label.setText("Camera: started")
+ if self.pending_action == "touch":
+ elapsed = self._finish_touch_timer()
+ elapsed_text = self._format_touch_elapsed(elapsed)
+ message = f"{message}{elapsed_text}"
+ self.status_label.setText(f"Touch Failed{elapsed_text}")
+ else:
+ self.status_label.setText("Error")
+ self.status_label.setStyleSheet("color: red;")
+ self.status_bar.showMessage(message, 5000)
+ QtWidgets.QMessageBox.critical(self, "RGB Image Error", message)
+
+ def _on_worker_finished(self):
+ self.camera_switch.setEnabled(True)
+ self.fetch_btn.setEnabled(True)
+ self.touch_btn.setEnabled(self.current_uv is not None)
+ if self.current_image is not None:
+ self.save_btn.setEnabled(True)
+ self.pending_action = None
+ self.worker = None
+
+ def _set_camera_switch_checked(self, checked: bool):
+ blocker = QtCore.QSignalBlocker(self.camera_switch)
+ self.camera_switch.setChecked(checked)
+ del blocker
+
+ def _save_image(self):
+ if self.current_image is None:
+ return
+
+ self.save_dir = Path(self.save_dir_edit.text())
+ self.save_dir.mkdir(parents=True, exist_ok=True)
+ timestamp = QtCore.QDateTime.currentDateTime().toString("yyyyMMdd_HHmmss")
+ target = self.save_dir / f"rgb_{self.device_id.text().strip()}_{timestamp}.png"
+ if self.current_image.save(str(target)):
+ self.status_bar.showMessage(f"Saved to {target.name}", 3000)
+ else:
+ QtWidgets.QMessageBox.critical(self, "Save Error", "Failed to save image.")
+
+ def shutdown(self):
+ worker = self.worker
+ if worker and worker.isRunning():
+ worker.wait(2000)