Files
TapeGenerator/QRdoubletape.py
T
shuai.liandCursor ef1f59d41a Add TapeGenerator source code and EXE package with assets.
Include QRdoubletape scripts, packaged executables, and code_ims/QRTape resources for running the tape generator.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-24 13:13:54 +08:00

353 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from concurrent.futures import ThreadPoolExecutor
import os
import re
import sys
import time
from bs4 import BeautifulSoup
import threading
import tkinter as tk
from tkinter import messagebox
def get_app_dir():
if getattr(sys, 'frozen', False):
return os.path.dirname(os.path.abspath(sys.executable))
return os.path.dirname(os.path.abspath(__file__))
# 定义常量
i = 1
j = 0
scale_count = 0
# 489300
n = 700
m2cm = 100
Segments = int((n*m2cm//2)/5000) + 1
APP_DIR = get_app_dir()
SOURCE_HTML_PATH = os.path.join(APP_DIR, 'code_ims')
OUTPUT_FOLDER = os.path.join(APP_DIR, 'QRTape')
# 修改二维码的SVG值
def data_scaling(svg_content):
replacements = {
"40": "7.092198581560284",
"80": "14.18439716312057",
"120": "21.27659574468085",
"160": "28.36879432624113",
"200": "35.46099290780142",
"240": "42.55319148936170",
"280": "49.64539007092199",
"320": "56.73758865248227",
"400": "56.73758865248227",
"420": "56.73758865248227"
}
# 替换函数
def replace_text(text):
for old, new in replacements.items():
text = re.sub(r'\b' + re.escape(old) + r'\b', new, text)
return text
return replace_text(svg_content)
# 将svg与二维码的起点对齐
subtract_value = 7.092198581560284
# 定义正则表达式模式,用于匹配 x 和 y 属性
pattern = re.compile(r'(x|y)="\d+(\.\d+)?"')
# 定义一个替换函数,用于减去指定值
def subtract_coords(match):
attr = match.group(1)
value = float(match.group(0).split('=')[1].strip('"'))
new_value = value - subtract_value
return f'{attr}="{new_value:.4f}"'
# 获取二维码的 SVG 数据
def get_qr_code_svg(html_file):
with open(html_file, 'r', encoding='utf-8') as file:
html_content = file.read()
soup = BeautifulSoup(html_content, 'html.parser')
for text_tag in soup.find_all('text'):
text_tag.decompose()
svgs = soup.find_all('svg')
all_svg_content = ""
for svg in svgs:
all_svg_content += str(svg) + "\n"
return all_svg_content
def save_svg_to_html(svg_content, output_path, segment, length):
"""将 SVG 内容保存到 HTML 文件中"""
# if segment == Segments:
tick_mark_html = ""
tick_interval = 189.1252955082742 # 每隔189像素一个刻度线
total_width = n*(n-1)*75.65011820330969 # outer-container 的宽度
outer_container_width = length*56.73758865248227 + (length - 1)*18.91252955082742 + 18.91252955082742
for i in range(0, int(total_width // tick_interval) + 1):
scale_line_left = i * 189.1252955082742
if scale_line_left <= outer_container_width:
tick_position = i * tick_interval
# 刻度线
tick_mark_html += f'<div class="line" style="left: {tick_position}px;"></div>\n'
tick_label = int(int(require_length_start * m2cm) + 5 * i + 10000 * (segment - 1))
# 刻度数
tick_mark_html += f'<div class="scale-value" style="left: {tick_position + 3}px;">{tick_label}</div>\n'
html_content = f"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
.outer-container1 {{
border-top: 2px solid black; /* 上边框 */
border-right: 2px solid black; /* 右边框 */
border-left: 6px solid black; /* 左边框加粗 */
border-bottom: 6px solid black; /* 下边框加粗 */
display: flex;
justify-content: center; /* 居中对齐内容 */
align-items: center; /* 居中对齐内容 */
margin: 0px; /* 可选:外边距以确保大框与页面其他部分之间有距离 */
position: absolute; /* 使用绝对定位 */
top: 10%; /* 从页面顶部 % 的位置开始 */
left: 2%; /* 从页面左侧 % 的位置开始 */
# width: {n*(n-1)/2/Segments*56.73758865248227 + (n*(n-1)/2/Segments-1)*18.91252955082742 + 18.91252955082742}px; /* 显式设置大框的宽度 */
width: {outer_container_width}px; /* 显式设置大框的宽度 */
height: 169.4167px; /* 显式设置大框的高度 */
}}
.grid-container1 {{
display: grid;
position: absolute;
grid-template-columns: repeat({length}, 56.73758865248227px);
grid-template-rows: repeat(2, 56.73758865248227px);
grid-gap: 18.91252955082742px 18.91252955082742px;
padding: 0px;
left: 9.456264775413712px;
top: 18.91252955082742px;
}}
.line {{
position: absolute;
width: 2px; /* 刻度线的宽度 */
height: 10px; /* 刻度线的高度 */
background-color: black; /* 刻度线的颜色 */
bottom: 0; /* 将刻度线放置在容器的底部 */
}}
.scale-value {{
position: absolute;
bottom: -1px; /* 将数字放置在刻度线的上方 */
font-size: 12px; /* 字体大小 */
color: black; /* 字体颜色 */
# left: 0; /* 设置初始 left 属性,稍后会在每个数字上覆盖 */
transform: translateX(45%); /* 水平居中对齐数字 */
}}
</style>
</head>
<body>
<div class="outer-container1">
<div class="grid-container1" id="outerContainer">
{svg_content}
</div>
{tick_mark_html}
</div>
</body>
</html>
"""
with open(output_path, 'w', encoding='utf-8') as file:
file.write(html_content)
print(f"html 文件已保存至" + output_path)
def process_segment(segment_num, result_dict, output_path):
"""处理单个段的二维码生成"""
all_scaled_svg_content = ""
count = 0
segment_size = len(result_dict[f"result_{segment_num}_1"])
for k in range(1, 3): # 处理两排
for j in range(segment_size):
html_file = os.path.join(SOURCE_HTML_PATH, f'im{result_dict[f"result_{segment_num}_{k}"][j]}.html')
svg_contents = get_qr_code_svg(html_file)
scaled_svg_content = data_scaling(svg_contents)
updated_svg_content = pattern.sub(subtract_coords, scaled_svg_content)
all_scaled_svg_content += updated_svg_content
count += 1
save_svg_to_html(all_scaled_svg_content, output_path, segment_num, len(result_dict[f"result_{segment_num}_{k}"]))
print(f"码带段{segment_num}处理完成")
def create_html_with_qrcodes(output_path, segments = Segments):
try:
# result_all代表700个码生成的码带的所有ID1,2,1,3,...699700
result_all = []
# result_all_1代表result_all的第一排码ID
result_all_1 = []
# result_all_2代表result_all的第二排码ID
result_all_2 = []
result_dict = {}
for i in range(1, segments + 1):
# result_dict表示一个 包含多个列表的字典。例如:
# "result_1_1": [1, 2, 3], # 第一段,第一排码
# "result_1_2": [7, 8, 9], # 第一段,第二排码
# "result_2_1": [4, 5], # 第二段,第一排码
# "result_2_2": [10, 11] # 第二段,第二排码
result_dict[f"result_{i}_1"] = []
result_dict[f"result_{i}_2"] = []
global scale_count
for i in range(1, n+1):
for j in range(i+1, n+1):
result_all.append(i)
result_all.append(j)
for i in range(0, len(result_all)):
if(i < n*(n-1) // 2):
result_all_1.append(result_all[i])
else:
result_all_2.append(result_all[i])
for i in range(0, segments):
for j in range(int(require_length_start * m2cm // 2), 5000 + int(require_length_start * m2cm // 2)):
j = j + 5000 * i
if(j >= len(result_all_1) or j >= require_length_end * m2cm // 2):
break
result_dict[f"result_{i+1}_1"].append(result_all_1[j])
result_dict[f"result_{i+1}_2"].append(result_all_2[j])
# for i in range(1, segments + 1):
# print(f"第{i}段第1排:" + f" length:" + str(len(result_dict[f"result_{i}_1"])))
# print(f"第{i}段第2排:" + f" length:" + str(len(result_dict[f"result_{i}_2"])))
start_time = time.perf_counter()
print("开始处理,请稍等...")
with ThreadPoolExecutor(max_workers=min(segments, os.cpu_count() * 2)) as executor:
futures = []
for i in range(1, segments + 1):
if len(result_dict[f"result_{i}_1"]) == 0 :
continue
future = executor.submit(
process_segment,
i,
result_dict,
output_path
)
futures.append(future)
# 等待所有任务完成
for future in futures:
future.result()
print("所有码带段处理完成")
end_time = time.perf_counter()
elapsed_time = (end_time - start_time) * 1000
print(f"段落处理执行耗时: {elapsed_time:.4f} 毫秒")
except Exception as e:
# 捕获生成码带时的错误
print(f"生成码带时发生错误: {str(e)}")
def run_gui():
def on_generate():
try:
global require_length_start, require_length_end
# 获取用户输入的码带长度
require_length_start = float(entry_start.get())
require_length_end = float(entry_end.get())
if len(str(require_length_start).split('.')[1]) > 2 or len(str(require_length_end).split('.')[1]) > 2:
raise ValueError("最多保留小数点后两位,请重新输入!")
if require_length_start < 0:
raise ValueError("码带起点必须大于或等于零")
if require_length_end < 0:
raise ValueError("码带终点必须大于零")
if require_length_end >= 4800:
raise ValueError("码带终点必须小于或等于4800")
if require_length_end < require_length_start:
raise ValueError("码带终点必须大于起点")
output_html_path = os.path.normpath(OUTPUT_FOLDER)
if not os.path.exists(output_html_path):
os.makedirs(output_html_path)
file_name = f"{require_length_start}_{require_length_end}.html"
OUTPUT_HTML_PATH = os.path.normpath(os.path.join(output_html_path, file_name))
if not os.path.exists(output_html_path):
messagebox.showerror("错误", f"输出文件夹路径不存在: {output_html_path}")
return
if not os.path.exists(SOURCE_HTML_PATH):
messagebox.showerror("错误", f"请将 code_ims 文件夹放在程序同一目录下:\n{SOURCE_HTML_PATH}")
return
def show_open_folder_button():
last_output_dir["path"] = output_html_path
if not btn_open.winfo_ismapped():
btn_open.pack(pady=5)
def show_loading_window():
loading_window = tk.Toplevel(root)
loading_window.title("生成码带")
loading_window.geometry("300x100")
label = tk.Label(loading_window, text="正在生成码带中,请稍等......", font=("Arial", 14))
label.pack(pady=30)
loading_window.update_idletasks()
create_html_with_qrcodes(OUTPUT_HTML_PATH)
loading_window.destroy()
root.after(0, show_open_folder_button)
messagebox.showinfo("成功", "码带生成成功,文件已保存至程序目录下QRTape文件夹")
threading.Thread(target=show_loading_window, daemon=True).start()
except ValueError as e:
# 如果输入的长度无效,弹出错误提示框
messagebox.showerror("错误", f"输入的码带长度无效: {str(e)}")
except Exception as e:
# 其他异常处理
messagebox.showerror("错误", f"发生错误: {str(e)}")
last_output_dir = {"path": None}
def on_open_folder():
folder_path = last_output_dir["path"]
if not folder_path or not os.path.exists(folder_path):
messagebox.showerror("错误", "还没有生成码带,或文件夹不存在")
return
os.startfile(folder_path)
# 创建主窗口
root = tk.Tk()
root.title("新码带生成器")
root.geometry("300x300")
label_start = tk.Label(root, text="请输入码带起点(m):", font=("Arial", 14))
label_start.pack(padx=10, pady=5)
# 输入框:输入码带起点
entry_start = tk.Entry(root, width=10, font=("Arial", 14))
entry_start.pack(padx=10, pady=5)
# 标签:提示输入码带终点
label_end = tk.Label(root, text="请输入码带终点(m):", font=("Arial", 14))
label_end.pack(padx=10, pady=5)
# 输入框:输入码带终点
entry_end = tk.Entry(root, width=10, font=("Arial", 14))
entry_end.pack(padx=10, pady=5)
# 按钮:点击生成码带
btn_generate = tk.Button(root, text="Click to Generate", command=on_generate, font=("Arial", 15))
btn_generate.pack(pady=20)
btn_open = tk.Button(root, text="打开生成文件夹", command=on_open_folder, font=("Arial", 14))
# 运行GUI
root.mainloop()
if __name__ == '__main__':
run_gui()