diff --git a/.DS_Store b/.DS_Store index 292ebfd..f438c45 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/MRobot.py b/MRobot.py index 038f3e8..fcc90cf 100644 --- a/MRobot.py +++ b/MRobot.py @@ -8,7 +8,6 @@ from PyQt5.QtWidgets import QApplication from app.main_window import MainWindow - # 启用 DPI 缩放 QApplication.setHighDpiScaleFactorRoundingPolicy(Qt.HighDpiScaleFactorRoundingPolicy.PassThrough) QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) # 启用高 DPI 缩放 diff --git a/ai.py b/ai.py new file mode 100644 index 0000000..4cc0dd6 --- /dev/null +++ b/ai.py @@ -0,0 +1,21 @@ +import requests +import json + +url = "http://154.37.215.220:11434/api/generate" +payload = { + "model": "qwen3:0.6b", + "prompt": "你好,介绍一下你自己" +} +response = requests.post(url, json=payload, stream=True) + +for line in response.iter_lines(): + if line: + try: + data = json.loads(line.decode('utf-8')) + # 只输出 response 字段内容 + print(data.get("response", ""), end="", flush=True) + # 如果 done 为 True,则换行 + if data.get("done", False): + print() + except Exception as e: + pass # 忽略解析异常 \ No newline at end of file diff --git a/app/about_interface.py b/app/about_interface.py index 407787d..a8a6792 100644 --- a/app/about_interface.py +++ b/app/about_interface.py @@ -26,20 +26,29 @@ class AboutInterface(QWidget): layout.addWidget(card) def on_check_update_clicked(self): - latest = check_update(__version__) - if latest: - InfoBar.success( - title="发现新版本", - content=f"检测到新版本:{latest},请前往官网或仓库下载更新。", + try: + latest = check_update(__version__) + if latest: + InfoBar.success( + title="发现新版本", + content=f"检测到新版本:{latest},请前往官网或仓库下载更新。", + parent=self, + position=InfoBarPosition.TOP, + duration=5000 + ) + elif latest is None: + InfoBar.info( + title="已是最新版本", + content="当前已是最新版本,无需更新。", + parent=self, + position=InfoBarPosition.TOP, + duration=3000 + ) + except Exception: + InfoBar.error( + title="检查更新失败", + content="无法获取最新版本,请检查网络连接。", parent=self, position=InfoBarPosition.TOP, - duration=5000 - ) - else: - InfoBar.info( - title="已是最新版本", - content="当前已是最新版本,无需更新。", - parent=self, - position=InfoBarPosition.TOP, - duration=3000 + duration=4000 ) \ No newline at end of file diff --git a/app/ai_interface.py b/app/ai_interface.py new file mode 100644 index 0000000..6149ea3 --- /dev/null +++ b/app/ai_interface.py @@ -0,0 +1,182 @@ +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel +from PyQt5.QtCore import Qt, QThread, pyqtSignal + +from qfluentwidgets import TextEdit, LineEdit, PushButton, TitleLabel, SubtitleLabel, FluentIcon, InfoBar, InfoBarPosition +import requests +import json + +class AIWorker(QThread): + response_signal = pyqtSignal(str) + done_signal = pyqtSignal() + error_signal = pyqtSignal(str) # 新增 + + def __init__(self, prompt, parent=None): + super().__init__(parent) + self.prompt = prompt + + def run(self): + url = "http://154.37.215.220:11434/api/generate" + payload = { + "model": "qwen3:0.6b", + "prompt": self.prompt + } + try: + response = requests.post(url, json=payload, stream=True, timeout=60) + got_response = False + for line in response.iter_lines(): + if line: + got_response = True + try: + data = json.loads(line.decode('utf-8')) + self.response_signal.emit(data.get("response", "")) + if data.get("done", False): + self.done_signal.emit() + break + except Exception: + continue + if not got_response: + self.error_signal.emit("服务器繁忙,请稍后再试。") + self.done_signal.emit() + except requests.ConnectionError: + self.error_signal.emit("网络连接失败,请检查网络设置。") + self.done_signal.emit() + except Exception as e: + self.error_signal.emit(f"[错误]: {str(e)}") + self.done_signal.emit() + + +class AIInterface(QWidget): + MAX_HISTORY = 20 # 新增最大对话条数 + + def __init__(self, parent=None): + super().__init__(parent) + self.setObjectName("aiPage") + self.layout = QVBoxLayout(self) + self.layout.setContentsMargins(20, 20, 20, 20) + self.layout.setSpacing(10) + + self.title = SubtitleLabel("MRobot AI小助手", self) + self.title.setAlignment(Qt.AlignCenter) + self.layout.addWidget(self.title) + + self.chat_display = TextEdit(self) + self.chat_display.setReadOnly(True) + + self.layout.addWidget(self.chat_display, stretch=1) + + input_layout = QHBoxLayout() + self.input_box = LineEdit(self) + self.input_box.setPlaceholderText("请输入你的问题...") + input_layout.addWidget(self.input_box, stretch=1) + + # self.send_btn = PushButton("发送", self) + self.send_btn = PushButton("发送", icon=FluentIcon.SEND, parent=self) + + self.send_btn.setFixedWidth(80) + input_layout.addWidget(self.send_btn) + + self.layout.addLayout(input_layout) + + self.send_btn.clicked.connect(self.send_message) + self.input_box.returnPressed.connect(self.send_message) + + self.worker = None + self.is_waiting = False + self.history = [] + self.chat_display.setText( + "MRobot: 欢迎使用MRobot AI小助手!" + ) + + def send_message(self): + if self.is_waiting: + return + prompt = self.input_box.text().strip() + if not prompt: + return + if len(prompt) > 1000: + InfoBar.warning( + title='警告', + content="每条发送内容不能超过1000字,请精简后再发送。", + orient=Qt.Horizontal, + isClosable=True, + position=InfoBarPosition.BOTTOM, + duration=-1, + parent=self + ) + return + if len(self.history) >= self.MAX_HISTORY: + InfoBar.warning( + title='警告', + content="对话条数已达上限,请清理历史或重新开始。", + orient=Qt.Horizontal, + isClosable=True, + position=InfoBarPosition.BOTTOM, + duration=-1, + parent=self + ) + return + self.append_chat("你", prompt) + self.input_box.clear() + self.append_chat("MRobot", "", new_line=False) + self.is_waiting = True + + # 只在首次对话时加入身份提示 + if not self.history: + system_prompt = ( + "你是MRobot,是QUT青岛理工大学机器人战队的AI机器人。" + "请以此身份与用户进行交流。" + ) + else: + system_prompt = "" + + self.history.append({"role": "user", "content": prompt}) + context = system_prompt + "\n" if system_prompt else "" + for msg in self.history: + if msg["role"] == "user": + context += f"你: {msg['content']}\n" + else: + context += f"AI: {msg['content']}\n" + + self.worker = AIWorker(context) + self.worker.response_signal.connect(self.stream_response) + self.worker.done_signal.connect(self.finish_response) + self.worker.error_signal.connect(self.show_error) # 新增 + self.worker.start() + + + def append_chat(self, sender, message, new_line=True): + if new_line: + self.chat_display.append(f"{sender}: {message}") + else: + self.chat_display.append(f"{sender}: ") + self.chat_display.moveCursor(self.chat_display.textCursor().End) + # 新增:保存AI回复到历史 + if sender == "AI" and message: + self.history.append({"role": "ai", "content": message}) + + def stream_response(self, text): + cursor = self.chat_display.textCursor() + cursor.movePosition(cursor.End) + cursor.insertText(text) + self.chat_display.setTextCursor(cursor) + # 新增:流式保存AI回复 + if self.history and self.history[-1]["role"] == "ai": + self.history[-1]["content"] += text + elif text: + self.history.append({"role": "ai", "content": text}) + + def finish_response(self): + self.chat_display.append("") # 换行 + self.is_waiting = False + + def show_error(self, msg): # 新增 + InfoBar.error( + title='失败', + content=msg, + orient=Qt.Vertical, + isClosable=True, + position=InfoBarPosition.TOP, + duration=-1, + parent=self + ) + self.is_waiting = False diff --git a/app/mini_tool_interface.py b/app/mini_tool_interface.py index aaa3856..8e4faea 100644 --- a/app/mini_tool_interface.py +++ b/app/mini_tool_interface.py @@ -3,6 +3,7 @@ from PyQt5.QtCore import Qt from qfluentwidgets import PushSettingCard, FluentIcon, TabBar from .function_fit_interface import FunctionFitInterface +from .ai_interface import AIInterface class MiniToolInterface(QWidget): def __init__(self, parent=None): @@ -26,14 +27,22 @@ class MiniToolInterface(QWidget): mainLayout = QVBoxLayout(self.mainPage) mainLayout.setAlignment(Qt.AlignTop) # 卡片靠顶部 self.card = PushSettingCard( - text="▶启动", + text="▶ 启动", icon=FluentIcon.UNIT, title="曲线拟合工具", content="简单的曲线拟合工具,支持多种函数类型", ) mainLayout.addWidget(self.card) - self.mainPage.setLayout(mainLayout) + self.mainPage.setLayout(mainLayout) + self.aiCard = PushSettingCard( + text="▶ 启动", + icon=FluentIcon.ROBOT, + title="MRobot AI助手", + content="与 MRobot 进行图一乐交流, 使用开源模型qwen3:0.6b。", + ) + mainLayout.addWidget(self.aiCard) + self.aiCard.clicked.connect(self.open_ai_tab) # 添加主页面到堆叠窗口 self.addSubInterface(self.mainPage, "mainPage", "工具箱主页") @@ -79,4 +88,17 @@ class MiniToolInterface(QWidget): fit_page = FunctionFitInterface(self) self.addSubInterface(fit_page, "fitPage", "曲线拟合") self.stackedWidget.setCurrentWidget(fit_page) - self.tabBar.setCurrentTab("fitPage") \ No newline at end of file + self.tabBar.setCurrentTab("fitPage") + + def open_ai_tab(self): + # 检查是否已存在标签页,避免重复添加 + for i in range(self.stackedWidget.count()): + widget = self.stackedWidget.widget(i) + if widget.objectName() == "aiPage": + self.stackedWidget.setCurrentWidget(widget) + self.tabBar.setCurrentTab("aiPage") + return + ai_page = AIInterface(self) + self.addSubInterface(ai_page, "aiPage", "AI问答") + self.stackedWidget.setCurrentWidget(ai_page) + self.tabBar.setCurrentTab("aiPage") diff --git a/app/part_library_interface.py b/app/part_library_interface.py index ecad14d..1279d8c 100644 --- a/app/part_library_interface.py +++ b/app/part_library_interface.py @@ -1,51 +1,12 @@ -from PyQt5.QtCore import Qt, QThread, pyqtSignal +from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QWidget -from qfluentwidgets import SubtitleLabel, BodyLabel, HorizontalSeparator, PushButton, TreeWidget, ProgressBar, Dialog, InfoBar, InfoBarPosition, FluentIcon +from qfluentwidgets import SubtitleLabel, BodyLabel, HorizontalSeparator, PushButton, TreeWidget, ProgressBar, Dialog, InfoBar, InfoBarPosition, FluentIcon, ProgressRing, Dialog import requests import shutil import os +from .tools.part_download import DownloadThread # 新增导入 + from urllib.parse import quote - -class DownloadThread(QThread): - progressChanged = pyqtSignal(int) - finished = pyqtSignal(list, list) # success, fail - - def __init__(self, files, server_url, secret_key, local_dir, parent=None): - super().__init__(parent) - self.files = files - self.server_url = server_url - self.secret_key = secret_key - self.local_dir = local_dir - - def run(self): - success, fail = [], [] - total = len(self.files) - max_retry = 3 - for idx, rel_path in enumerate(self.files): - retry = 0 - while retry < max_retry: - try: - rel_path_unix = rel_path.replace("\\", "/") - encoded_path = quote(rel_path_unix) - url = f"{self.server_url}/download/{encoded_path}" - params = {"key": self.secret_key} - resp = requests.get(url, params=params, stream=True, timeout=10) - if resp.status_code == 200: - local_path = os.path.join(self.local_dir, rel_path) - os.makedirs(os.path.dirname(local_path), exist_ok=True) - with open(local_path, "wb") as f: - shutil.copyfileobj(resp.raw, f) - success.append(rel_path) - break - else: - retry += 1 - except Exception: - retry += 1 - else: - fail.append(rel_path) - self.progressChanged.emit(int((idx + 1) / total * 100)) - self.finished.emit(success, fail) - class PartLibraryInterface(QWidget): SERVER_URL = "http://154.37.215.220:5000" SECRET_KEY = "MRobot_Download" @@ -151,41 +112,64 @@ class PartLibraryInterface(QWidget): def download_selected_files(self): files = self.get_checked_files() if not files: - InfoBar.info( - title="提示", - content="请先勾选要下载的文件。", - parent=self, - position=InfoBarPosition.TOP, - duration=2000 + dialog = Dialog( + title="温馨提示", + content="请先勾选需要下载的文件。", + parent=self ) + dialog.yesButton.setText("知道啦") + dialog.cancelButton.hide() + dialog.exec() return - self.progress_dialog = Dialog( - title="正在下载", - content="正在下载选中文件,请稍候...", - parent=self - ) - self.progress_bar = ProgressBar() - self.progress_bar.setValue(0) - self.progress_dialog.textLayout.addWidget(self.progress_bar) - self.progress_dialog.show() + # 创建进度环 + self.progress_ring = ProgressRing() + self.progress_ring.setRange(0, 100) + self.progress_ring.setValue(0) + self.progress_ring.setTextVisible(True) + self.progress_ring.setFixedSize(32, 32) + self.progress_ring.setStrokeWidth(4) + # 展示消息条(关闭按钮即中断下载) + self.info_bar = InfoBar( + icon=FluentIcon.DOWNLOAD, + title="正在下载", + content="正在下载选中文件...", + parent=self, + position=InfoBarPosition.TOP, + duration=-1 # 不自动消失 + ) + self.info_bar.addWidget(self.progress_ring) + self.info_bar.closeButton.clicked.connect(self.stop_download) # 关闭即中断下载 + self.info_bar.show() + + # 启动下载线程 self.download_thread = DownloadThread( files, self.SERVER_URL, self.SECRET_KEY, self.LOCAL_LIB_DIR ) - self.download_thread.progressChanged.connect(self.progress_bar.setValue) + self.download_thread.progressChanged.connect(self.progress_ring.setValue) self.download_thread.finished.connect(self.on_download_finished) self.download_thread.finished.connect(self.download_thread.deleteLater) self.download_thread.start() + def stop_download(self): + if hasattr(self, "download_thread") and self.download_thread.isRunning(): + self.download_thread.terminate() + self.download_thread.wait() + self.info_bar.close() + InfoBar.warning( + title="下载已中断", + content="已手动中断下载任务。", + parent=self, + position=InfoBarPosition.TOP, + duration=2000 + ) + def on_download_finished(self, success, fail): - self.progress_dialog.close() - msg = f"成功下载: {len(success)} 个文件\n失败: {len(fail)} 个文件" - dialog = Dialog( - title="下载结果", - content=msg, - parent=self - ) + self.info_bar.close() + msg = f"成功下载:{len(success)} 个文件,失败:{len(fail)} 个文件" + + # 创建“打开文件夹”按钮 open_btn = PushButton("打开文件夹") def open_folder(): folder = os.path.abspath(self.LOCAL_LIB_DIR) @@ -196,10 +180,18 @@ class PartLibraryInterface(QWidget): subprocess.call(["explorer", folder]) else: subprocess.call(["xdg-open", folder]) - dialog.close() + + # 展示成功消息条,自动消失 + self.result_bar = InfoBar.success( + title="下载完成", + content=msg, + parent=self, + position=InfoBarPosition.TOP, + duration=4000 # 4秒后自动消失 + ) + self.result_bar.addWidget(open_btn) open_btn.clicked.connect(open_folder) - dialog.textLayout.addWidget(open_btn) - dialog.exec() + self.result_bar.show() def open_local_lib(self): folder = os.path.abspath(self.LOCAL_LIB_DIR) diff --git a/app/tools/check_update.py b/app/tools/check_update.py index 7095d6b..426f3ab 100644 --- a/app/tools/check_update.py +++ b/app/tools/check_update.py @@ -2,19 +2,13 @@ import requests from packaging.version import parse as vparse def check_update(local_version, repo="goldenfishs/MRobot"): - """ - 检查 GitHub 上是否有新版本 - :param local_version: 当前版本号字符串,如 "1.0.2" - :param repo: 仓库名,格式 "用户名/仓库名" - :return: 最新版本号字符串(如果有新版本),否则 None - """ url = f"https://api.github.com/repos/{repo}/releases/latest" - try: - resp = requests.get(url, timeout=5) - if resp.status_code == 200: - latest = resp.json()["tag_name"].lstrip("v") - if vparse(latest) > vparse(local_version): - return latest - except Exception as e: - print(f"检查更新失败: {e}") - return None \ No newline at end of file + resp = requests.get(url, timeout=5) + if resp.status_code == 200: + latest = resp.json()["tag_name"].lstrip("v") + if vparse(latest) > vparse(local_version): + return latest + else: + return None + else: + raise RuntimeError("GitHub API 请求失败") \ No newline at end of file diff --git a/app/tools/part_download.py b/app/tools/part_download.py new file mode 100644 index 0000000..1faf82c --- /dev/null +++ b/app/tools/part_download.py @@ -0,0 +1,45 @@ +from PyQt5.QtCore import QThread, pyqtSignal +import requests +import shutil +import os +from urllib.parse import quote + +class DownloadThread(QThread): + progressChanged = pyqtSignal(int) + finished = pyqtSignal(list, list) # success, fail + + def __init__(self, files, server_url, secret_key, local_dir, parent=None): + super().__init__(parent) + self.files = files + self.server_url = server_url + self.secret_key = secret_key + self.local_dir = local_dir + + def run(self): + success, fail = [], [] + total = len(self.files) + max_retry = 3 + for idx, rel_path in enumerate(self.files): + retry = 0 + while retry < max_retry: + try: + rel_path_unix = rel_path.replace("\\", "/") + encoded_path = quote(rel_path_unix) + url = f"{self.server_url}/download/{encoded_path}" + params = {"key": self.secret_key} + resp = requests.get(url, params=params, stream=True, timeout=10) + if resp.status_code == 200: + local_path = os.path.join(self.local_dir, rel_path) + os.makedirs(os.path.dirname(local_path), exist_ok=True) + with open(local_path, "wb") as f: + shutil.copyfileobj(resp.raw, f) + success.append(rel_path) + break + else: + retry += 1 + except Exception: + retry += 1 + else: + fail.append(rel_path) + self.progressChanged.emit(int((idx + 1) / total * 100)) + self.finished.emit(success, fail) \ No newline at end of file diff --git a/app/tools/task_config.py b/app/tools/task_config.py new file mode 100644 index 0000000..fec9f35 --- /dev/null +++ b/app/tools/task_config.py @@ -0,0 +1,114 @@ +from PyQt5.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout +from qfluentwidgets import TitleLabel, BodyLabel, TableWidget, PushButton, SubtitleLabel, SpinBox, InfoBar, InfoBarPosition, LineEdit, CheckBox +from PyQt5.QtCore import Qt +import yaml +import os + +class TaskConfigDialog(QDialog): + def __init__(self, parent=None, config_path=None): + super().__init__(parent) + self.setWindowTitle("任务配置") + self.resize(900, 480) + layout = QVBoxLayout(self) + layout.setContentsMargins(32, 32, 32, 32) + layout.setSpacing(18) + + layout.addWidget(TitleLabel("FreeRTOS 任务配置")) + layout.addWidget(BodyLabel("请添加并配置您的任务参数,支持频率控制与描述。")) + + self.table = TableWidget(self) + self.table.setColumnCount(6) + self.table.setHorizontalHeaderLabels(["任务名称", "运行频率", "初始化延迟", "堆栈大小", "任务描述", "频率控制"]) + self.table.horizontalHeader().setSectionResizeMode(0, self.table.horizontalHeader().Stretch) + self.table.horizontalHeader().setSectionResizeMode(1, self.table.horizontalHeader().ResizeToContents) + self.table.horizontalHeader().setSectionResizeMode(2, self.table.horizontalHeader().ResizeToContents) + self.table.horizontalHeader().setSectionResizeMode(3, self.table.horizontalHeader().ResizeToContents) + self.table.horizontalHeader().setSectionResizeMode(4, self.table.horizontalHeader().Stretch) + self.table.horizontalHeader().setSectionResizeMode(5, self.table.horizontalHeader().ResizeToContents) + self.table.setMinimumHeight(260) + layout.addWidget(self.table) + + btn_layout = QHBoxLayout() + self.add_btn = PushButton("添加任务") + self.del_btn = PushButton("删除选中") + self.ok_btn = PushButton("生成") + self.cancel_btn = PushButton("取消") + btn_layout.addWidget(self.add_btn) + btn_layout.addWidget(self.del_btn) + btn_layout.addStretch() + btn_layout.addWidget(self.ok_btn) + btn_layout.addWidget(self.cancel_btn) + layout.addLayout(btn_layout) + + self.add_btn.clicked.connect(self.add_row) + self.del_btn.clicked.connect(self.del_row) + self.ok_btn.clicked.connect(self.accept) + self.cancel_btn.clicked.connect(self.reject) + + # 自动读取配置文件 + if config_path and os.path.exists(config_path): + try: + with open(config_path, "r", encoding="utf-8") as f: + tasks = yaml.safe_load(f) + if tasks: + for t in tasks: + self.add_row(t) + except Exception: + pass + + def add_row(self, task=None): + row = self.table.rowCount() + self.table.insertRow(row) + name = LineEdit(task.get("name", f"Task{row+1}") if task else f"Task{row+1}") + freq = SpinBox() + freq.setRange(1, 10000) + freq.setValue(task.get("frequency", 500) if task else 500) + delay = SpinBox() + delay.setRange(0, 10000) + delay.setValue(task.get("delay", 0) if task else 0) + stack = SpinBox() + stack.setRange(128, 8192) + stack.setSingleStep(128) + stack.setValue(task.get("stack", 256) if task else 256) + desc = LineEdit(task.get("description", "") if task else "请填写任务描述") + freq_ctrl = CheckBox("启用") + freq_ctrl.setChecked(task.get("freq_control", True) if task else True) + + self.table.setCellWidget(row, 0, name) + self.table.setCellWidget(row, 1, freq) + self.table.setCellWidget(row, 2, delay) + self.table.setCellWidget(row, 3, stack) + self.table.setCellWidget(row, 4, desc) + self.table.setCellWidget(row, 5, freq_ctrl) + + def del_row(self): + selected = self.table.selectedItems() + if selected: + rows = set(item.row() for item in selected) + for row in sorted(rows, reverse=True): + self.table.removeRow(row) + + def get_tasks(self): + tasks = [] + for row in range(self.table.rowCount()): + name = self.table.cellWidget(row, 0).text().strip() + freq = self.table.cellWidget(row, 1).value() + delay = self.table.cellWidget(row, 2).value() + stack = self.table.cellWidget(row, 3).value() + desc = self.table.cellWidget(row, 4).text().strip() + freq_ctrl = self.table.cellWidget(row, 5).isChecked() + # 校验 stack 必须为 128*2^n + if stack < 128 or (stack & (stack - 1)) != 0 or stack % 128 != 0: + raise ValueError(f"第{row+1}行任务“{name}”的堆栈大小必须为128、256、512、1024等(128*2^n)") + task = { + "name": name, + "function": f"Task_{name}", + "delay": delay, + "stack": stack, + "description": desc, + "freq_control": freq_ctrl + } + if freq_ctrl: + task["frequency"] = freq + tasks.append(task) + return tasks \ No newline at end of file diff --git a/mech_lib/.DS_Store b/mech_lib/.DS_Store new file mode 100644 index 0000000..557d4b2 Binary files /dev/null and b/mech_lib/.DS_Store differ diff --git a/mech_lib/2.电机&舵机/5065BrushlessMotor/N5065 Magnet.SLDPRT b/mech_lib/2.电机&舵机/5065BrushlessMotor/N5065 Magnet.SLDPRT new file mode 100644 index 0000000..623f7b0 Binary files /dev/null and b/mech_lib/2.电机&舵机/5065BrushlessMotor/N5065 Magnet.SLDPRT differ diff --git a/mech_lib/2.电机&舵机/5065BrushlessMotor/N5065 Prop Endcap.SLDPRT b/mech_lib/2.电机&舵机/5065BrushlessMotor/N5065 Prop Endcap.SLDPRT new file mode 100644 index 0000000..56f91e7 Binary files /dev/null and b/mech_lib/2.电机&舵机/5065BrushlessMotor/N5065 Prop Endcap.SLDPRT differ diff --git a/mech_lib/2.电机&舵机/5065BrushlessMotor/N5065 Windings.SLDPRT b/mech_lib/2.电机&舵机/5065BrushlessMotor/N5065 Windings.SLDPRT new file mode 100644 index 0000000..3390fef Binary files /dev/null and b/mech_lib/2.电机&舵机/5065BrushlessMotor/N5065 Windings.SLDPRT differ diff --git a/mech_lib/4.大疆电池/TB47_外购件_01.SLDPRT b/mech_lib/4.大疆电池/TB47_外购件_01.SLDPRT new file mode 100644 index 0000000..2fa0207 Binary files /dev/null and b/mech_lib/4.大疆电池/TB47_外购件_01.SLDPRT differ diff --git a/mech_lib/4.大疆电池/TB47电池架_外购件_01.SLDPRT b/mech_lib/4.大疆电池/TB47电池架_外购件_01.SLDPRT new file mode 100644 index 0000000..a9244ec Binary files /dev/null and b/mech_lib/4.大疆电池/TB47电池架_外购件_01.SLDPRT differ diff --git a/mech_lib/4.大疆电池/TB48电池架_外购件_01.SLDPRT b/mech_lib/4.大疆电池/TB48电池架_外购件_01.SLDPRT new file mode 100644 index 0000000..beb9919 Binary files /dev/null and b/mech_lib/4.大疆电池/TB48电池架_外购件_01.SLDPRT differ