diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7cf68ab --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea/ +aio/.idea/ +aio/__pycache__/ \ No newline at end of file diff --git a/aio/README.md b/aio/README.md index b7c0108..3742ff2 100644 --- a/aio/README.md +++ b/aio/README.md @@ -1,67 +1,69 @@ -程序功能:自动化处理制动性能采集的数据,减少人工处理时长,目前测试单轴可从原来的4-6h,减少到15min -使用方法:修改 configs.xlsx 配置文件中的一些参数(数据文件路径/减速比/最大角速度/额定电流),然后直接执行即可 -第三方库:pandas/pywin32/openpyxl - pip3 install pandas -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn - pip3 install openpyxl -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn - pip3 install pywin32 -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn - pip3 install Pillow -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn - python.exe -m pip install --upgrade pip -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn - pip3 install --upgrade --force-reinstall numpy -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn -打包方法:pyinstaller.exe -F --version-file file_version_info.txt -i .\icon.ico .\aio.py - 最好不用虚拟环境 - pyinstaller.exe -F --version-file file_version_info.txt -i .\icon.ico .\aio.py -p .\brake.py -p .\current.py +### 程序功能 +自动化处理制动性能采集的数据,减少人工处理时长,目前测试单轴可从原来的4-6h,减少到15min +### 使用方法 +修改 configs.xlsx 配置文件中的一些参数(数据文件路径/减速比/最大角速度/额定电流),然后直接执行即可 +### 第三方库 +```commandline +# https://customtkinter.tomschimansky.com/documentation/packaging +pip3 install pandas -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn +pip3 install openpyxl -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn +pip3 install pywin32 -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn +pip3 install Pillow -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn +python.exe -m pip install --upgrade pip -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn +pip3 install --upgrade --force-reinstall numpy -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn +``` +### 打包方法 +```commandline +pyinstaller.exe -F --version-file file_version_info.txt -i .\icon.ico .\aio.py +pyinstaller.exe -F --version-file file_version_info.txt -i .\icon.ico .\aio.py -p .\brake.py -p .\current.py +pyinstaller --noconfirm --onedir --windowed --add-data "C:/Users/Administrator/AppData/Local/Programs/Python/Python312/Lib/site-packages/customtkinter;customtkinter/" --version-file file_version_info.txt -i .\icon.ico .\aio.py -p .\brake.py +``` - https://customtkinter.tomschimansky.com/documentation/packaging - pyinstaller --noconfirm --onedir --windowed --add-data "C:/Users/Administrator/AppData/Local/Programs/Python/Python312/Lib/site-packages/customtkinter;customtkinter/" --version-file file_version_info.txt -i .\icon.ico .\aio.py -p .\brake.py - -注意事项: - 1. 数据文件存储存储规则 - 所谓数据文件,就是我们拍急停的时候,采集到的 .data 文件,正方向拍三次急停,会采集到三个 .data 文件,存储在同一个文件夹内,即每组(三个 .data 文件)文件必须存储在同一个文件夹内,数据文件的命名无要求, +### 注意事项 +```text +1. 数据文件存储存储规则 +数据文件,就是我们拍急停的时候,采集到的 .data 文件,正方向拍三次急停,会采集到三个 .data 文件,存储在同一个文件夹内,即每组(三个 .data 文件)文件必须存储在同一个文件夹内,数据文件的命名无要求, - 2. 文件夹命名规则 - 虽然对采集到的 .data 文件没有命名要求,但是对于文件夹的命名是有要求的,必须是如下格式: - loadXX_speedXX_reachXX 或者 loadXX_reachXX_speedXX - 这里的XX代表不同条件下的测试数值,比如: - load100_speed33_reach66,指的是,负载100%,速度33%,臂展66% +2. 文件夹命名规则 +采集到的 .data 文件没有命名要求,但是对于文件夹的命名是有要求的,必须是如下格式: + dXX_speedXX_reachXX 或者 loadXX_reachXX_speedXX + XX代表不同条件下的测试数值,比如: + d100_speed33_reach66,指的是,负载100%,速度33%,臂展66% - 3. 结果文件命名规则 - 所谓结果文件,就是处理数据的那个 excle 文件,该文件名字的前缀必须是 loadXX_XXXXXXXXX.xlsx,比如: - load33_自研_制动性能测试.xlsx - load66_自研_制动性能测试.xlsx - load100_自研_制动性能测试.xlsx +3. 结果文件命名规则 +所谓结果文件,就是处理数据的那个 excle 文件,该文件名字的前缀必须是 loadXX_XXXXXXXXX.xlsx,比如: + load33_自研_制动性能测试.xlsx + load66_自研_制动性能测试.xlsx + load100_自研_制动性能测试.xlsx + +!!结果文件可以是没有数据的,也可以是之前有数据的,只要保证第 6 点中的那几个数据准确即可 + +4. 数据存储的组织结 + ..../j1/load100_speed33_reach100 + ..../j1/load100_speed66_reach100 + .... + ..../j1/load100_speed100_reach100 + ..../j1/load100_speed33_reach100/2024_05_16_09_18_52.data + ..../j1/load100_speed33_reach100/2024_05_16_09_19_52.data + ..../j1/load100_speed33_reach100/2024_05_16_09_20_52.data + ..../j1/load33_自研_制动性能测试.xlsx + ..../j1/load66_自研_制动性能测试.xlsx + ..../j1/load100_自研_制动性能测试.xlsx + +5. 文件的打开与关闭 + a. 在执行程序之前,需要关闭所有相关 excle 文件 + b. 在执行程序之中,不允许打开相关 excle 文件 + c. 在执行程序之后,需要逐个打开结果文件,并保存一次 + +6. 参数一致性检查 +执行程序前,需要确定 configs.xlsx 中设定的减速比/最大角速度/额定电流的值是正确的 - !!结果文件可以是没有数据的,也可以是之前有数据的,只要保证第 6 点中的那几个数据准确即可 +7. 数据准确性检查 +执行完程序之后,需要对结果文件的数据准确性做核对,通过我自己的数据观察,误差基本在10ms以内,也即10个数据点,误差较大的情况可自行调整 - 4. 数据存储的组织结 - ..../j1/load100_speed33_reach100 - ..../j1/load100_speed66_reach100 - .... - ..../j1/load100_speed100_reach100 - ..../j1/load100_speed33_reach100/2024_05_16_09_18_52.data - ..../j1/load100_speed33_reach100/2024_05_16_09_19_52.data - ..../j1/load100_speed33_reach100/2024_05_16_09_20_52.data - - ..../j1/load33_自研_制动性能测试.xlsx - ..../j1/load66_自研_制动性能测试.xlsx - ..../j1/load100_自研_制动性能测试.xlsx - - 5. 文件的打开与关闭 - a. 在执行程序之前,需要关闭所有相关 excle 文件 - b. 在执行程序之中,不允许打开相关 excle 文件 - c. 在执行程序之后,需要逐个打开结果文件,并保存一次 - - 6. 参数一致性检查 - 执行程序前,需要确定 configs.xlsx 中设定的减速比/最大角速度/额定电流的值是正确的 - - 7. 数据准确性检查 - 执行完程序之后,需要对结果文件的数据准确性做核对,通过我自己的数据观察,误差基本在10ms以内,也即10个数据点,误差较大的情况可自行调整 - - 8. .data 数据顺序 - .data 文件的第一列和第二列必须分别是速度和电流 - - 9. 其他 - 程序运行主要的耗时集中在打开,保存和关闭结果文件,第一次打开的时候会比较慢,是因为 excel 在做首次公式的计算,保存关闭之后,再打开会比较快一些,另外,如果在运行出错并重复运行程序的时候无响应,或者出现异常,请打开任务管理器,关闭一切和excel相关的进程,重新运行即可 - +8. 其他 +程序运行主要的耗时集中在打开,保存和关闭结果文件,第一次打开的时候会比较慢,是因为 excel 在做首次公式的计算,保存关闭之后,再打开会比较快一些,另外,如果在运行出错并重复运行程序的时候无响应,或者出现异常,请打开任务管理器,关闭一切和excel相关的进程,重新运行即可 +``` RELEASE CHANGES @@ -124,3 +126,9 @@ v0.1.1(2024/05/30) 5. 重新在write2textbox中添加exitcode参数,并补齐相关逻辑和修改brake中的调用方式 6. 修复参数检查无效的情况 7. 屏蔽电流相关的功能 + +v0.1.2(2024/06/01) +1. 增加iso数据处理功能 +2. 重新修改了README.md +3. 单独将rokae拉出来,作为一个独立的repo进行维护,与scripts分离 +4. 创建分支brake和current,分别单独开发 diff --git a/aio/aio.py b/aio/aio.py index 8bb06cc..7efe456 100644 --- a/aio/aio.py +++ b/aio/aio.py @@ -3,8 +3,7 @@ from os import getcwd from threading import Thread import tkinter.messagebox import customtkinter -import brake -import current +import brake, current, iso from time import time, strftime, localtime from urllib.request import urlopen import socket @@ -56,7 +55,7 @@ class App(customtkinter.CTk): self.btn_end = customtkinter.CTkButton(self.frame_func, corner_radius=10, text='结束运行', font=self.my_font, command=lambda: self.thread_it(self.func_end_call_back)) self.btn_end.grid(row=4, column=0, sticky='new', padx=10, pady=10, ipadx=5, ipady=5) # create version info - self.label_version = customtkinter.CTkLabel(self.frame_func, justify='left', text="Vers: 0.1.1\nDate: 05/30/2024", font=self.my_font, text_color="DarkCyan") + self.label_version = customtkinter.CTkLabel(self.frame_func, justify='left', text="Vers: 0.1.2\nDate: 06/01/2024", font=self.my_font, text_color="DarkCyan") self.frame_func.rowconfigure(6, weight=1) self.label_version.grid(row=6, column=0, padx=20, pady=20, sticky='s') @@ -65,7 +64,7 @@ class App(customtkinter.CTk): self.frame_param = customtkinter.CTkFrame(self, height=240, corner_radius=10) self.frame_param.grid(row=0, column=1, rowspan=3, columnspan=9, sticky='new', ipadx=20, ipady=10, padx=10, pady=10) # create main menu - self.menu_main = customtkinter.CTkOptionMenu(self.frame_param, values=["INIT", "brake", "current"], font=self.my_font, command=self.func_main_callback) + self.menu_main = customtkinter.CTkOptionMenu(self.frame_param, values=["INIT", "brake", "current", "iso"], font=self.my_font, command=self.func_main_callback) self.menu_main.grid(row=0, column=1, sticky='we', padx=(20, 10), pady=(10, 0)) self.menu_main.set("Start Here!") # create sub menu @@ -197,6 +196,9 @@ class App(customtkinter.CTk): self.entry_path.configure(state="normal") self.entry_rc.configure(state="normal") self.option_trq.configure(state="normal") + elif func_name == 'iso': + self.label_path.configure(text="*Path", text_color='red') + self.entry_path.configure(state="normal") else: self.initialization() self.menu_main.set("Start Here!") @@ -247,6 +249,7 @@ class App(customtkinter.CTk): c3 = rr.isdigit() c4 = rpm = 1 c5 = sub_func in ['industrial', 'cobot'] + c6 = True if vel != trq else False if self.menu_sub.get() == 'industrial': rpm = self.entry_rpm.get().strip('- ') @@ -256,7 +259,7 @@ class App(customtkinter.CTk): else: pass - if c1 and c2 and c3 and c4 and c5: + if c1 and c2 and c3 and c4 and c5 and c6: return 1, path, int(av), int(rr), int(rpm), int(axis), int(vel), int(trq) else: return 0, 0 @@ -269,17 +272,24 @@ class App(customtkinter.CTk): sub_func = self.menu_sub.get() c1 = exists(path) c2 = sub_func in ['max', 'avg'] - + c3 = True if vel != trq else False try: _ = float(rc) - c3 = True + c4 = True except ValueError: - c3 = False + c4 = False - if c1 and c2 and c3: + if c1 and c2 and c3 and c4: return 2, path, float(rc), int(vel), int(trq), sub_func else: return 0, 0 + elif func_name == 'iso': + path = self.entry_path.get() + c1 = exists(path) + if c1: + return 3, path + else: + return 0, 0 else: return 0, 0 @@ -289,13 +299,15 @@ class App(customtkinter.CTk): self.textbox.delete(index1='1.0', index2='end') flag, *args = self.check_param() - func_dict = {1: brake.main, 2: current.main} + func_dict = {1: brake.main, 2: current.main, 3: iso.main} if flag == 1: func_dict[flag](path=args[0], av=args[1], rr=args[2], rpm=args[3], axis=args[4], vel=args[5], trq=args[6], w2t=self.write2textbox) elif flag == 2: tkinter.messagebox.showinfo(title="TBD", message="功能待实现......") # func_dict[flag](path=args[0], rc=args[1], sub_func=args[2]) + elif flag == 3: + func_dict[flag](path=args[0], w2t=self.write2textbox) else: tkinter.messagebox.showerror(title="参数错误", message="请检查对应参数是否填写正确!", ) diff --git a/aio/iso.py b/aio/iso.py new file mode 100644 index 0000000..63fbf0c --- /dev/null +++ b/aio/iso.py @@ -0,0 +1,196 @@ +# _*_ encoding:utf-8 _*_ +import pdfplumber +from openpyxl import load_workbook +from os import scandir, remove +from os.path import exists +from sys import argv + + +def traversal_files(path, w2t): + # 功能:以列表的形式分别返回指定路径下的文件和文件夹,不包含子目录 + # 参数:路径 + # 返回值:路径下的文件夹列表 路径下的文件列表 + if not exists(path): + msg = f'数据文件夹{path}不存在,请确认后重试......' + w2t(msg, 0, 1) + else: + dirs = files = [] + for item in scandir(path): + if item.is_dir(): + dirs.append(item.path) + elif item.is_file(): + files.append(item.path) + + return dirs, files + + +def p_iso(file, p_files, ws, tmpfile): + p_files.append(file) + + pdf = pdfplumber.open(file) + with open(tmpfile, mode='w', encoding='utf-8') as fb: + for page in pdf.pages: + fb.write(page.extract_text()) + + with open(tmpfile, mode='r', encoding='utf-8') as fb: + lines = fb.readlines() + lines = [line for line in lines if not line.startswith('Page ')] + for line in lines: + if line.strip() == "Pose Accuracy and Repeatability": + index = lines.index(line) + ws.cell(row=3, column=7).value = float(lines[index+4].split()[1]) + ws.cell(row=4, column=7).value = float(lines[index+5].split()[1]) + ws.cell(row=5, column=7).value = float(lines[index+6].split()[1]) + ws.cell(row=6, column=7).value = float(lines[index+7].split()[1]) + ws.cell(row=7, column=7).value = float(lines[index+8].split()[1]) + + ws.cell(row=8, column=7).value = float(lines[index+4].split()[2]) + ws.cell(row=9, column=7).value = float(lines[index+5].split()[2]) + ws.cell(row=10, column=7).value = float(lines[index+6].split()[2]) + ws.cell(row=11, column=7).value = float(lines[index+7].split()[2]) + ws.cell(row=12, column=7).value = float(lines[index+8].split()[2]) + elif line.strip() == 'Pose Accuracy Variation': + index = lines.index(line) + ws.cell(row=13, column=7).value = float(lines[index+4].split()[1]) + ws.cell(row=14, column=7).value = float(lines[index+5].split()[1]) + ws.cell(row=15, column=7).value = float(lines[index+6].split()[1]) + elif line.strip() == 'Distance Accuracy': + index = lines.index(line) + ws.cell(row=16, column=7).value = float(lines[index + 4].split()[1]) + ws.cell(row=17, column=7).value = float(lines[index + 4].split()[2]) + elif line.strip() == 'Stabilisation Time and Overshoot': + index = lines.index(line) + ws.cell(row=18, column=7).value = float(lines[index + 7].split()[3]) + ws.cell(row=19, column=7).value = float(lines[index + 7].split()[2]) + elif line.strip() == 'Velocity Accuracy and Repeatability': + index = lines.index(line) + ws.cell(row=20, column=7).value = float(lines[index + 4].split()[1]) + ws.cell(row=21, column=7).value = float(lines[index + 4].split()[2]) + ws.cell(row=22, column=7).value = float(lines[index + 4].split()[3]) + elif line.strip()[:31] == 'Path Accuracy and Repeatability': + index = lines.index(line) + ws.cell(row=29, column=7).value = float(lines[index + 4].split()[1]) + ws.cell(row=30, column=7).value = float(lines[index + 4].split()[2]) + elif line.strip() == 'Corner Overshoot and Roundoff': + index = lines.index(line) + ws.cell(row=35, column=7).value = float(lines[index + 4].split()[1]) + ws.cell(row=36, column=7).value = float(lines[index + 4].split()[2]) + elif line.strip() == 'Robot Weaving': + index = lines.index(line) + ws.cell(row=41, column=7).value = float(lines[index + 4].split()[2]) + ws.cell(row=42, column=7).value = float(lines[index + 4].split()[3]) + ws.cell(row=43, column=7).value = float(lines[index + 4].split()[4]) + else: + pass + + +def p_iso_100(file, p_files, ws, tmpfile): + p_files.append(file) + + pdf = pdfplumber.open(file) + with open(tmpfile, mode='w', encoding='utf-8') as fb: + for page in pdf.pages: + fb.write(page.extract_text()) + + with open(tmpfile, mode='r', encoding='utf-8') as fb: + lines = fb.readlines() + lines = [line for line in lines if not line.startswith('Page ')] + for line in lines: + if line.strip() == 'Velocity Accuracy and Repeatability': + index = lines.index(line) + ws.cell(row=26, column=7).value = float(lines[index + 4].split()[1]) + ws.cell(row=27, column=7).value = float(lines[index + 4].split()[2]) + ws.cell(row=28, column=7).value = float(lines[index + 4].split()[3]) + elif line.strip()[:31] == 'Path Accuracy and Repeatability': + index = lines.index(line) + ws.cell(row=33, column=7).value = float(lines[index + 4].split()[1]) + ws.cell(row=34, column=7).value = float(lines[index + 4].split()[2]) + elif line.strip() == 'Corner Overshoot and Roundoff': + index = lines.index(line) + ws.cell(row=39, column=7).value = float(lines[index + 4].split()[1]) + ws.cell(row=40, column=7).value = float(lines[index + 4].split()[2]) + elif line.strip() == 'Robot Weaving': + index = lines.index(line) + ws.cell(row=47, column=7).value = float(lines[index + 4].split()[2]) + ws.cell(row=48, column=7).value = float(lines[index + 4].split()[3]) + ws.cell(row=49, column=7).value = float(lines[index + 4].split()[4]) + else: + pass + + +def p_iso_1000(file, p_files, ws, tmpfile): + p_files.append(file) + + pdf = pdfplumber.open(file) + with open(tmpfile, mode='w', encoding='utf-8') as fb: + for page in pdf.pages: + fb.write(page.extract_text()) + + with open(tmpfile, mode='r', encoding='utf-8') as fb: + lines = fb.readlines() + lines = [line for line in lines if not line.startswith('Page ')] + for line in lines: + if line.strip() == 'Velocity Accuracy and Repeatability': + index = lines.index(line) + ws.cell(row=23, column=7).value = float(lines[index + 4].split()[1]) + ws.cell(row=24, column=7).value = float(lines[index + 4].split()[2]) + ws.cell(row=25, column=7).value = float(lines[index + 4].split()[3]) + elif line.strip()[:31] == 'Path Accuracy and Repeatability': + index = lines.index(line) + ws.cell(row=31, column=7).value = float(lines[index + 4].split()[1]) + ws.cell(row=32, column=7).value = float(lines[index + 4].split()[2]) + elif line.strip() == 'Corner Overshoot and Roundoff': + index = lines.index(line) + ws.cell(row=37, column=7).value = float(lines[index + 4].split()[1]) + ws.cell(row=38, column=7).value = float(lines[index + 4].split()[2]) + elif line.strip() == 'Robot Weaving': + index = lines.index(line) + ws.cell(row=44, column=7).value = float(lines[index + 4].split()[2]) + ws.cell(row=45, column=7).value = float(lines[index + 4].split()[3]) + ws.cell(row=46, column=7).value = float(lines[index + 4].split()[4]) + else: + pass + + +def main(path, w2t): + dirs, files = traversal_files(path, 1) + + try: + wb = load_workbook(path + "/iso-results.xlsx") + ws = wb.active + tmpfile = f"{path}\\data.txt" + except Exception as Err: + w2t(f"发生错误:{Err}", 0, 2) + + p_files = [] + for file in files: + if file.endswith('.pdf') and file.split('\\')[-1] == 'ISO.pdf': + w2t(f"正在处理{file}......") + p_iso(file, p_files, ws, tmpfile) + w2t(f"文件{file}已处理完毕。\n") + + elif file.endswith('.pdf') and file.split('\\')[-1] == 'ISO-V100.pdf': + w2t(f"正在处理{file}......") + p_iso_100(file, p_files, ws, tmpfile) + w2t(f"文件{file}已处理完毕。\n") + + elif file.endswith('.pdf') and file.split('\\')[-1] == 'ISO-V1000.pdf': + w2t(f"正在处理{file}......") + p_iso_1000(file, p_files, ws, tmpfile) + w2t(f"文件{file}已处理完毕。\n") + + else: + pass + wb.save(path + '/iso-results.xlsx') + wb.close() + + if len(p_files) == 0: + w2t(f"目录 {path} 下没有需要处理的文件,需要确认......", 0, 3) + else: + remove(tmpfile) + w2t("------------------------------------------") + w2t("所有文件均已处理完毕!") + + +if __name__ == '__main__': + main(path=argv[1], w2t=argv[2]) diff --git a/aio/vers b/aio/vers index abace5c..655cf7a 100644 --- a/aio/vers +++ b/aio/vers @@ -1 +1 @@ -1.1.1 @ 05/30/2024 +0.1.2 @ 06/01/2024