Template
353 lines
14 KiB
Python
353 lines
14 KiB
Python
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个码生成的码带的所有ID:1,2,1,3,...,699,700;
|
|||
|
|
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()
|