├── .gitignore ├── LICENSE ├── README.md ├── config.json ├── gui.py ├── gui.spec ├── modify.py └── requitrments.txt /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | dist -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FuckJLC 2 | 3 | 核心代码源自 https://github.com/acha666/FuckJLC 4 | 5 | 将其他EDA软件的gerber转换为立创EDA格式的脚本 6 | 7 | # Usage 8 | 9 | ## 命令行版本`modify.py` 10 | 11 | 首先查看`config.yaml`,修改工作目录等配置信息,选择适合你的规则 12 | 13 | 输出目录可为空,默认是 Gerber 文件夹下的 output 文件夹(没有这个文件夹会自动创建) 14 | 15 | 然后运行脚本,在输出目录中查看处理完成的gerber 16 | 17 | 最后复制钻孔文件 18 | 19 | ``` shell 20 | # yum/apt/brew/scoop/winget install python 21 | pip install json 22 | python modify.py 23 | ``` 24 | 25 | # 文件结构 26 | 27 | * `modify.py` 脚本主文件 28 | * `config.yaml` 通用配置信息 29 | 30 | ## GUI版本`gui.py` 31 | 32 | 1. 下载 [Release](https://github.com/wc7086/fuckself/releases) 中的 [FKJLC_win-x64.zip](https://github.com/wc7086/fuckself/releases/download/v0.1.0/FKJLC_win-x64.zip) 解压后直接运行,有概率误报 33 | 34 | 2. 运行目录中的`gui.py` 35 | 36 | ```shell 37 | pip install json 38 | python gui.py 39 | ``` 40 | 41 | 输出目录可为空,默认是 Gerber 文件夹下的 output 文件夹(没有这个文件夹会自动创建) 42 | 43 | 44 | # 提示 45 | 46 | 目前仅对有限格式的gerber做适配,其他软件请发Issue并附带目录结构或者自力更生PR 47 | 48 | 脚本并未严格测试,仅给各位提供一个绕过检测的思路,保险起见仍建议各位手动进行修改 49 | 50 | 由于钻孔文件格式千奇百怪,脚本**不处理钻孔文件**,脚本完成后你需要手动复制钻孔文件 51 | 52 | 作者并没有仔细研究嘉立创的判定规则,目前看来凑合能用,就这样吧 53 | 54 | 作者不为使用脚本造成的任何后果负责 55 | 56 | 欢迎一切 新功能/bug/建议/对线/改错字 Issue/PR 57 | 58 | # 工作原理 59 | 60 | 脚本将会将你的gerber重命名为立创EDA的命名格式,并在gerber文件的头部添加立创EDA的注释信息 61 | 62 | 除此之外,脚本不会做任何处理 63 | 64 | **低调使用** 65 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "WorkDir": "C:\\Users\\Public\\Desktop\\", 3 | "DestDir": "", 4 | "GerberType": "KiCad", 5 | "Header": "G04 EasyEDA Pro v1.9.28, 2023-01-14 19:19:00*\nG04 Gerber Generator version 0.3*", 6 | "TextFileName": "PCB下单必读.txt", 7 | "TextFileContent": "如何进行PCB下单\n\n请查看:\nhttps://docs.lceda.cn/cn/PCB/Order-PCB", 8 | "FileName": { 9 | "Outline": "Gerber_BoardOutlineLayer.GKO", 10 | "Top_Cu": "Gerber_TopLayer.GTL", 11 | "Bottom_Cu": "Gerber_BottomLayer.GBL", 12 | "InnerLayer1_Cu": "Gerber_InnerLayer1.G1", 13 | "InnerLayer2_Cu": "Gerber_InnerLayer2.G2", 14 | "InnerLayer3_Cu": "Gerber_InnerLayer3.G3", 15 | "InnerLayer4_Cu": "Gerber_InnerLayer4.G4", 16 | "Top_SilkScreen": "Gerber_TopSilkscreenLayer.GTO", 17 | "Bottom_SilkScreen": "Gerber_BottomSilkscreenLayer.GBO", 18 | "Top_SolderMask": "Gerber_TopSolderMaskLayer.GTS", 19 | "Bottom_SolderMask": "Gerber_BottomSolderMaskLayer.GBS", 20 | "Top_SolderPaste": "Gerber_TopPasteMaskLayer.GTP", 21 | "Bottom_SolderPaste": "Gerber_BottomPasteMaskLayer.GBP", 22 | "Drill_Pth": "Drill_PTH_Through.DRL", 23 | "Drill_Npth": "Drill_NPTH_Through.DRL", 24 | "Drill_Drawing": "Drill_Through.GD1" 25 | }, 26 | "GerberCustom": { 27 | "KiCad": { 28 | "Outline": "-Edge_Cuts", 29 | "Top_Cu": "-F_Cu", 30 | "Bottom_Cu": "-B_Cu", 31 | "Mechanical1": "(\\.gm1|\\.GM1)$", 32 | "Mechanical13": "(\\.gm13|\\.GM13)$", 33 | "InnerLayer1_Cu": "-In1_Cu", 34 | "InnerLayer2_Cu": "-In2_Cu", 35 | "InnerLayer3_Cu": "-In3_Cu", 36 | "InnerLayer4_Cu": "-In4_Cu", 37 | "Top_SilkScreen": "-F_SilkS", 38 | "Bottom_SilkScreen": "-B_SilkS", 39 | "Top_SolderMask": "-F_Mask", 40 | "Bottom_SolderMask": "-B_Mask", 41 | "Top_SolderPaste": "-F_Paste", 42 | "Bottom_SolderPaste": "-B_Paste", 43 | "Drill_Pth": "-PTH", 44 | "Drill_Npth": "-NPTH" 45 | }, 46 | "AD": { 47 | "Outline": "(\\.gko|\\.GKO)$", 48 | "Top_Cu": "(\\.gtl|\\.GTL)$", 49 | "Bottom_Cu": "(\\.gbl|\\.GBL)$", 50 | "Mechanical1": "(\\.gm1|\\.GM1)$", 51 | "Mechanical13": "(\\.gm13|\\.GM13)$", 52 | "InnerLayer1_Cu": "(\\.g1|\\.G1)$", 53 | "InnerLayer2_Cu": "(\\.g2|\\.G2)$", 54 | "InnerLayer3_Cu": "(\\.g3|\\.G3)$", 55 | "InnerLayer4_Cu": "(\\.g4|\\.G4)$", 56 | "Top_SilkScreen": "(\\.gto|\\.GTO)$", 57 | "Bottom_SilkScreen": "(\\.gbo|\\.GBO)$", 58 | "Top_SolderMask": "(\\.gts|\\.GTS)$", 59 | "Bottom_SolderMask": "(\\.gbs|\\.GBS)$", 60 | "Top_SolderPaste": "(\\.gtp|\\.GTP)$", 61 | "Bottom_SolderPaste": "(\\.gbp|\\.GBP)$", 62 | "Drill_Drawing": "(\\.gd1|\\.GD1)$" 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /gui.py: -------------------------------------------------------------------------------- 1 | import json, os, re, shutil, sys 2 | import tkinter as tk 3 | from tkinter import ttk 4 | from tkinter import filedialog 5 | import windnd 6 | 7 | # 创建主窗口 8 | root = tk.Tk() 9 | root.title("GUI Window") 10 | 11 | # 获取屏幕尺寸以及窗口尺寸 12 | screen_width = root.winfo_screenwidth() 13 | screen_height = root.winfo_screenheight() 14 | window_width = 330 15 | window_height = 330 16 | 17 | # 计算屏幕中央的坐标 18 | center_x = int(screen_width/2 - window_width/2) 19 | center_y = int(screen_height/2 - window_height/2) 20 | 21 | # 设置窗口的大小和位置 22 | root.geometry(f'{window_width}x{window_height}+{center_x}+{center_y}') 23 | 24 | # 禁止窗口的宽度和高度调整(固定窗口大小) 25 | root.resizable(False, False) 26 | 27 | # 文件拖放 28 | def dragged_files(folder_path): 29 | input_entry.delete(0, tk.END) 30 | input_entry.insert(0, folder_path[0]) 31 | 32 | # 配置所有列的权重为1,使其可以扩展 33 | for col in range(3): 34 | root.grid_columnconfigure(col, weight=1) 35 | 36 | # 配置所有行的权重为1,使其可以扩展 37 | for row in range(4): # 现在有4行 38 | root.grid_rowconfigure(row, weight=1) 39 | 40 | # 定义打开文件的函数 41 | def browse_input(): 42 | input_folder_path = filedialog.askdirectory() 43 | input_entry.delete(0, tk.END) 44 | input_entry.insert(0, input_folder_path) 45 | 46 | # 定义浏览文件的函数 47 | def browse_output(): 48 | folder_path = filedialog.askdirectory() 49 | output_entry.delete(0, tk.END) 50 | output_entry.insert(0, folder_path) 51 | 52 | def on_select(event): 53 | # 获取选中的值 54 | print("选中:", config.get()) 55 | 56 | def load_config(): 57 | try: 58 | with open("config.json", "r", encoding="utf-8") as json_file: 59 | config_data = json.load(json_file) 60 | 61 | # 获取 WorkDir 和 DestDir,确保它们是字符串 62 | work_dir = str(config_data.get("WorkDir", "")) 63 | dest_dir = str(config_data.get("DestDir", "")) 64 | 65 | # 更新输入框的值 66 | input_entry.delete(0, tk.END) 67 | input_entry.insert(0, work_dir) 68 | output_entry.delete(0, tk.END) 69 | output_entry.insert(0, dest_dir) 70 | 71 | # 从 GerberCustom 获取下拉框的选项 72 | gerber_custom_options = list(config_data["GerberCustom"].keys()) 73 | config["values"] = gerber_custom_options 74 | 75 | # 设置默认选项 76 | default_gerber_type = config_data.get("GerberType", gerber_custom_options[0] if gerber_custom_options else "") 77 | config.set(default_gerber_type) 78 | except Exception as e: 79 | custom_print(f"读取配置文件时出错: {e}") 80 | 81 | def modify_files(work_dir, dest_dir, gerber_type, header, text_file_name, text_file_content, file_names, gerber_custom): 82 | work_dir_files = os.listdir(work_dir) 83 | 84 | if not dest_dir: 85 | dest_dir = os.path.join(work_dir, 'output') 86 | if not os.path.exists(dest_dir): 87 | os.mkdir(dest_dir) 88 | 89 | with open(os.path.join(dest_dir, text_file_name), "w") as text_file: 90 | text_file.write(text_file_content) 91 | 92 | selected_rule_set = gerber_custom[gerber_type] 93 | 94 | # 对匹配到的文件进行重命名和添加头部信息 95 | for key, value in selected_rule_set.items(): 96 | match_file = None 97 | re_pattern = re.compile(pattern=value) 98 | 99 | for file_name in work_dir_files: 100 | if re_pattern.search(file_name): 101 | match_file = file_name 102 | break 103 | 104 | if match_file: 105 | with open(os.path.join(work_dir, match_file), "r") as file: 106 | file_data = file.read() 107 | with open(os.path.join(dest_dir, file_names[key]), "w") as file: 108 | file.write(header) 109 | file.write(file_data) 110 | custom_print(key + " -> " + match_file) 111 | else: 112 | custom_print(key + " 文件未找到,跳过处理") 113 | 114 | def save_to_json_and_run_modify(): 115 | try: 116 | clear_output() 117 | # 读取现有的配置 118 | with open("config.json", "r", encoding="utf-8") as json_file: 119 | config_data = json.load(json_file) 120 | 121 | # 从 GUI 获取数据并更新配置 122 | config_data["WorkDir"] = input_entry.get() 123 | config_data["DestDir"] = output_entry.get() 124 | config_data["GerberType"] = config.get() 125 | 126 | # 将更新后的数据写回 config.json 127 | with open("config.json", "w", encoding="utf-8") as json_file: 128 | json.dump(config_data, json_file, indent=4, ensure_ascii=False) 129 | 130 | # 调用 modify_files 函数 131 | modify_files( 132 | config_data["WorkDir"], 133 | config_data["DestDir"], 134 | config_data["GerberType"], 135 | config_data["Header"], 136 | config_data["TextFileName"], 137 | config_data["TextFileContent"], 138 | config_data["FileName"], 139 | config_data["GerberCustom"] 140 | ) 141 | except Exception as e: 142 | custom_print(f"保存配置并运行修改时出错: {e}") 143 | 144 | # 创建自定义打印函数 145 | def custom_print(text): 146 | output_text.insert(tk.END, text + "\n") # 在文本框的末尾添加文本 147 | output_text.see(tk.END) # 自动滚动到最新的文本 148 | 149 | # 创建清空输出框内容的函数 150 | def clear_output(): 151 | output_text.delete("1.0", tk.END) 152 | 153 | def get_resource_path(relative_path): 154 | """ 获取资源的绝对路径。用于 PyInstaller 打包后资源的访问 """ 155 | try: 156 | # PyInstaller 创建的临时文件夹的路径 157 | base_path = sys._MEIPASS 158 | except Exception: 159 | # 正常执行时的路径 160 | base_path = os.path.abspath(".") 161 | 162 | return os.path.join(base_path, relative_path) 163 | 164 | def copy_config_to_current_dir(): 165 | config_filename = "config.json" 166 | temp_config_path = get_resource_path(config_filename) 167 | current_dir_config_path = os.path.join(os.getcwd(), config_filename) 168 | 169 | if not os.path.isfile(current_dir_config_path): 170 | # 如果当前目录下没有 config.json,则从临时目录复制 171 | shutil.copy(temp_config_path, current_dir_config_path) 172 | 173 | # 创建下拉框 174 | config_label = tk.Label(root, text="配置文件", anchor="e") 175 | config_label.grid(row=0, column=0, padx=5, pady=5, sticky="ew") 176 | config = ttk.Combobox(root, state="readonly") 177 | config.grid(row=0, column=1, padx=5, pady=5, columnspan=2, sticky="ew") 178 | 179 | # 绑定选择事件 180 | # config.bind("<>", on_select) 181 | 182 | # 创建输入框和其标签 183 | input_label = tk.Label(root, text="Gerber 文件夹", anchor="e") 184 | input_label.grid(row=1, column=0, padx=5, pady=5, sticky="ew") 185 | input_entry = tk.Entry(root, width=18) 186 | input_entry.grid(row=1, column=1, padx=5, pady=5) 187 | 188 | # 创建输出框和其标签 189 | output_label = tk.Label(root, text="输出文件夹", anchor="e") 190 | output_label.grid(row=2, column=0, padx=5, pady=5, sticky="ew") 191 | output_entry = tk.Entry(root, width=18) 192 | output_entry.grid(row=2, column=1, padx=5, pady=5) 193 | 194 | # 创建浏览输入文件夹按钮 195 | browse_input_button = tk.Button(root, text="浏览文件夹", command=browse_input) 196 | browse_input_button.grid(row=1, column=2, padx=5, pady=5) 197 | 198 | # 创建浏览输出文件夹按钮 199 | browse_output_button = tk.Button(root, text="浏览文件夹", command=browse_output) 200 | browse_output_button.grid(row=2, column=2, padx=5, pady=5) 201 | 202 | # 创建一个按钮,使其填满最后一行的所有单元格 203 | # 我们使用columnspan=3来跨越三列 204 | fill_button = tk.Button(root, text="Fuck JLC", command=save_to_json_and_run_modify) 205 | fill_button.grid(row=3, column=0, padx=5, pady=5, columnspan=3, sticky="nsew") 206 | 207 | # 创建输出框 208 | output_text = tk.Text(root, height=10) # 设置输出框的高度 209 | output_text.grid(row=4, column=0, columnspan=3, sticky="ew") 210 | 211 | copy_config_to_current_dir() 212 | 213 | load_config() 214 | 215 | windnd.hook_dropfiles(root, func=dragged_files) 216 | 217 | # 运行主循环 218 | root.mainloop() 219 | -------------------------------------------------------------------------------- /gui.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python ; coding: utf-8 -*- 2 | 3 | 4 | a = Analysis( 5 | ['gui.py'], 6 | pathex=[], 7 | binaries=[], 8 | datas=[('.\\config.json', '.')], 9 | hiddenimports=[], 10 | hookspath=[], 11 | hooksconfig={}, 12 | runtime_hooks=[], 13 | excludes=[], 14 | noarchive=False, 15 | ) 16 | pyz = PYZ(a.pure) 17 | 18 | exe = EXE( 19 | pyz, 20 | a.scripts, 21 | a.binaries, 22 | a.datas, 23 | [], 24 | name='FKJLC', 25 | debug=False, 26 | bootloader_ignore_signals=False, 27 | strip=False, 28 | upx=True, 29 | upx_exclude=[], 30 | runtime_tmpdir=None, 31 | console=False, 32 | disable_windowed_traceback=False, 33 | argv_emulation=False, 34 | target_arch=None, 35 | codesign_identity=None, 36 | entitlements_file=None, 37 | ) 38 | -------------------------------------------------------------------------------- /modify.py: -------------------------------------------------------------------------------- 1 | import json, os, re 2 | 3 | # 从 config.json 读取配置信息 4 | with open("config.json", "r", encoding="utf-8") as fconfig: 5 | config = json.load(fconfig) 6 | 7 | work_dir = config["WorkDir"] 8 | dest_dir = config["DestDir"] 9 | gerber_custom = config["GerberCustom"] # 使用 GerberCustom 作为规则 10 | gerber_type = config["GerberType"] # 使用 GerberType 作为文件类型 11 | work_dir_files = os.listdir(work_dir) 12 | 13 | if not dest_dir: 14 | dest_dir = os.path.join(work_dir, 'output') 15 | if not os.path.exists(dest_dir): 16 | os.mkdir(dest_dir) 17 | 18 | # 创建 PCB 下单必读文档 19 | with open(os.path.join(dest_dir, config["TextFileName"]), "w") as text_file: 20 | text_file.write(config["TextFileContent"]) 21 | 22 | # 选择一个特定的规则集,例如 'KiCad' 或 'AD' 23 | selected_rule_set = gerber_custom[gerber_type] 24 | 25 | # 对匹配到的文件进行重命名和添加头部信息 26 | for key, value in selected_rule_set.items(): 27 | match_file = None 28 | re_pattern = re.compile(pattern=value) 29 | 30 | for file_name in work_dir_files: 31 | if re_pattern.search(file_name): 32 | match_file = file_name 33 | break 34 | 35 | if match_file: 36 | # 找到文件,执行重命名和添加头部信息 37 | with open(os.path.join(work_dir, match_file), "r") as file: 38 | file_data = file.read() 39 | with open(os.path.join(dest_dir, config["FileName"][key]), "w") as file: 40 | file.write(config["Header"]) 41 | file.write(file_data) 42 | print(key + " -> " + match_file) 43 | else: 44 | # 文件未找到,继续处理下一个 45 | print(key + " 文件未找到,跳过处理") 46 | -------------------------------------------------------------------------------- /requitrments.txt: -------------------------------------------------------------------------------- 1 | pyyaml --------------------------------------------------------------------------------