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 + + + + + + + \ 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 + + + + \ 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 + + + + \ 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د=LP JV O/KPn Pa!\/QS +uvQ ePr;l(ccPYKh͙RI{x-CF2lmA˱1L e9"+ 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Ĕ΁ ^P2 WY)ɠ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-Ƈ{wM e`ơm+ԯMk.i؍L2F:r, !UЏ).i(}(5Aa%I:3 :4r,v϶*7 &\ҰtJzRF:r,aPHVRe4b% qKE +th(O$˙93mJHۛy thXB t8E6oF* +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:IzOAa JFTl+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 url td&"?? 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~͘`/Ʊ1 6盘2Abjc GzN@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).B EPA)Gu1qmAi rl4SFgNbYs t#+vl(cNSSdC*#4ëiSRl mAʱ/,ѹ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:Q A%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@a J2{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 @@ + + + + + + + + 2026-03-18T17:39:49.905934 + image/svg+xml + + + Matplotlib v3.5.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + 2026-03-18T17:39:49.935930 + image/svg+xml + + + Matplotlib v3.5.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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د=LP JV O/KPn Pa!\/QS +uvQ ePr;l(ccPYKh͙RI{x-CF2lmA˱1L e9"+ 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Ĕ΁ ^P2 WY)ɠ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-Ƈ{wM e`ơm+ԯMk.i؍L2F:r, !UЏ).i(}(5Aa%I:3 :4r,v϶*7 &\ҰtJzRF:r,aPHVRe4b% qKE +th(O$˙93mJHۛy thXB t8E6oF* +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:IzOAa JFTl+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 url td&"?? 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~͘`/Ʊ1 6盘2Abjc GzN@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).B EPA)Gu1qmAi rl4SFgNbYs t#+vl(cNSSdC*#4ëiSRl mAʱ/,ѹ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:Q A%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@a J2{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)