Template
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>
This commit is contained in:
@@ -0,0 +1,427 @@
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
from bs4 import BeautifulSoup
|
||||
import threading
|
||||
import tkinter as tk
|
||||
from tkinter import messagebox
|
||||
|
||||
|
||||
# 这个是旧码带生成。组合顺序不变:1,2, 1,3, 1,4, ...
|
||||
# 码带实际长度只由输入的起点、终点决定,码本大小按终点自动计算
|
||||
|
||||
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
|
||||
m2cm = 100
|
||||
APP_DIR = get_app_dir()
|
||||
HTML_FOLDER = os.path.join(APP_DIR, 'code_ims')
|
||||
OUTPUT_FOLDER = os.path.join(APP_DIR, 'QRTape')
|
||||
|
||||
# 每个码的 SVG 只解析、缩放一次
|
||||
_processed_svg_cache = {}
|
||||
|
||||
# 修改二维码的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 get_processed_svg(code_id):
|
||||
"""同一编号的码只读取并处理一次。"""
|
||||
cached = _processed_svg_cache.get(code_id)
|
||||
if cached is not None:
|
||||
return cached
|
||||
html_file = os.path.join(HTML_FOLDER, f'im{code_id}.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)
|
||||
_processed_svg_cache[code_id] = updated_svg_content
|
||||
return updated_svg_content
|
||||
|
||||
|
||||
def save_svg_to_html(svg_content, output_path, length):
|
||||
"""将 SVG 内容保存到 HTML 文件中"""
|
||||
tick_mark_html = []
|
||||
tick_interval = 189.1252955082742 # 每隔188像素一个刻度线
|
||||
outer_container_width = length * 56.73758865248227 + (length - 1) * 18.91252955082742 + 18.91252955082742
|
||||
max_ticks = int(outer_container_width // tick_interval) + 1
|
||||
start_cm = int(require_length_start * m2cm)
|
||||
for i in range(0, max_ticks):
|
||||
tick_position = i * tick_interval
|
||||
if tick_position <= outer_container_width:
|
||||
tick_mark_html.append(f'<div class="line" style="left: {tick_position}px;"></div>\n')
|
||||
tick_label = start_cm + 5 * i
|
||||
tick_mark_html.append(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: {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; /* 字体颜色 */
|
||||
transform: translateX(45%); /* 水平居中对齐数字 */
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="outer-container1">
|
||||
<div class="grid-container1" id="outerContainer">
|
||||
{svg_content}
|
||||
</div>
|
||||
{''.join(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 codebook_size_for_end(end_m):
|
||||
"""旧组合下,覆盖到终点所需的最小码数量:n*(n-1) 厘米 >= 终点厘米。"""
|
||||
end_idx = int(end_m * m2cm // 2)
|
||||
needed = 2 * end_idx
|
||||
if needed <= 2:
|
||||
return 2
|
||||
n = math.ceil((1 + math.sqrt(1 + 4 * needed)) / 2)
|
||||
while n * (n - 1) < needed:
|
||||
n += 1
|
||||
return n
|
||||
|
||||
|
||||
def build_code_rows(code_count):
|
||||
"""按旧逻辑生成双排码 ID:先 (i,j) 依次展开,再对半拆成上下两排。"""
|
||||
result_all = []
|
||||
for i in range(1, code_count + 1):
|
||||
for j in range(i + 1, code_count + 1):
|
||||
result_all.append(i)
|
||||
result_all.append(j)
|
||||
|
||||
half = code_count * (code_count - 1) // 2
|
||||
return result_all[:half], result_all[half:]
|
||||
|
||||
|
||||
def create_html_with_qrcodes(output_path, codebook_end=None):
|
||||
try:
|
||||
start_idx = int(require_length_start * m2cm // 2)
|
||||
end_idx = int(require_length_end * m2cm // 2)
|
||||
row_len = end_idx - start_idx
|
||||
if row_len <= 0:
|
||||
raise ValueError("所选区间没有可生成的码带内容")
|
||||
|
||||
if codebook_end is None:
|
||||
codebook_end = require_length_end
|
||||
code_count = codebook_size_for_end(codebook_end)
|
||||
max_code_file = os.path.join(HTML_FOLDER, f'im{code_count}.html')
|
||||
if not os.path.exists(max_code_file):
|
||||
raise ValueError(f"终点过长,缺少码文件 im{code_count}.html")
|
||||
|
||||
result_all_1, result_all_2 = build_code_rows(code_count)
|
||||
|
||||
row1 = result_all_1[start_idx:start_idx + row_len]
|
||||
row2 = result_all_2[start_idx:start_idx + row_len]
|
||||
|
||||
start_time = time.perf_counter()
|
||||
print("开始处理,请稍等...")
|
||||
|
||||
needed_ids = set(row1)
|
||||
needed_ids.update(row2)
|
||||
|
||||
def preload(code_id):
|
||||
get_processed_svg(code_id)
|
||||
|
||||
with ThreadPoolExecutor(max_workers=min(8, os.cpu_count() or 4)) as executor:
|
||||
list(executor.map(preload, needed_ids))
|
||||
|
||||
svg_parts = [get_processed_svg(code_id) for code_id in row1]
|
||||
svg_parts.extend(get_processed_svg(code_id) for code_id in row2)
|
||||
save_svg_to_html(''.join(svg_parts), output_path, row_len)
|
||||
|
||||
print("所有码带段处理完成")
|
||||
elapsed_time = (time.perf_counter() - start_time) * 1000
|
||||
print(f"段落处理执行耗时: {elapsed_time:.4f} 毫秒")
|
||||
except Exception as e:
|
||||
print(f"生成码带时发生错误: {str(e)}")
|
||||
raise
|
||||
|
||||
|
||||
def _decimal_places(value):
|
||||
text = str(value)
|
||||
if '.' not in text:
|
||||
return 0
|
||||
return len(text.split('.')[1])
|
||||
|
||||
|
||||
def _format_m(value):
|
||||
return f"{value:.1f}"
|
||||
|
||||
|
||||
def _parse_range(start_text, end_text, start_label="码带起点", end_label="码带终点"):
|
||||
start = float(start_text)
|
||||
end = float(end_text)
|
||||
if _decimal_places(start) > 2 or _decimal_places(end) > 2:
|
||||
raise ValueError("最多保留小数点后两位,请重新输入!")
|
||||
if start < 0:
|
||||
raise ValueError(f"{start_label}必须大于或等于零")
|
||||
if end <= 0:
|
||||
raise ValueError(f"{end_label}必须大于零")
|
||||
if end <= start:
|
||||
raise ValueError(f"{end_label}必须大于{start_label}")
|
||||
return start, end
|
||||
|
||||
|
||||
def _output_dir():
|
||||
output_html_path = os.path.normpath(OUTPUT_FOLDER)
|
||||
if not os.path.exists(output_html_path):
|
||||
os.makedirs(output_html_path)
|
||||
if not os.path.exists(output_html_path):
|
||||
raise FileNotFoundError(f"输出文件夹路径不存在: {output_html_path}")
|
||||
if not os.path.exists(HTML_FOLDER):
|
||||
raise FileNotFoundError(f"请将 code_ims 文件夹放在程序同一目录下:\n{HTML_FOLDER}")
|
||||
return output_html_path
|
||||
|
||||
|
||||
def run_gui():
|
||||
mode = {"value": "parent"}
|
||||
parent_range = {"start": None, "end": None}
|
||||
|
||||
last_output_dir = {"path": None}
|
||||
|
||||
def show_open_folder_button(folder_path):
|
||||
last_output_dir["path"] = folder_path
|
||||
if not btn_open.winfo_ismapped():
|
||||
btn_open.pack(pady=5)
|
||||
|
||||
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)
|
||||
|
||||
def start_generate(file_name, codebook_end):
|
||||
output_html_path = _output_dir()
|
||||
output_file = os.path.normpath(os.path.join(output_html_path, file_name))
|
||||
|
||||
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()
|
||||
try:
|
||||
create_html_with_qrcodes(output_file, codebook_end=codebook_end)
|
||||
except Exception as e:
|
||||
loading_window.destroy()
|
||||
messagebox.showerror("错误", f"生成码带时发生错误: {str(e)}")
|
||||
return
|
||||
loading_window.destroy()
|
||||
root.after(0, show_open_folder_button, output_html_path)
|
||||
messagebox.showinfo("成功", "码带生成成功,文件已保存至程序目录下QRTape文件夹")
|
||||
|
||||
threading.Thread(target=show_loading_window, daemon=True).start()
|
||||
|
||||
def on_generate():
|
||||
try:
|
||||
global require_length_start, require_length_end
|
||||
if mode["value"] == "parent":
|
||||
require_length_start, require_length_end = _parse_range(
|
||||
entry_start.get(), entry_end.get()
|
||||
)
|
||||
file_name = f"{_format_m(require_length_start)}_{_format_m(require_length_end)}.html"
|
||||
start_generate(file_name, codebook_end=require_length_end)
|
||||
return
|
||||
|
||||
sub_start, sub_end = _parse_range(
|
||||
entry_start.get(), entry_end.get(), "子码带起点", "子码带终点"
|
||||
)
|
||||
parent_start = parent_range["start"]
|
||||
parent_end = parent_range["end"]
|
||||
if sub_start < parent_start or sub_end > parent_end:
|
||||
raise ValueError(
|
||||
f"子码带必须在母码带 {_format_m(parent_start)}~{_format_m(parent_end)} m 范围内"
|
||||
)
|
||||
require_length_start = sub_start
|
||||
require_length_end = sub_end
|
||||
file_name = (
|
||||
f"{_format_m(parent_start)}_{_format_m(parent_end)}"
|
||||
f"_sub_{_format_m(sub_start)}_{_format_m(sub_end)}.html"
|
||||
)
|
||||
start_generate(file_name, codebook_end=parent_end)
|
||||
except ValueError as e:
|
||||
messagebox.showerror("错误", f"输入的码带长度无效: {str(e)}")
|
||||
except Exception as e:
|
||||
messagebox.showerror("错误", f"发生错误: {str(e)}")
|
||||
|
||||
def show_sub_page():
|
||||
try:
|
||||
parent_start, parent_end = _parse_range(entry_start.get(), entry_end.get())
|
||||
except ValueError as e:
|
||||
messagebox.showerror("错误", f"请先输入有效的母码带起点和终点: {str(e)}")
|
||||
return
|
||||
except Exception as e:
|
||||
messagebox.showerror("错误", f"发生错误: {str(e)}")
|
||||
return
|
||||
|
||||
parent_range["start"] = parent_start
|
||||
parent_range["end"] = parent_end
|
||||
mode["value"] = "sub"
|
||||
label_start.config(text="请输入子码带起点(m):")
|
||||
label_end.config(text="请输入子码带终点(m):")
|
||||
parent_info.config(text=f"母码带: {_format_m(parent_start)} ~ {_format_m(parent_end)} m")
|
||||
entry_start.delete(0, tk.END)
|
||||
entry_end.delete(0, tk.END)
|
||||
btn_sub.pack_forget()
|
||||
btn_back.pack(pady=5)
|
||||
if last_output_dir["path"]:
|
||||
if btn_open.winfo_ismapped():
|
||||
btn_open.pack_forget()
|
||||
btn_open.pack(pady=5)
|
||||
|
||||
def show_parent_page():
|
||||
mode["value"] = "parent"
|
||||
label_start.config(text="请输入码带起点(m):")
|
||||
label_end.config(text="请输入码带终点(m):")
|
||||
parent_info.config(text="")
|
||||
entry_start.delete(0, tk.END)
|
||||
entry_end.delete(0, tk.END)
|
||||
if parent_range["start"] is not None:
|
||||
entry_start.insert(0, str(parent_range["start"]))
|
||||
entry_end.insert(0, str(parent_range["end"]))
|
||||
btn_back.pack_forget()
|
||||
btn_sub.pack(pady=5)
|
||||
if last_output_dir["path"]:
|
||||
if btn_open.winfo_ismapped():
|
||||
btn_open.pack_forget()
|
||||
btn_open.pack(pady=5)
|
||||
|
||||
root = tk.Tk()
|
||||
root.title("旧码带生成器")
|
||||
root.geometry("320x380")
|
||||
|
||||
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)
|
||||
|
||||
parent_info = tk.Label(root, text="", font=("Arial", 11), fg="#444444")
|
||||
parent_info.pack(padx=10, pady=2)
|
||||
|
||||
btn_generate = tk.Button(root, text="Click to Generate", command=on_generate, font=("Arial", 15))
|
||||
btn_generate.pack(pady=10)
|
||||
|
||||
btn_sub = tk.Button(root, text="生成子码带", command=show_sub_page, font=("Arial", 14))
|
||||
btn_sub.pack(pady=5)
|
||||
|
||||
btn_back = tk.Button(root, text="返回", command=show_parent_page, font=("Arial", 14))
|
||||
btn_open = tk.Button(root, text="打开生成文件夹", command=on_open_folder, font=("Arial", 14))
|
||||
|
||||
root.mainloop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run_gui()
|
||||
Reference in New Issue
Block a user