ezgo 0.0.6__tar.gz → 0.0.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ezgo-0.0.6/src/ezgo.egg-info → ezgo-0.0.8}/PKG-INFO +11 -1
- {ezgo-0.0.6 → ezgo-0.0.8}/README.md +10 -0
- {ezgo-0.0.6 → ezgo-0.0.8}/pyproject.toml +1 -1
- ezgo-0.0.8/src/ezgo/__init__.py +41 -0
- ezgo-0.0.8/src/ezgo/ezcamera.py +181 -0
- ezgo-0.0.8/src/ezgo/eztk.py +219 -0
- {ezgo-0.0.6 → ezgo-0.0.8/src/ezgo.egg-info}/PKG-INFO +11 -1
- {ezgo-0.0.6 → ezgo-0.0.8}/src/ezgo.egg-info/SOURCES.txt +2 -0
- ezgo-0.0.6/src/ezgo/__init__.py +0 -87
- {ezgo-0.0.6 → ezgo-0.0.8}/LICENSE +0 -0
- {ezgo-0.0.6 → ezgo-0.0.8}/setup.cfg +0 -0
- {ezgo-0.0.6 → ezgo-0.0.8}/src/ezgo/camera.py +0 -0
- {ezgo-0.0.6 → ezgo-0.0.8}/src/ezgo/go2.py +0 -0
- {ezgo-0.0.6 → ezgo-0.0.8}/src/ezgo/go2_camera.py +0 -0
- {ezgo-0.0.6 → ezgo-0.0.8}/src/ezgo/go2_vui.py +0 -0
- {ezgo-0.0.6 → ezgo-0.0.8}/src/ezgo/ui.py +0 -0
- {ezgo-0.0.6 → ezgo-0.0.8}/src/ezgo.egg-info/dependency_links.txt +0 -0
- {ezgo-0.0.6 → ezgo-0.0.8}/src/ezgo.egg-info/requires.txt +0 -0
- {ezgo-0.0.6 → ezgo-0.0.8}/src/ezgo.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ezgo
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.8
|
|
4
4
|
Summary: 宇树Go2机器狗Python控制库
|
|
5
5
|
Author-email: ezgo <noreply@example.com>
|
|
6
6
|
License: MIT
|
|
@@ -359,6 +359,16 @@ pip install opencv-python numpy Pillow netifaces
|
|
|
359
359
|
|
|
360
360
|
## 更新日志
|
|
361
361
|
|
|
362
|
+
### v0.0.8 (2025-12-09)
|
|
363
|
+
**改进**:
|
|
364
|
+
- 🔧 移除自动依赖检查逻辑,避免导入时的警告提示
|
|
365
|
+
- 🔧 用户按需使用功能,不再强制检查所有依赖
|
|
366
|
+
- 🔧 提升用户体验,减少不必要的警告信息
|
|
367
|
+
|
|
368
|
+
**修复**:
|
|
369
|
+
- 🐛 修复使用eztk时频繁提示unitree-sdk2py依赖的问题
|
|
370
|
+
- 🐛 修复导入库时自动检查所有模块依赖的逻辑
|
|
371
|
+
|
|
362
372
|
### v0.0.5 (2025-12-04)
|
|
363
373
|
**新增功能**:
|
|
364
374
|
- ✨ 新增15种高级运动模式和特技动作
|
|
@@ -324,6 +324,16 @@ pip install opencv-python numpy Pillow netifaces
|
|
|
324
324
|
|
|
325
325
|
## 更新日志
|
|
326
326
|
|
|
327
|
+
### v0.0.8 (2025-12-09)
|
|
328
|
+
**改进**:
|
|
329
|
+
- 🔧 移除自动依赖检查逻辑,避免导入时的警告提示
|
|
330
|
+
- 🔧 用户按需使用功能,不再强制检查所有依赖
|
|
331
|
+
- 🔧 提升用户体验,减少不必要的警告信息
|
|
332
|
+
|
|
333
|
+
**修复**:
|
|
334
|
+
- 🐛 修复使用eztk时频繁提示unitree-sdk2py依赖的问题
|
|
335
|
+
- 🐛 修复导入库时自动检查所有模块依赖的逻辑
|
|
336
|
+
|
|
327
337
|
### v0.0.5 (2025-12-04)
|
|
328
338
|
**新增功能**:
|
|
329
339
|
- ✨ 新增15种高级运动模式和特技动作
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
ezgo - 宇树Go2机器狗Python控制库
|
|
6
|
+
|
|
7
|
+
这是一个用于控制宇树Go2机器狗的Python库,提供了简单易用的API接口。
|
|
8
|
+
支持运动控制、视频流获取、UI界面等功能。
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
__version__ = "0.0.8"
|
|
12
|
+
__author__ = "ezgo"
|
|
13
|
+
__email__ = ""
|
|
14
|
+
__license__ = "MIT"
|
|
15
|
+
|
|
16
|
+
# 导入主要类
|
|
17
|
+
try:
|
|
18
|
+
from .go2 import Go2
|
|
19
|
+
from .camera import Camera
|
|
20
|
+
from .ui import APP
|
|
21
|
+
from .go2_camera import Go2Camera
|
|
22
|
+
from .go2_vui import Go2VUI
|
|
23
|
+
except ImportError as e:
|
|
24
|
+
print(f"警告: 无法导入部分模块: {e}")
|
|
25
|
+
print("请确保已安装所有依赖包")
|
|
26
|
+
Go2 = None
|
|
27
|
+
Camera = None
|
|
28
|
+
APP = None
|
|
29
|
+
Go2Camera = None
|
|
30
|
+
Go2VUI = None
|
|
31
|
+
|
|
32
|
+
# 定义公开的API
|
|
33
|
+
__all__ = [
|
|
34
|
+
"Go2",
|
|
35
|
+
"Camera",
|
|
36
|
+
"APP",
|
|
37
|
+
"Go2Camera",
|
|
38
|
+
"Go2VUI",
|
|
39
|
+
"__version__"
|
|
40
|
+
]
|
|
41
|
+
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import cv2
|
|
2
|
+
from PIL import Image, ImageTk
|
|
3
|
+
import platform
|
|
4
|
+
import time
|
|
5
|
+
import queue
|
|
6
|
+
import threading
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Camera:
|
|
10
|
+
def __init__(self, index=0, width=640, height=480, fps=30,):
|
|
11
|
+
"""
|
|
12
|
+
初始化摄像头对象。
|
|
13
|
+
|
|
14
|
+
参数:
|
|
15
|
+
- index: 摄像头索引,默认值为 0。
|
|
16
|
+
- width: 视频宽度,默认值为 640。
|
|
17
|
+
- height: 视频高度,默认值为 480。
|
|
18
|
+
- fps: 视频帧率,默认值为 30。
|
|
19
|
+
"""
|
|
20
|
+
# 基础参数
|
|
21
|
+
self.index = index
|
|
22
|
+
self.width = width
|
|
23
|
+
self.height = height
|
|
24
|
+
self.fps = fps
|
|
25
|
+
|
|
26
|
+
# 状态控制
|
|
27
|
+
self.is_opened = False # 摄像头是否成功打开
|
|
28
|
+
self.is_running = False # 线程退出标志
|
|
29
|
+
self.first_frame_ready = threading.Event() # 第一帧就绪事件
|
|
30
|
+
|
|
31
|
+
# 资源管理
|
|
32
|
+
self.cap = None
|
|
33
|
+
self.capture_thread = None # 读帧线程对象
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# 双缓冲:两个缓冲区
|
|
37
|
+
self.buffer_write = None # 写入缓冲区
|
|
38
|
+
self.buffer_read = None # 读取缓冲区
|
|
39
|
+
self.buffer_lock = threading.Lock() # 用于交换缓冲区的锁
|
|
40
|
+
|
|
41
|
+
# 线程锁
|
|
42
|
+
# self.lock = threading.Lock()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def open(self, timeout=5):
|
|
47
|
+
"""
|
|
48
|
+
打开摄像头。
|
|
49
|
+
若在 Linux 系统下,启用 V4L2 硬件加速和 MJPG 压缩格式。
|
|
50
|
+
|
|
51
|
+
参数:
|
|
52
|
+
- timeout: 等待第一帧图像的超时时间,默认值为 5 秒。
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
if self.is_opened:
|
|
56
|
+
print(f"摄像头 {self.index} 已打开")
|
|
57
|
+
return True
|
|
58
|
+
|
|
59
|
+
if platform.system() == "Linux":
|
|
60
|
+
self.cap = cv2.VideoCapture(self.index, cv2.CAP_V4L2)
|
|
61
|
+
# 尝试设置MJPG格式(硬件加速)
|
|
62
|
+
fourcc = cv2.VideoWriter_fourcc(*'MJPG')
|
|
63
|
+
self.cap.set(cv2.CAP_PROP_FOURCC, fourcc)
|
|
64
|
+
self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) # 减少摄像头缓冲区,降低延迟
|
|
65
|
+
elif platform.system() == "Windows":
|
|
66
|
+
self.cap = cv2.VideoCapture(self.index, cv2.CAP_DSHOW) # 替代默认的CAP_MSMF,降低延迟
|
|
67
|
+
fourcc = cv2.VideoWriter_fourcc(*'MJPG')
|
|
68
|
+
self.cap.set(cv2.CAP_PROP_FOURCC, fourcc)
|
|
69
|
+
else: # macOS
|
|
70
|
+
self.cap = cv2.VideoCapture(self.index)
|
|
71
|
+
self.cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*'MJPG'))
|
|
72
|
+
|
|
73
|
+
if not self.cap.isOpened():
|
|
74
|
+
raise Exception("无法打开摄像头")
|
|
75
|
+
|
|
76
|
+
# 仅当实际分辨率不一致时才设置分辨率
|
|
77
|
+
actual_width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
|
78
|
+
actual_height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
|
79
|
+
if actual_width != self.width or actual_height != self.height:
|
|
80
|
+
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, self.width)
|
|
81
|
+
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self.height)
|
|
82
|
+
|
|
83
|
+
# 重置状态
|
|
84
|
+
self.first_frame_ready.clear()
|
|
85
|
+
self.buffer_write = None
|
|
86
|
+
self.buffer_read = None
|
|
87
|
+
|
|
88
|
+
# 启动读帧线程
|
|
89
|
+
self.is_running = True
|
|
90
|
+
self.capture_thread = threading.Thread(target=self._capture_thread, daemon=True)
|
|
91
|
+
self.capture_thread.start()
|
|
92
|
+
|
|
93
|
+
# 等待第一帧就绪(带超时)
|
|
94
|
+
if not self.first_frame_ready.wait(timeout=timeout):
|
|
95
|
+
self.close()
|
|
96
|
+
raise TimeoutError(f"等待第一帧超时({timeout}秒)")
|
|
97
|
+
|
|
98
|
+
self.is_opened = True
|
|
99
|
+
|
|
100
|
+
return self
|
|
101
|
+
|
|
102
|
+
def _capture_thread(self):
|
|
103
|
+
"""读取图像帧线程"""
|
|
104
|
+
frame_interval = 1.0 / self.fps # 理论帧间隔
|
|
105
|
+
first_frame = True
|
|
106
|
+
next_frame_time = time.time() # 初始化
|
|
107
|
+
while self.is_running and self.cap.isOpened():
|
|
108
|
+
start_time = time.time()
|
|
109
|
+
# 读取帧
|
|
110
|
+
ret, frame = self.cap.read()
|
|
111
|
+
if not ret:
|
|
112
|
+
time.sleep(0.001)
|
|
113
|
+
continue
|
|
114
|
+
|
|
115
|
+
# 写入缓冲区
|
|
116
|
+
with self.buffer_lock:
|
|
117
|
+
# 先交换缓冲区:把已写好的交给读缓冲区
|
|
118
|
+
self.buffer_write, self.buffer_read = self.buffer_read, self.buffer_write
|
|
119
|
+
# 再写入新帧到空的写缓冲区
|
|
120
|
+
self.buffer_write = frame
|
|
121
|
+
|
|
122
|
+
# 标记第一帧就绪
|
|
123
|
+
if first_frame and self.buffer_read is not None:
|
|
124
|
+
self.first_frame_ready.set()
|
|
125
|
+
first_frame = False
|
|
126
|
+
|
|
127
|
+
# 基于绝对时间的帧率控制,避免累积误差
|
|
128
|
+
next_frame_time += frame_interval
|
|
129
|
+
sleep_time = max(0, next_frame_time - time.time())
|
|
130
|
+
# 短睡眠用time.sleep,长睡眠用cv2.waitKey(更低CPU占用)
|
|
131
|
+
if sleep_time > 0.01:
|
|
132
|
+
cv2.waitKey(int(sleep_time * 1000)) # 毫秒级,释放GIL
|
|
133
|
+
else:
|
|
134
|
+
time.sleep(sleep_time)
|
|
135
|
+
|
|
136
|
+
def read(self):
|
|
137
|
+
"""读取最新帧"""
|
|
138
|
+
frame = None
|
|
139
|
+
with self.buffer_lock:
|
|
140
|
+
if self.buffer_read is not None:
|
|
141
|
+
frame = self.buffer_read # 先引用,不拷贝
|
|
142
|
+
return frame.copy() if frame is not None else None # 仅当有数据时拷贝
|
|
143
|
+
|
|
144
|
+
def save_image(self, frame, filename="frame.jpg"):
|
|
145
|
+
"""保存当前帧到文件"""
|
|
146
|
+
if frame is None:
|
|
147
|
+
raise ValueError("无法保存空帧")
|
|
148
|
+
cv2.imwrite(filename, frame)
|
|
149
|
+
|
|
150
|
+
def resize(self, frame, size=(224, 224)):
|
|
151
|
+
"""将图像调整为指定尺寸"""
|
|
152
|
+
if frame is None:
|
|
153
|
+
return None
|
|
154
|
+
return cv2.resize(frame, size, interpolation=cv2.INTER_LINEAR)
|
|
155
|
+
|
|
156
|
+
def crop_to_square(self, frame):
|
|
157
|
+
"""将图像裁剪为正方形"""
|
|
158
|
+
if frame is None:
|
|
159
|
+
return None
|
|
160
|
+
h, w = frame.shape[:2]
|
|
161
|
+
min_dim = min(h, w)
|
|
162
|
+
y = (h - min_dim) // 2
|
|
163
|
+
x = (w - min_dim) // 2
|
|
164
|
+
return frame[y:y+min_dim, x:x+min_dim]
|
|
165
|
+
|
|
166
|
+
def close(self):
|
|
167
|
+
"""手动释放资源"""
|
|
168
|
+
self.is_running = False # 停止读帧线程
|
|
169
|
+
self.is_opened = False
|
|
170
|
+
|
|
171
|
+
if self.capture_thread is not None:
|
|
172
|
+
self.capture_thread.join(timeout=1.0) # 等待线程退出
|
|
173
|
+
self.capture_thread = None
|
|
174
|
+
|
|
175
|
+
if self.cap is not None:
|
|
176
|
+
self.cap.release()
|
|
177
|
+
self.cap = None
|
|
178
|
+
|
|
179
|
+
def __del__(self):
|
|
180
|
+
"""释放资源"""
|
|
181
|
+
self.close()
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
import numpy as np
|
|
3
|
+
import cv2
|
|
4
|
+
from PIL import Image, ImageTk
|
|
5
|
+
import platform
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
class EasyTk:
|
|
9
|
+
def __init__(self, title="APP", size=None):
|
|
10
|
+
"""
|
|
11
|
+
初始化EasyTk窗口
|
|
12
|
+
:param title: 窗口标题
|
|
13
|
+
:param size: 窗口大小(格式:"宽度x高度",例如"480x800")
|
|
14
|
+
"""
|
|
15
|
+
self.root = tk.Tk()
|
|
16
|
+
self.root.title(title)
|
|
17
|
+
self.root.geometry(size)
|
|
18
|
+
|
|
19
|
+
if platform.system() == "Linux": # 灵芯派默认设置
|
|
20
|
+
if size is None:
|
|
21
|
+
self.root.geometry("480x800") # 窗口分辨率480*800
|
|
22
|
+
self.root.resizable(False, False) # 固定窗口大小
|
|
23
|
+
self.root.attributes('-fullscreen', True) # 全屏
|
|
24
|
+
self.root.config(cursor="none") # 隐藏鼠标
|
|
25
|
+
|
|
26
|
+
# self.root.update_idletasks() # 强制立即应用全屏,避免闪烁/延迟
|
|
27
|
+
|
|
28
|
+
self._image = None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def add_frame(self, master=None, **kwargs):
|
|
32
|
+
"""
|
|
33
|
+
添加一个框架(LabelFrame)到窗口中
|
|
34
|
+
|
|
35
|
+
:param master: 父容器(默认是root)
|
|
36
|
+
:param kwargs: 组件配置和布局参数
|
|
37
|
+
组件配置常用参数:
|
|
38
|
+
- width: 宽度(像素),如果为空则根据内容自适应
|
|
39
|
+
- height: 高度(像素),如果为空则根据内容自适应
|
|
40
|
+
- bg: 背景颜色,例如"white"
|
|
41
|
+
组件布局常用参数:
|
|
42
|
+
- side: 组件在父容器中的位置,tk.TOP(上)、tk.BOTTOM(下)、tk.LEFT(左)、tk.RIGHT(右)
|
|
43
|
+
- fill: 组件在父容器中的填充方式,tk.BOTH(填充父容器)、tk.X(填充水平方向)、tk.Y(填充垂直方向)
|
|
44
|
+
- expand: 是否允许组件在父容器中扩展,True(允许)、False(不允许)
|
|
45
|
+
- anchor: 组件在父容器中的对齐方式,tk.N(北)、tk.S(南)、tk.E(东)、tk.W(西)、tk.CENTER(居中)
|
|
46
|
+
- padx: 组件在父容器中的水平间距,单位像素
|
|
47
|
+
- pady: 组件在父容器中的垂直间距,单位像素
|
|
48
|
+
|
|
49
|
+
:return: 新添加的框架(LabelFrame)
|
|
50
|
+
:rtype: LabelFrame
|
|
51
|
+
"""
|
|
52
|
+
kwargs['master'] = master if master else self.root
|
|
53
|
+
pack_kwargs = {
|
|
54
|
+
'side': kwargs.pop('side', tk.TOP),
|
|
55
|
+
'fill': kwargs.pop('fill', tk.BOTH),
|
|
56
|
+
'expand': kwargs.pop('expand', False if kwargs['master'] == self.root else True),
|
|
57
|
+
'anchor': kwargs.pop('anchor', tk.CENTER),
|
|
58
|
+
'padx': kwargs.pop('padx', 0),
|
|
59
|
+
'pady': kwargs.pop('pady', 0)}
|
|
60
|
+
|
|
61
|
+
frame = tk.Frame(**kwargs)
|
|
62
|
+
if kwargs.get('width', None) and kwargs.get('height', None):
|
|
63
|
+
pack_kwargs['fill'] = tk.NONE
|
|
64
|
+
elif kwargs.get('width', None):
|
|
65
|
+
pack_kwargs['fill'] = tk.Y
|
|
66
|
+
elif kwargs.get('height', None):
|
|
67
|
+
pack_kwargs['fill'] = tk.X
|
|
68
|
+
|
|
69
|
+
frame.pack_propagate(False if (kwargs.get('width', None) or kwargs.get('height', None)) else True)
|
|
70
|
+
frame.pack(**pack_kwargs)
|
|
71
|
+
|
|
72
|
+
return frame
|
|
73
|
+
|
|
74
|
+
def add_label(self, master=None, **kwargs):
|
|
75
|
+
"""
|
|
76
|
+
添加一个标签(Label)到窗口中, 可以显示文本或图片
|
|
77
|
+
|
|
78
|
+
:param master: 父容器(默认是root)
|
|
79
|
+
:param kwargs: 组件配置和布局参数
|
|
80
|
+
组件配置常用参数:
|
|
81
|
+
- text: 显示的文本内容
|
|
82
|
+
- image: 显示的图片路径、opencv图像(np.array)、PIL.Image.Image对象
|
|
83
|
+
- font: 字体设置,例如("SimHei", 24)
|
|
84
|
+
- fg: 字体颜色,例如"red"
|
|
85
|
+
- bg: 背景颜色,例如"white"
|
|
86
|
+
- width: 宽度(像素),如果为空则根据内容自适应
|
|
87
|
+
- height: 高度(像素),如果为空则根据内容自适应
|
|
88
|
+
- wraplength: 文本换行长度,单位像素(默认0,不换行)
|
|
89
|
+
组件布局常用参数:
|
|
90
|
+
- side: 组件在父容器中的位置,tk.TOP(上)、tk.BOTTOM(下)、tk.LEFT(左)、tk.RIGHT(右)
|
|
91
|
+
- fill: 组件在父容器中的填充方式,tk.BOTH(填充父容器)、tk.X(填充水平方向)、tk.Y(填充垂直方向)
|
|
92
|
+
- expand: 是否允许组件在父容器中扩展,True(允许)、False(不允许)
|
|
93
|
+
- anchor: 组件在父容器中的对齐方式,tk.N(北)、tk.S(南)、tk.E(东)、tk.W(西)、tk.CENTER(居中)
|
|
94
|
+
- padx: 组件在父容器中的水平间距,单位像素
|
|
95
|
+
- pady: 组件在父容器中的垂直间距,单位像素
|
|
96
|
+
:return: 新添加的标签(Label)
|
|
97
|
+
:rtype: Label
|
|
98
|
+
"""
|
|
99
|
+
# 在Label外部添加一个Frame,用于布局控制
|
|
100
|
+
master = master if master else self.root
|
|
101
|
+
_kwargs = {
|
|
102
|
+
"side" : kwargs.pop('side', tk.TOP), # 堆叠方向
|
|
103
|
+
"fill" : kwargs.pop('fill', tk.BOTH), # 填充方向
|
|
104
|
+
"expand" : kwargs.pop('expand', False if master == self.root else True) , # 是否展开填充
|
|
105
|
+
"anchor" : kwargs.pop('anchor', tk.CENTER),
|
|
106
|
+
"padx" : kwargs.pop('padx', 0),
|
|
107
|
+
"pady" : kwargs.pop('pady', 0),
|
|
108
|
+
"width" : kwargs.pop('width', None),
|
|
109
|
+
"height" : kwargs.pop('height', None),
|
|
110
|
+
"bg": kwargs.get('bg', None),
|
|
111
|
+
}
|
|
112
|
+
_frame = self.add_frame(master, **_kwargs)
|
|
113
|
+
|
|
114
|
+
# 在Frame内部创建Label组件
|
|
115
|
+
image = kwargs.pop('image', None)
|
|
116
|
+
label = tk.Label(_frame, **kwargs)
|
|
117
|
+
if image is not None: # 配置图片
|
|
118
|
+
self.config(label, image=image)
|
|
119
|
+
label.pack(fill=tk.BOTH, expand=True) # 放置组件
|
|
120
|
+
return label
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def add_button(self, master=None, **kwargs):
|
|
124
|
+
"""
|
|
125
|
+
添加一个按钮(Button)到窗口中, 可以显示文本或图片
|
|
126
|
+
|
|
127
|
+
:param master: 父容器(默认是root)
|
|
128
|
+
:param kwargs: 组件配置和布局参数
|
|
129
|
+
组件配置常用参数:
|
|
130
|
+
- text: 显示的文本内容
|
|
131
|
+
- image: 显示的图片路径、opencv图像(np.array)、PIL.Image.Image对象
|
|
132
|
+
- font: 字体设置,例如("SimHei", 24)
|
|
133
|
+
- fg: 字体颜色,例如"red"
|
|
134
|
+
- bg: 背景颜色,例如"white"
|
|
135
|
+
- width: 宽度(像素),如果为空则根据内容自适应
|
|
136
|
+
- height: 高度(像素),如果为空则根据内容自适应
|
|
137
|
+
- wraplength: 文本换行长度,单位像素(默认0,不换行)
|
|
138
|
+
组件布局常用参数:
|
|
139
|
+
- side: 组件在父容器中的位置,tk.TOP(上)、tk.BOTTOM(下)、tk.LEFT(左)、tk.RIGHT(右)
|
|
140
|
+
- fill: 组件在父容器中的填充方式,tk.BOTH(填充父容器)、tk.X(填充水平方向)、tk.Y(填充垂直方向)
|
|
141
|
+
- expand: 是否允许组件在父容器中扩展,True(允许)、False(不允许)
|
|
142
|
+
- anchor: 组件在父容器中的对齐方式,tk.N(北)、tk.S(南)、tk.E(东)、tk.W(西)、tk.CENTER(居中)
|
|
143
|
+
- padx: 组件在父容器中的水平间距,单位像素
|
|
144
|
+
- pady: 组件在父容器中的垂直间距,单位像素
|
|
145
|
+
:return: 新添加的按钮(Button)
|
|
146
|
+
:rtype: Button
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
kwargs['master'] = master if master else self.root
|
|
150
|
+
pack_kwargs = {
|
|
151
|
+
'side': kwargs.pop('side', tk.TOP),
|
|
152
|
+
'fill': kwargs.pop('fill', tk.NONE), # 按钮默认不填充父容器
|
|
153
|
+
'expand': kwargs.pop('expand', False if kwargs['master'] == self.root else True),
|
|
154
|
+
'anchor': kwargs.pop('anchor', tk.CENTER),
|
|
155
|
+
'padx': kwargs.pop('padx', 10), # 按钮默认水平间距10像素
|
|
156
|
+
'pady': kwargs.pop('pady', 10)} # 按钮默认垂直间距10像素
|
|
157
|
+
kwargs['master'] = master if master else self.root
|
|
158
|
+
|
|
159
|
+
# 创建组件
|
|
160
|
+
image = kwargs.pop('image', None)
|
|
161
|
+
button = tk.Button(**kwargs)
|
|
162
|
+
if image is not None: # 配置图片
|
|
163
|
+
self.config(button, image=image)
|
|
164
|
+
button.pack(pack_kwargs) # 放置组件
|
|
165
|
+
|
|
166
|
+
return button
|
|
167
|
+
|
|
168
|
+
def config(self, widget, **kwargs):
|
|
169
|
+
"""
|
|
170
|
+
1. 仅当传入image参数时,才处理图片转换和引用
|
|
171
|
+
2. 不传入image时,不修改image参数
|
|
172
|
+
"""
|
|
173
|
+
# 处理图片参数(仅当传入image时)
|
|
174
|
+
if 'image' in kwargs:
|
|
175
|
+
img = kwargs['image']
|
|
176
|
+
img_tk = self._convert_image(img)
|
|
177
|
+
widget.img_tk = img_tk # 关键:保留引用,避免垃圾回收
|
|
178
|
+
kwargs['image'] = img_tk
|
|
179
|
+
|
|
180
|
+
# 执行组件配置
|
|
181
|
+
widget.config(**kwargs)
|
|
182
|
+
|
|
183
|
+
def _convert_image(self, img):
|
|
184
|
+
"""
|
|
185
|
+
将不同格式的图片转换为tkinter可用的PhotoImage对象
|
|
186
|
+
支持:字符串路径、OpenCV图像(BGR格式)、PIL Image对象
|
|
187
|
+
"""
|
|
188
|
+
if isinstance(img, str) and img: # 字符串路径
|
|
189
|
+
# 检查文件是否存在
|
|
190
|
+
if not os.path.exists(img):
|
|
191
|
+
raise FileNotFoundError(f"图片文件不存在:{img}")
|
|
192
|
+
img_cv = cv2.imread(img)
|
|
193
|
+
img_rgb = cv2.cvtColor(img_cv, cv2.COLOR_BGR2RGB)
|
|
194
|
+
img_pil = Image.fromarray(img_rgb)
|
|
195
|
+
img_tk = ImageTk.PhotoImage(img_pil)
|
|
196
|
+
elif isinstance(img, np.ndarray): # OpenCV图像(BGR格式)
|
|
197
|
+
if len(img.shape) not in (2, 3):
|
|
198
|
+
raise ValueError("OpenCV图像维度错误,需为2D/3D数组")
|
|
199
|
+
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) if len(img.shape) == 3 else img
|
|
200
|
+
img_pil = Image.fromarray(img_rgb)
|
|
201
|
+
img_tk = ImageTk.PhotoImage(img_pil)
|
|
202
|
+
elif isinstance(img, Image.Image): # PIL Image对象
|
|
203
|
+
img_tk = ImageTk.PhotoImage(img)
|
|
204
|
+
elif isinstance(img, ImageTk.PhotoImage):
|
|
205
|
+
img_tk = img
|
|
206
|
+
else: # 清空图片
|
|
207
|
+
img_tk = "" # 用空字符串替代None,避免tkinter报错
|
|
208
|
+
return img_tk
|
|
209
|
+
|
|
210
|
+
def after(self, delay, func):
|
|
211
|
+
self.root.after(delay, func)
|
|
212
|
+
|
|
213
|
+
def run(self):
|
|
214
|
+
self.root.mainloop()
|
|
215
|
+
|
|
216
|
+
def quit(self):
|
|
217
|
+
self.root.quit()
|
|
218
|
+
self.root.destroy()
|
|
219
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ezgo
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.8
|
|
4
4
|
Summary: 宇树Go2机器狗Python控制库
|
|
5
5
|
Author-email: ezgo <noreply@example.com>
|
|
6
6
|
License: MIT
|
|
@@ -359,6 +359,16 @@ pip install opencv-python numpy Pillow netifaces
|
|
|
359
359
|
|
|
360
360
|
## 更新日志
|
|
361
361
|
|
|
362
|
+
### v0.0.8 (2025-12-09)
|
|
363
|
+
**改进**:
|
|
364
|
+
- 🔧 移除自动依赖检查逻辑,避免导入时的警告提示
|
|
365
|
+
- 🔧 用户按需使用功能,不再强制检查所有依赖
|
|
366
|
+
- 🔧 提升用户体验,减少不必要的警告信息
|
|
367
|
+
|
|
368
|
+
**修复**:
|
|
369
|
+
- 🐛 修复使用eztk时频繁提示unitree-sdk2py依赖的问题
|
|
370
|
+
- 🐛 修复导入库时自动检查所有模块依赖的逻辑
|
|
371
|
+
|
|
362
372
|
### v0.0.5 (2025-12-04)
|
|
363
373
|
**新增功能**:
|
|
364
374
|
- ✨ 新增15种高级运动模式和特技动作
|
ezgo-0.0.6/src/ezgo/__init__.py
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
|
|
4
|
-
"""
|
|
5
|
-
ezgo - 宇树Go2机器狗Python控制库
|
|
6
|
-
|
|
7
|
-
这是一个用于控制宇树Go2机器狗的Python库,提供了简单易用的API接口。
|
|
8
|
-
支持运动控制、视频流获取、UI界面等功能。
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
__version__ = "0.0.6"
|
|
12
|
-
__author__ = "ezgo"
|
|
13
|
-
__email__ = ""
|
|
14
|
-
__license__ = "MIT"
|
|
15
|
-
|
|
16
|
-
# 导入主要类
|
|
17
|
-
try:
|
|
18
|
-
from .go2 import Go2
|
|
19
|
-
from .camera import Camera
|
|
20
|
-
from .ui import APP
|
|
21
|
-
from .go2_camera import Go2Camera
|
|
22
|
-
from .go2_vui import Go2VUI
|
|
23
|
-
except ImportError as e:
|
|
24
|
-
print(f"警告: 无法导入部分模块: {e}")
|
|
25
|
-
print("请确保已安装所有依赖包")
|
|
26
|
-
Go2 = None
|
|
27
|
-
Camera = None
|
|
28
|
-
APP = None
|
|
29
|
-
Go2Camera = None
|
|
30
|
-
Go2VUI = None
|
|
31
|
-
|
|
32
|
-
# 定义公开的API
|
|
33
|
-
__all__ = [
|
|
34
|
-
"Go2",
|
|
35
|
-
"Camera",
|
|
36
|
-
"APP",
|
|
37
|
-
"Go2Camera",
|
|
38
|
-
"Go2VUI",
|
|
39
|
-
"__version__"
|
|
40
|
-
]
|
|
41
|
-
|
|
42
|
-
# 依赖检查和提示
|
|
43
|
-
def _check_dependencies():
|
|
44
|
-
"""检查关键依赖是否安装,并给出安装提示"""
|
|
45
|
-
missing_deps = []
|
|
46
|
-
|
|
47
|
-
try:
|
|
48
|
-
import cv2
|
|
49
|
-
except ImportError:
|
|
50
|
-
missing_deps.append("opencv-python")
|
|
51
|
-
|
|
52
|
-
try:
|
|
53
|
-
import numpy
|
|
54
|
-
except ImportError:
|
|
55
|
-
missing_deps.append("numpy")
|
|
56
|
-
|
|
57
|
-
try:
|
|
58
|
-
from PIL import Image
|
|
59
|
-
except ImportError:
|
|
60
|
-
missing_deps.append("Pillow")
|
|
61
|
-
|
|
62
|
-
try:
|
|
63
|
-
import netifaces
|
|
64
|
-
except ImportError:
|
|
65
|
-
missing_deps.append("netifaces")
|
|
66
|
-
|
|
67
|
-
try:
|
|
68
|
-
import unitree_sdk2py
|
|
69
|
-
except ImportError:
|
|
70
|
-
missing_deps.append("unitree-sdk2py")
|
|
71
|
-
|
|
72
|
-
if missing_deps:
|
|
73
|
-
print("=" * 60)
|
|
74
|
-
print("警告: ezgo 缺少以下依赖包:")
|
|
75
|
-
for dep in missing_deps:
|
|
76
|
-
print(f" - {dep}")
|
|
77
|
-
print()
|
|
78
|
-
print("请安装缺少的依赖包:")
|
|
79
|
-
print(" pip install opencv-python numpy Pillow netifaces")
|
|
80
|
-
print(" # unitree-sdk2py 需要从官方源安装")
|
|
81
|
-
print("=" * 60)
|
|
82
|
-
return False
|
|
83
|
-
|
|
84
|
-
return True
|
|
85
|
-
|
|
86
|
-
# 在导入时进行依赖检查
|
|
87
|
-
_check_dependencies()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|