# -*- 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 ('")
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()