# -*- coding: utf-8 -*- """功能区 24 图标(SVG 正式版):手写 SVG 设计稿 → Edge 无头渲染 256px 透明 PNG → 缩 64px。 设计原则(frontend-design 技能):视觉词汇取自电气成套行业(图纸/柜体/铜排/元件符号); 主描边 3px 圆角端点;每个图标 = 墨青灰线性主形 + 1 个实色焦点(绿/铜/红/蓝)。 调色:墨青灰#3D434A 强调绿#217346(=功能区AccentColor) 铜橙#C8791E 警示红#D64541 点缀蓝#2B579A 输出: bin\\Debug\\icons\\*.png(文件名不变,Designer.cs FromFile 路径不用改) + icons\\svg\\*.svg 设计稿。""" import os import io import subprocess import tempfile import shutil from PIL import Image ROOT = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件" PNG_DIR = os.path.join(ROOT, "成套报价软件", "bin", "Debug", "icons") SVG_DIR = os.path.join(ROOT, "成套报价软件", "icons", "svg") EDGE = r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" INK = "#3D434A" # 墨青灰 GRN = "#217346" # 强调绿 COP = "#C8791E" # 铜橙 RED = "#D64541" # 警示红 BLU = "#2B579A" # 点缀蓝 PGR = "#E5F0EA" # 淡绿底 LGR = "#9DBFAA" # 灰绿(弱线) def S(body, stroke=INK, w=3): """外层 svg: 256 输出 / 64 网格""" return ('\n' + body + "\n") def L(pts, c=INK, w=3): p = " ".join("%g %g" % (x, y) for x, y in pts) return '' % (p, c, w) def R(x, y, w2, h2, rx, c=INK, sw=3, fill="none"): f = ' fill="%s"' % fill s = ' stroke="%s" stroke-width="%g"' % (c, sw) if sw else "" return '' % (x, y, w2, h2, rx, f, s) def C(cx, cy, r, c=INK, sw=3, fill="none"): f = ' fill="%s"' % fill s = ' stroke="%s" stroke-width="%g"' % (c, sw) if sw else "" return '' % (cx, cy, r, f, s) def E(cx, cy, rx, ry, c=INK, sw=3, fill="none"): return '' % (cx, cy, rx, ry, fill, c, sw) def P(d, c=INK, w=3, fill="none"): s = ' stroke="%s" stroke-width="%g"' % (c, w) if w else "" f = ' fill="%s"' % fill return '' % (d, f, s) def T(x, y, txt, size, c="#FFFFFF", weight="bold"): return ('%s' % (x, y, size, weight, c, txt)) # ---- 图标定义(64 网格) ---- ICONS = {} # 1 新建项目:完整折角图纸 + 右下绿加号徽标(白色隔离圈防穿插) ICONS["new-project"] = S( P("M12 6 H34 L46 18 V36 H12 Z") + L([(18, 14), (28, 14)], INK, 2.25) + L([(18, 21), (34, 21)], INK, 2.25) + L([(18, 28), (31, 28)], INK, 2.25) + C(46, 46, 15, "#FFFFFF", 0, fill="#FFFFFF") + C(46, 46, 12.5, GRN, 0, fill=GRN) + L([(46, 40.5), (46, 51.5)], "#FFFFFF", 3) + L([(40.5, 46), (51.5, 46)], "#FFFFFF", 3)) # 2 修改项目:完整图纸 + 45° 铅笔(白色隔离层分离,规整几何) def _edit_body(): return ( P("M10 6 H32 L42 16 V32 H10 Z") + L([(16, 14), (26, 14)], INK, 2.25) + L([(16, 21), (32, 21)], INK, 2.25) + L([(16, 28), (30, 28)], INK, 2.25) + # 铅笔:白色 gap 层(同形放大) → 彩色层。rotate(45) 后笔尖朝左下。 '' + '' '' '' + '' + '' + '' + '') ICONS["edit-project"] = S(_edit_body()) # 3 删除项目:红色垃圾桶 ICONS["delete-project"] = S( P("M17 20 H47 L44.3 52 A4 4 0 0 1 40.3 56 H23.7 A4 4 0 0 1 19.7 52 Z", RED, 3) + L([(13, 20), (51, 20)], RED, 3.5) + L([(26, 20), (26, 15)], RED, 3) + L([(38, 20), (38, 15)], RED, 3) + L([(26.5, 28), (27.5, 48)], RED, 2.5) + L([(37.5, 28), (36.5, 48)], RED, 2.5)) # 4 打开项目:标准两层文件夹(后板墨青线,前板圆角底+淡绿填充,白色 gap 分离) def _open_body(): return ( P("M8 13 H23 L27.5 18.5 H51 A3.5 3.5 0 0 1 54.5 22 V27 H8 Z") + P("M6.5 31 H57.5 L52.9 46.2 A5.2 5.2 0 0 1 47.9 50.2 H11.1 A5.2 5.2 0 0 1 6.1 45.2 Z", "#FFFFFF", 8, fill="#FFFFFF") + P("M6.5 31 H57.5 L52.9 46.2 A5.2 5.2 0 0 1 47.9 50.2 H11.1 A5.2 5.2 0 0 1 6.1 45.2 Z", GRN, 3, fill=PGR)) ICONS["open-project"] = S(_open_body()) # 5 复制项目:双层图纸,后层绿 ICONS["copy-project"] = S( P("M22 8 H44 V44 H22 Z") + P("M14 16 V54 H36", GRN, 3) + L([(28, 16), (38, 16)], INK, 2.25) + L([(28, 24), (38, 24)], INK, 2.25) + L([(28, 32), (34, 32)], INK, 2.25)) # 6 导入项目:托盘 + 绿下箭头 ICONS["import-project"] = S( P("M10 34 V48 A6 6 0 0 0 16 54 H48 A6 6 0 0 0 54 48 V34", GRN, 3.5) + L([(32, 6), (32, 26)], GRN, 4) + P("M23 19 L32 28 L41 19", GRN, 4)) # 7 导出项目:托盘 + 蓝上箭头 ICONS["export-project"] = S( P("M10 34 V48 A6 6 0 0 0 16 54 H48 A6 6 0 0 0 54 48 V34", BLU, 3.5) + L([(32, 26), (32, 6)], BLU, 4) + P("M23 13 L32 4 L41 13", BLU, 4)) # 8 打开项目(报价页):同语义,与项目页"打开项目"同款图形 ICONS["open-quote"] = S(_open_body()) # 9 修改项目(报价页):同语义,与项目页"修改项目"同款图形 ICONS["edit-quote"] = S(_edit_body()) # 10 保存:绿色软盘 ICONS["save"] = S( P("M10 10 H44 L54 20 V54 H10 Z", GRN, 3.5) + R(22, 10, 18, 14, 1, GRN, 2.5) + L([(35, 12), (35, 20)], GRN, 2.5) + R(18, 36, 28, 18, 1, GRN, 2.5)) # 11 导入报价:报价单 + 绿下箭头 ICONS["import-quote"] = S( R(10, 8, 30, 44, 2) + L([(17, 18), (33, 18)], INK, 2.25) + L([(17, 25), (29, 25)], INK, 2.25) + T(24, 37, "¥", 12, LGR, "600") + L([(48, 30), (48, 53)], GRN, 4) + P("M40 47 L48 55 L56 47", GRN, 4)) # 12 甲方报表:表格 + 绿勾角标 ICONS["report"] = S( R(8, 10, 48, 44, 3) + L([(8, 22), (56, 22)], INK, 2.5) + L([(8, 33), (56, 33)], INK, 2.5) + L([(24, 22), (24, 54)], INK, 2.5) + L([(40, 22), (40, 54)], INK, 2.5) + C(47, 46, 11.5, fill=GRN) + P("M41.5 46 L45.5 50 L53 41.5", "#FFFFFF", 3.5)) # 13 调母排:三根铜排(深浅层次) + 调节箭头 ICONS["busbar"] = S( R(7, 12, 50, 9, 3, sw=0, fill=COP) + R(7, 28, 50, 9, 3, sw=0, fill="#D69449") + R(7, 44, 38, 9, 3, sw=0, fill="#E5B176") + P("M46 56 L52 50 L58 56", INK, 3)) # 14 调系数:双滑杆,绿+蓝滑钮 ICONS["factor"] = S( L([(10, 21), (54, 21)], INK, 3) + L([(10, 43), (54, 43)], INK, 3) + C(41, 21, 7.5, GRN, 0, fill=GRN) + C(23, 43, 7.5, BLU, 0, fill=BLU)) # 15 元件排序:三行元件符号(电阻矩形,电气符号) + 上箭头 ICONS["sort-components"] = S( L([(9, 15), (19, 15)], INK, 2.5) + R(19, 10.5, 13, 9, 1.5, INK, 2.5) + L([(32, 15), (40, 15)], INK, 2.5) + L([(9, 32), (19, 32)], INK, 2.5) + R(19, 27.5, 13, 9, 1.5, INK, 2.5) + L([(32, 32), (40, 32)], INK, 2.5) + L([(9, 49), (19, 49)], INK, 2.5) + R(19, 44.5, 13, 9, 1.5, INK, 2.5) + L([(32, 49), (40, 49)], INK, 2.5) + P("M50 24 L56 17 L62 24", GRN, 3) + P("M50 44 L56 51 L62 44", GRN, 3)) # 16 模板费用:模板网格 + ¥ 绿徽 ICONS["template-cost"] = S( R(7, 7, 50, 50, 4) + L([(7, 23), (57, 23)], INK, 2.5) + L([(7, 38), (36, 38)], INK, 2.5) + L([(28, 23), (28, 57)], INK, 2.5) + C(47, 46, 12.5, fill=GRN) + T(47, 46.5, "¥", 20)) # 17 CAD扒图:齿轮 + 绿放大镜 ICONS["cad-grab"] = S( C(25, 25, 9.5, INK, 3) + P("M25 7 V13 M25 37 V43 M7 25 H13 M37 25 H43 M12.4 12.4 L16.6 16.6 M33.4 33.4 L37.6 37.6 M37.6 12.4 L33.4 16.6 M16.6 33.4 L12.4 37.6", INK, 4) + C(45, 45, 9.5, GRN, 3.5) + L([(51.5, 51.5), (58, 58)], GRN, 4)) # 18 登录:门框 + 绿人形 ICONS["login"] = S( P("M38 8 H54 V56 H38", INK, 3.5) + C(21, 23, 8, GRN, 3) + P("M7 52 A14 14 0 0 1 35 52", GRN, 3) + C(47, 33, 1.8, INK, 0, fill=INK)) # 19 修改密码:绿挂锁 ICONS["password"] = S( R(14, 28, 36, 27, 4, GRN, 3.5) + P("M22 28 V19 A10 10 0 0 1 42 19 V28", GRN, 3.5) + C(32, 39, 4, GRN, 0, fill=GRN) + L([(32, 43), (32, 49)], GRN, 3)) # 20 会员信息:证件卡(人像+信息行) ICONS["member"] = S( R(7, 13, 50, 38, 4) + C(23, 29, 6, GRN, 2.75) + P("M13 45 A10 10 0 0 1 33 45", GRN, 2.75) + L([(40, 26), (51, 26)], INK, 2.75) + L([(40, 35), (51, 35)], INK, 2.75) + L([(16, 51), (48, 51)], LGR, 0)) # 占位(卡内底线省略) # 21 企业信息:办公楼群(主楼+绿副楼) ICONS["company"] = S( R(11, 9, 27, 47, 1) + L([(18, 19), (24, 19)], INK, 2.5) + L([(30, 19), (36, 19)], INK, 2.5) + L([(18, 28), (24, 28)], INK, 2.5) + L([(30, 28), (36, 28)], INK, 2.5) + L([(18, 37), (24, 37)], INK, 2.5) + L([(30, 37), (36, 37)], INK, 2.5) + R(43, 26, 13, 30, 1, GRN, 3) + L([(48, 34), (52, 34)], GRN, 2.25) + L([(48, 42), (52, 42)], GRN, 2.25) + R(21, 47, 8, 9, 1, INK, 2.5)) # 22 保存布局:停靠面板示意 + 绿勾 ICONS["save-layout"] = S( R(7, 7, 50, 50, 4) + L([(7, 24), (57, 24)], INK, 2.5) + L([(28, 24), (28, 57)], INK, 2.5) + R(11, 11, 13, 9, 1.5, sw=0, fill=LGR) + C(46, 44, 11.5, fill=GRN) + P("M40.5 44 L44.5 48 L52 39.5", "#FFFFFF", 3.5)) # 23 备份:数据库 + 绿右箭头 ICONS["backup"] = S( E(24, 15, 13, 5.5) + P("M11 15 V45 A13 5.5 0 0 0 37 45 V15") + P("M11 30 A13 5.5 0 0 0 37 30", INK, 2.5) + L([(40, 38), (55, 38)], GRN, 3.5) + P("M50 32.5 L56.5 38 L50 43.5", GRN, 3.5)) # 24 恢复:数据库 + 墨青回转箭头 ICONS["restore"] = S( E(40, 15, 13, 5.5, GRN) + P("M27 15 V45 A13 5.5 0 0 0 53 45 V15", GRN) + P("M27 30 A13 5.5 0 0 0 53 30", GRN, 2.5) + L([(24, 38), (9, 38)], INK, 3.5) + P("M14 32.5 L7.5 38 L14 43.5", INK, 3.5)) def render(name, body): """单个 SVG → Edge 截图 → 64px PNG""" sp = os.path.join(SVG_DIR, name + ".svg") with io.open(sp, "w", encoding="utf-8") as f: f.write(body) tmp = tempfile.mkdtemp(prefix="ribbon_icon_") try: tmp_svg = os.path.join(tmp, "i.svg") tmp_png = os.path.join(tmp, "i.png") shutil.copy(sp, tmp_svg) w = subprocess.run( [EDGE, "--headless", "--disable-gpu", "--no-sandbox", "--user-data-dir=" + os.path.join(tmp, "profile"), "--screenshot=" + tmp_png, "--window-size=256,256", "--default-background-color=00000000", "file:///" + tmp_svg.replace("\\", "/")], capture_output=True, timeout=40) if not os.path.exists(tmp_png): raise RuntimeError("Edge 未产出 PNG: " + w.stderr.decode("utf-8", "ignore")[-150:]) img = Image.open(tmp_png).convert("RGBA").resize((64, 64), Image.LANCZOS) img.save(os.path.join(PNG_DIR, name + ".png")) finally: shutil.rmtree(tmp, ignore_errors=True) def main(): os.makedirs(SVG_DIR, exist_ok=True) os.makedirs(PNG_DIR, exist_ok=True) ok, fail = 0, [] for name, body in ICONS.items(): try: render(name, body) ok += 1 except Exception as ex: fail.append((name, str(ex)[:160])) print("SVG→PNG: %d 成功 / %d 失败" % (ok, len(fail))) for n, e in fail: print(" [失败]", n, e) if __name__ == "__main__": main()