修复了网络参数的保存逻辑
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""生成 docs/项目架构总览.svg —— 分层架构图(纯标准库,无需第三方依赖)。
|
||||
|
||||
为什么不用 Mermaid:自动布局不可控,多节点/跨层依赖会画成交叉的"意大利面"。
|
||||
本脚本手工排布:每层一个圆角框(标题在框内顶部 + 模块芯片自动换行),
|
||||
同一"行"可放多个并列层(如 应用层 与 UI 层 同级并排),行间单向下箭头。
|
||||
|
||||
用法(仓库根目录,任意 python3):
|
||||
python tools/arch_diagram/gen_arch_svg.py
|
||||
输出:
|
||||
docs/项目架构总览.svg
|
||||
改架构时:编辑本文件顶部的 LAYERS / ROWS 数据后重跑即可。
|
||||
"""
|
||||
import html
|
||||
import os
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
OUT = os.path.join(ROOT, "docs", "项目架构总览.svg")
|
||||
|
||||
# ----------------------------- 画布与尺寸参数 -----------------------------
|
||||
W = 1560
|
||||
PAD = 26
|
||||
TITLE_FONT = 20
|
||||
SUB_FONT = 13
|
||||
BAND_TITLE_FONT = 17
|
||||
BAND_SUB_FONT = 12
|
||||
CHIP_FONT = 13
|
||||
|
||||
CHIP_H = 38 # 芯片盒高
|
||||
CHIP_GAP = 10 # 芯片水平间距
|
||||
CHIP_ROW_GAP = 10 # 芯片行间距
|
||||
CHIP_PAD = 16 # 芯片内左右留白
|
||||
BAND_PAD_X = 18 # 框内左右留白
|
||||
BAND_TITLE_H = 38 # 框内标题区高
|
||||
BAND_PAD_BOTTOM = 16
|
||||
BAND_H_GAP = 26 # 同一行内并列框的间距
|
||||
BAND_V_GAP = 44 # 行间距(放箭头)
|
||||
TITLE_H = 78 # 顶部标题区高
|
||||
|
||||
FONT = "Microsoft YaHei, Segoe UI, PingFang SC, sans-serif"
|
||||
|
||||
# ----------------------------- 架构数据(改这里) -----------------------------
|
||||
L_BIZ = dict(
|
||||
title="① 业务层", sub="顶层共享配置 · 被 UI 与基础服务共用", color="#2563eb", bg="#eff6ff",
|
||||
chips=["net_cfg(网口配置:IP/掩码/网关/MAC + Flash 持久化)"],
|
||||
)
|
||||
L_UI = dict(
|
||||
title="② UI 层", sub="Drivers/BSP/UI · 与 PC 模拟器共用", color="#7c3aed", bg="#f5f3ff",
|
||||
chips=["ui_port(显示端口)", "ui_menu / ui_menu_view(级联菜单)", "ui_menu_tab(菜单表)",
|
||||
"ui_content_page(内容页框架)", "ui_page_*(5 页插件)",
|
||||
"ui_net_sink(远程显示 :7003)", "ui_font_cn_12(中文字体)", "ui_deco(浅 V 装饰)"],
|
||||
)
|
||||
L_SVC = dict(
|
||||
title="③ 基础服务层", sub="应用侧服务 / 编排", color="#0d9488", bg="#f0fdfa",
|
||||
chips=["app_main(启动编排)", "sys_clock(墙钟基准)", "sys_events(就绪屏障)",
|
||||
"app_cfg(编译开关)", "task/(net · ui · adc* · rs485*)",
|
||||
"lftpd(FTP :21)", "time_sync(TCP 对时 :8888)"],
|
||||
)
|
||||
L_MW = dict(
|
||||
title="④ 中间件层", sub="通用库 / 协议栈", color="#16a34a", bg="#f0fdf4",
|
||||
chips=["net_socket / net_select(BSD API)",
|
||||
"FatFs(文件系统)", "nand_ftl(dhara FTL)", "LVGL v9.2(图形库)",
|
||||
"util(crc · ringbuf)"],
|
||||
)
|
||||
L_DRV = dict(
|
||||
title="⑤ 驱动层", sub="Drivers/BSP · 芯片时序/中断", color="#d97706", bg="#fffbeb",
|
||||
chips=["ch395f(SPI2 + DMA)", "gd5f2gq5ue(SPI1)", "tpafe5160(并行 16 位)",
|
||||
"sd2506(I2C1)", "rs485(UART5 + DE)"],
|
||||
)
|
||||
L_KERN = dict(
|
||||
title="⑥ 内核 / HAL", sub="调度 · 外设抽象", color="#475569", bg="#f1f5f9",
|
||||
chips=["FreeRTOS V10.3.1(CMSIS-RTOS2 · heap_4)", "STM32 HAL(时基 TIM7)",
|
||||
"CMSIS / 启动文件"],
|
||||
)
|
||||
L_HW = dict(
|
||||
title="⑦ 硬件", sub="STM32F407ZGTx @168MHz", color="#e11d48", bg="#fff1f2",
|
||||
chips=["CH395F 以太网(SPI2)", "GD5F2GQ5UE NAND(SPI1)", "TPAFE5160 ADC(并行)",
|
||||
"SD2506 RTC(I2C1)", "RS-485(UART5)", "USART1 调试(115200)", "6x LED"],
|
||||
)
|
||||
|
||||
# 每一行可放多个"同级并列"的层(上层只依赖下方各层)
|
||||
ROWS = [
|
||||
[L_BIZ], # 顶层:业务层(仅共享配置 net_cfg)
|
||||
[L_UI, L_SVC], # UI 层与基础服务层同级并排,均使用 net_cfg
|
||||
[L_MW], # 中间件层(通用库 / 协议栈)
|
||||
[L_DRV],
|
||||
[L_KERN],
|
||||
[L_HW],
|
||||
]
|
||||
|
||||
FOOT = "上层只依赖相邻下层(单向);UI 层与基础服务层同级并列,均使用顶层 net_cfg。跨层调用细节见 docs/项目架构总览.md §1.2 * 预留未接入调度"
|
||||
|
||||
|
||||
# ----------------------------- 工具函数 -----------------------------
|
||||
def esc(s):
|
||||
return html.escape(s, quote=True)
|
||||
|
||||
|
||||
def text_w(s, fs):
|
||||
"""粗略估算文本像素宽:CJK/全角/圈码/箭头 ≈ 1.0em,其余 ≈ 0.58em。
|
||||
|
||||
取偏大估计(宁可芯片略宽,不可文字溢出盒外)。
|
||||
"""
|
||||
w = 0.0
|
||||
for ch in s:
|
||||
w += fs * (1.0 if ord(ch) >= 0x2460 else 0.58)
|
||||
return w
|
||||
|
||||
|
||||
def chip_w(label):
|
||||
return text_w(label, CHIP_FONT) + 2 * CHIP_PAD
|
||||
|
||||
|
||||
def wrap_chips(chips, avail):
|
||||
rows, cur, cur_w = [], [], 0.0
|
||||
for c in chips:
|
||||
cw = chip_w(c)
|
||||
need = cw if not cur else cur_w + CHIP_GAP + cw
|
||||
if cur and need > avail:
|
||||
rows.append(cur)
|
||||
cur, cur_w = [c], cw
|
||||
else:
|
||||
cur.append(c)
|
||||
cur_w = need
|
||||
if cur:
|
||||
rows.append(cur)
|
||||
return rows
|
||||
|
||||
|
||||
# ----------------------------- 生成 -----------------------------
|
||||
def build():
|
||||
out = []
|
||||
add = out.append
|
||||
|
||||
# 布局:逐行排布(行内可并列多个框,行高取该行最高框)
|
||||
placements = [] # (layer, chip_rows, x, y, w, h)
|
||||
y = TITLE_H
|
||||
for row in ROWS:
|
||||
n = len(row)
|
||||
bw = (W - 2 * PAD - BAND_H_GAP * (n - 1)) // n
|
||||
bands = []
|
||||
for i, layer in enumerate(row):
|
||||
bx = PAD + i * (bw + BAND_H_GAP)
|
||||
avail = bw - 2 * BAND_PAD_X
|
||||
rows = wrap_chips(layer["chips"], avail)
|
||||
bh = BAND_TITLE_H + len(rows) * CHIP_H + (len(rows) - 1) * CHIP_ROW_GAP + BAND_PAD_BOTTOM
|
||||
bands.append([layer, rows, bx, y, bw, bh])
|
||||
row_h = max(b[5] for b in bands)
|
||||
for b in bands:
|
||||
b[5] = row_h # 同行等高(视觉"同级")
|
||||
placements.extend(bands)
|
||||
y += row_h + BAND_V_GAP
|
||||
total_h = y - BAND_V_GAP + 54
|
||||
|
||||
add('<rect x="0" y="0" width="%d" height="%d" fill="#ffffff"/>' % (W, total_h))
|
||||
add('<text x="%d" y="%d" font-size="%d" font-weight="700" fill="#0f172a">%s</text>'
|
||||
% (PAD, 40, TITLE_FONT, esc("STM32F4-Base 项目分层架构")))
|
||||
add('<text x="%d" y="%d" font-size="%d" fill="#64748b">%s</text>'
|
||||
% (PAD, 62, SUB_FONT,
|
||||
esc("STM32F407ZGTx @168MHz · FreeRTOS V10.3.1 (CMSIS-RTOS2) · 上层只依赖相邻下层")))
|
||||
|
||||
for (layer, rows, bx, by, bw, bh) in placements:
|
||||
# 框背景 + 边框
|
||||
add('<rect x="%d" y="%d" width="%d" height="%d" rx="12" fill="%s" stroke="%s" '
|
||||
'stroke-opacity="0.45"/>' % (bx, by, bw, bh, layer["bg"], layer["color"]))
|
||||
# 标题(层名 + 目录/职责说明)
|
||||
add('<text x="%d" y="%d" font-size="%d" font-weight="700" fill="%s">%s</text>'
|
||||
% (bx + BAND_PAD_X, by + 26, BAND_TITLE_FONT, layer["color"], esc(layer["title"])))
|
||||
tw = text_w(layer["title"], BAND_TITLE_FONT)
|
||||
add('<text x="%.0f" y="%d" font-size="%s" fill="#64748b">%s</text>'
|
||||
% (bx + BAND_PAD_X + tw + 12, by + 26, BAND_SUB_FONT, esc(layer["sub"])))
|
||||
# 芯片
|
||||
cy = by + BAND_TITLE_H
|
||||
for row in rows:
|
||||
cx = bx + BAND_PAD_X
|
||||
for c in row:
|
||||
cw = chip_w(c)
|
||||
add('<rect x="%d" y="%d" width="%d" height="%d" rx="8" fill="#ffffff" '
|
||||
'stroke="%s" stroke-opacity="0.55"/>' % (cx, cy, cw, CHIP_H, layer["color"]))
|
||||
add('<text x="%d" y="%d" font-size="%d" fill="#1e293b" text-anchor="middle">%s</text>'
|
||||
% (cx + cw / 2, cy + CHIP_H / 2 + CHIP_FONT * 0.35, CHIP_FONT, esc(c)))
|
||||
cx += cw + CHIP_GAP
|
||||
cy += CHIP_H + CHIP_ROW_GAP
|
||||
|
||||
# 行间箭头:每个上层框向下引一条(顶层两框 → 两条,体现"都依赖下层")
|
||||
row_first = [] # 每行首个 placement 的下标
|
||||
idx = 0
|
||||
for row in ROWS:
|
||||
row_first.append(idx)
|
||||
idx += len(row)
|
||||
for r in range(len(ROWS) - 1):
|
||||
y0 = placements[row_first[r]][3] + placements[row_first[r]][5] + 6
|
||||
y1 = placements[row_first[r + 1]][3] - 8
|
||||
for i in range(row_first[r], row_first[r] + len(ROWS[r])):
|
||||
(_, _, bx, _, bw, _) = placements[i]
|
||||
ax = bx + bw / 2
|
||||
add('<line x1="%.1f" y1="%.1f" x2="%.1f" y2="%.1f" stroke="#94a3b8" '
|
||||
'stroke-width="2.5"/>' % (ax, y0, ax, y1 - 7))
|
||||
add('<path d="M %.1f %.1f l -6 -9 h 12 z" fill="#94a3b8"/>' % (ax, y1))
|
||||
|
||||
add('<text x="%d" y="%d" font-size="12" fill="#94a3b8">%s</text>'
|
||||
% (PAD, total_h - 18, esc(FOOT)))
|
||||
|
||||
svg = ('<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d" '
|
||||
'viewBox="0 0 %d %d" font-family="%s">\n%s\n</svg>\n'
|
||||
% (W, total_h, W, total_h, FONT, "\n".join(out)))
|
||||
return svg, total_h
|
||||
|
||||
|
||||
def main():
|
||||
svg, h = build()
|
||||
with open(OUT, "w", encoding="utf-8", newline="\n") as f:
|
||||
f.write(svg)
|
||||
import xml.etree.ElementTree as ET
|
||||
ET.fromstring(svg)
|
||||
print("OK %d bytes, canvas %dx%d -> %s" % (len(svg.encode("utf-8")), W, h, OUT))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,246 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""生成 docs/就绪屏障.svg —— 启动就绪屏障(Event Flags)依赖图(纯标准库)。
|
||||
|
||||
为什么不用 Mermaid:原图 9 条边全挤在单一中心节点上,画成交叉的"意大利面"。
|
||||
本脚本手工排布为三栏:
|
||||
左 = 生产者(set) 中 = 事件组 sys_evtHandle 右 = 消费者(wait)
|
||||
任务卡与事件位各自对齐到同一水平行,箭头基本水平/正交;bit1 → ftpTask 的
|
||||
"与"依赖用汇合点(All)表达,避免跨行连线。
|
||||
|
||||
用法(仓库根目录,任意 python3):
|
||||
python tools/arch_diagram/gen_barrier_svg.py
|
||||
输出:
|
||||
docs/就绪屏障.svg
|
||||
改依赖时:编辑本文件顶部的 BITS / PRODUCERS / CONSUMERS 数据后重跑。
|
||||
"""
|
||||
import html
|
||||
import math
|
||||
import os
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
OUT = os.path.join(ROOT, "docs", "就绪屏障.svg")
|
||||
|
||||
# ----------------------------- 画布与尺寸 -----------------------------
|
||||
W = 1400
|
||||
PAD = 40
|
||||
TITLE_FONT = 20
|
||||
SUB_FONT = 13
|
||||
CARD_TITLE_FONT = 15
|
||||
CARD_NOTE_FONT = 12
|
||||
BIT_TITLE_FONT = 14
|
||||
BIT_NOTE_FONT = 12
|
||||
LABEL_FONT = 11.5
|
||||
FOOT_FONT = 12
|
||||
|
||||
H_CARD = 84 # 任务卡 / 事件位行高
|
||||
GAP = 26 # 行间距
|
||||
MAIN_Y = 200 # 首行 y
|
||||
CARD_W_L = 290 # 生产者卡宽
|
||||
CARD_W_R = 450 # 消费者卡宽
|
||||
PANEL_X, PANEL_W = 400, 420
|
||||
PROD_X = 40
|
||||
CONS_X = 910
|
||||
TAP_X = 832 # bit1 → All 汇合点的下引线 x
|
||||
BUS_X = 862 # bit0 分发总线 x
|
||||
ARROW_W = 2.2
|
||||
|
||||
FONT = "Microsoft YaHei, Segoe UI, PingFang SC, sans-serif"
|
||||
|
||||
C_PROD, BG_PROD = "#2563eb", "#eff6ff"
|
||||
C_CONS, BG_CONS = "#7c3aed", "#f5f3ff"
|
||||
|
||||
# ----------------------------- 数据(改这里) -----------------------------
|
||||
# 事件位(中间栏),自上而下 = 时序先后
|
||||
BITS = [
|
||||
dict(bit="bit1", flag="STORAGE_READY", color="#16a34a", bg="#f0fdf4",
|
||||
note="defaultTask 置位 · netTask / ftpTask 等待"),
|
||||
dict(bit="bit0", flag="NET_READY", color="#0d9488", bg="#f0fdfa",
|
||||
note="netTask 置位 · 4 个任务等待"),
|
||||
dict(bit="bit2", flag="FTP_READY", color="#d97706", bg="#fffbeb",
|
||||
note="ftpTask 置位 · 预留消费者"),
|
||||
dict(bit="bit3", flag="TIMESYNC_READY", color="#94a3b8", bg="#f8fafc",
|
||||
note="预留(尚未接入调度)"),
|
||||
]
|
||||
|
||||
# 生产者(左栏):row = 其置位的 bit 行
|
||||
PRODUCERS = [
|
||||
dict(name="defaultTask", note="FatFS 挂载(f_mount / f_mkfs)", row=0, label="set bit1"),
|
||||
dict(name="netTask", note="net_stack_bring_up()", row=1, label="set bit0"),
|
||||
dict(name="ftpTask", note='lftpd_start("/", 21)', row=2, label="set bit2"),
|
||||
]
|
||||
|
||||
# 消费者(右栏):row = 卡片排布行(前两行与 bit1 / bit0 对齐)
|
||||
CONSUMERS = [
|
||||
dict(name="netTask", note="wait bit1 · ≤10s", row=0),
|
||||
dict(name="ftpTask", note="wait bit0 + bit1 · All", row=1),
|
||||
dict(name="uiTask", note="wait bit0 · Forever", row=2),
|
||||
dict(name="timeSyncTask", note="wait bit0 · Forever", row=3),
|
||||
dict(name="testTask", note="wait bit0 · NET 模式", row=4),
|
||||
]
|
||||
|
||||
FOOT = ("生产者与消费者互不知晓,只经事件组解耦;新增子系统 = 新 bit + 各自 wait/set,"
|
||||
"不改已有任务。详见 docs/项目启动顺序管理.md")
|
||||
|
||||
|
||||
# ----------------------------- 工具函数 -----------------------------
|
||||
def esc(s):
|
||||
return html.escape(s, quote=True)
|
||||
|
||||
|
||||
def text_w(s, fs):
|
||||
"""粗略估算文本像素宽:CJK/全角 ≈ 1.0em,其余 ≈ 0.58em(宁可偏大)。"""
|
||||
w = 0.0
|
||||
for ch in s:
|
||||
w += fs * (1.0 if ord(ch) >= 0x2E80 else 0.58)
|
||||
return w
|
||||
|
||||
|
||||
# ----------------------------- 生成 -----------------------------
|
||||
def build():
|
||||
out = []
|
||||
add = out.append
|
||||
|
||||
def row_y(r):
|
||||
return MAIN_Y + r * (H_CARD + GAP)
|
||||
|
||||
def row_cy(r):
|
||||
return row_y(r) + H_CARD / 2.0
|
||||
|
||||
def poly(points, color, dash=False, head=True, width=ARROW_W):
|
||||
pts = " ".join("%.1f,%.1f" % p for p in points)
|
||||
d = ' stroke-dasharray="7 5"' if dash else ''
|
||||
add('<polyline points="%s" fill="none" stroke="%s" stroke-width="%.1f" '
|
||||
'stroke-linejoin="round"%s/>' % (pts, color, width, d))
|
||||
if head:
|
||||
(x1, y1), (x2, y2) = points[-2], points[-1]
|
||||
ang = math.atan2(y2 - y1, x2 - x1)
|
||||
hl, hw = 11.0, 5.5
|
||||
bx, by = x2 - hl * math.cos(ang), y2 - hl * math.sin(ang)
|
||||
px, py = -math.sin(ang) * hw, math.cos(ang) * hw
|
||||
add('<path d="M %.1f %.1f L %.1f %.1f L %.1f %.1f z" fill="%s"/>'
|
||||
% (x2, y2, bx + px, by + py, bx - px, by - py, color))
|
||||
|
||||
def card(x, y, w, title, note, color, bg):
|
||||
add('<rect x="%d" y="%d" width="%d" height="%d" rx="10" fill="%s" '
|
||||
'stroke="%s" stroke-opacity="0.5"/>' % (x, y, w, H_CARD, bg, color))
|
||||
add('<text x="%d" y="%d" font-size="%d" font-weight="700" fill="#0f172a">%s</text>'
|
||||
% (x + 16, y + 34, CARD_TITLE_FONT, esc(title)))
|
||||
add('<text x="%d" y="%d" font-size="%d" fill="#64748b">%s</text>'
|
||||
% (x + 16, y + 58, CARD_NOTE_FONT, esc(note)))
|
||||
|
||||
# 行/画布尺寸
|
||||
n_rows = max(len(CONSUMERS), len(BITS))
|
||||
main_bottom = row_y(n_rows - 1) + H_CARD
|
||||
total_h = main_bottom + 78
|
||||
|
||||
add('<rect x="0" y="0" width="%d" height="%d" fill="#ffffff"/>' % (W, total_h))
|
||||
|
||||
# ---- 标题 ----
|
||||
add('<text x="%d" y="42" font-size="%d" font-weight="700" fill="#0f172a">%s</text>'
|
||||
% (PAD, TITLE_FONT, esc("就绪屏障(Readiness Barrier)· 启动依赖链")))
|
||||
add('<text x="%d" y="68" font-size="%d" fill="#64748b">%s</text>'
|
||||
% (PAD, SUB_FONT,
|
||||
esc("CMSIS-RTOS2 Event Flags · 生产者置位声明「我好了」,消费者阻塞等待")))
|
||||
|
||||
# ---- 图例 ----
|
||||
lx, ly = PAD, 108
|
||||
for kind, color, label in [
|
||||
("rect", C_PROD, "生产者(set)"),
|
||||
("rect", C_CONS, "消费者(wait)"),
|
||||
("rect", "#16a34a", "事件位(Event Flag)"),
|
||||
("dot", "#0d9488", "All 汇合(等全部位)"),
|
||||
("dash", "#94a3b8", "预留 / 未接入"),
|
||||
]:
|
||||
if kind == "rect":
|
||||
add('<rect x="%d" y="%d" width="16" height="16" rx="4" fill="#ffffff" '
|
||||
'stroke="%s" stroke-width="2"/>' % (lx, ly - 12, color))
|
||||
elif kind == "dot":
|
||||
add('<circle cx="%d" cy="%d" r="6" fill="%s"/>' % (lx + 8, ly - 4, color))
|
||||
else:
|
||||
add('<line x1="%d" y1="%d" x2="%d" y2="%d" stroke="%s" stroke-width="2.4" '
|
||||
'stroke-dasharray="7 5"/>' % (lx, ly - 4, lx + 16, ly - 4, color))
|
||||
lx += 24
|
||||
add('<text x="%d" y="%d" font-size="12" fill="#475569">%s</text>' % (lx, ly, esc(label)))
|
||||
lx += text_w(label, 12) + 34
|
||||
|
||||
# ---- 事件组面板 ----
|
||||
py0 = MAIN_Y - 34
|
||||
py1 = row_y(len(BITS) - 1) + H_CARD + 14
|
||||
add('<rect x="%d" y="%d" width="%d" height="%d" rx="14" fill="#f8fafc" '
|
||||
'stroke="#64748b" stroke-opacity="0.35"/>' % (PANEL_X, py0, PANEL_W, py1 - py0))
|
||||
head = "sys_evtHandle(Event Flags 事件组)"
|
||||
add('<text x="%d" y="%d" font-size="14" font-weight="700" fill="#334155">%s</text>'
|
||||
% (PANEL_X + 16, py0 + 26, esc(head)))
|
||||
for i, b in enumerate(BITS):
|
||||
by = row_y(i)
|
||||
add('<rect x="%d" y="%d" width="%d" height="%d" rx="10" fill="%s" stroke="%s" '
|
||||
'stroke-opacity="0.6"/>' % (PANEL_X + 14, by, PANEL_W - 28, H_CARD, b["bg"], b["color"]))
|
||||
add('<rect x="%d" y="%d" width="30" height="30" rx="7" fill="%s"/>'
|
||||
% (PANEL_X + 28, by + (H_CARD - 30) / 2, b["color"]))
|
||||
add('<text x="%d" y="%d" font-size="14" font-weight="700" fill="#ffffff" '
|
||||
'text-anchor="middle">%s</text>'
|
||||
% (PANEL_X + 43, by + H_CARD / 2 + 5, esc(b["bit"][3])))
|
||||
tx = PANEL_X + 70
|
||||
add('<text x="%d" y="%d" font-size="%d" font-weight="700" fill="%s">%s</text>'
|
||||
% (tx, by + 36, BIT_TITLE_FONT, b["color"], esc(b["bit"] + " · " + b["flag"])))
|
||||
add('<text x="%d" y="%d" font-size="%d" fill="#64748b">%s</text>'
|
||||
% (tx, by + 58, BIT_NOTE_FONT, esc(b["note"])))
|
||||
|
||||
# ---- 生产者卡 + set 箭头 ----
|
||||
for p in PRODUCERS:
|
||||
y, cy = row_y(p["row"]), row_cy(p["row"])
|
||||
card(PROD_X, y, CARD_W_L, p["name"], p["note"], C_PROD, BG_PROD)
|
||||
poly([(PROD_X + CARD_W_L, cy), (PANEL_X, cy)], BITS[p["row"]]["color"])
|
||||
add('<text x="%.0f" y="%.1f" font-size="%s" fill="%s" text-anchor="middle">%s</text>'
|
||||
% ((PROD_X + CARD_W_L + PANEL_X) / 2.0, cy - 8, LABEL_FONT,
|
||||
BITS[p["row"]]["color"], esc(p["label"])))
|
||||
|
||||
# ---- 消费者卡 ----
|
||||
for c in CONSUMERS:
|
||||
card(CONS_X, row_y(c["row"]), CARD_W_R, c["name"], c["note"], C_CONS, BG_CONS)
|
||||
|
||||
# ---- wait 连线:bit1 → netTask(水平) ----
|
||||
c0, c1 = row_cy(0), row_cy(1)
|
||||
col1, col0 = BITS[0]["color"], BITS[1]["color"]
|
||||
poly([(PANEL_X + PANEL_W, c0), (CONS_X, c0)], col1)
|
||||
add('<text x="%.0f" y="%.1f" font-size="%s" fill="%s" text-anchor="middle">%s</text>'
|
||||
% ((PANEL_X + PANEL_W + CONS_X) / 2.0, c0 - 8, LABEL_FONT, col1, esc("wait")))
|
||||
|
||||
# ---- wait 连线:bit1 → All 汇合点(下引,与 bit0 相与) ----
|
||||
poly([(TAP_X, c0), (TAP_X, c1)], col1, head=False)
|
||||
add('<circle cx="%d" cy="%.1f" r="5.5" fill="%s"/>' % (TAP_X, c1, col0))
|
||||
add('<text x="%d" y="%.1f" font-size="%s" font-weight="700" fill="%s">All</text>'
|
||||
% (TAP_X + 9, c1 - 8, LABEL_FONT, col0))
|
||||
|
||||
# ---- wait 连线:bit0 → 总线 → 4 个消费者 ----
|
||||
c_last = row_cy(len(CONSUMERS) - 1)
|
||||
poly([(PANEL_X + PANEL_W, c1), (BUS_X, c1)], col0)
|
||||
poly([(BUS_X, c1), (BUS_X, c_last)], col0, head=False)
|
||||
for c in CONSUMERS[1:]:
|
||||
poly([(BUS_X, row_cy(c["row"])), (CONS_X, row_cy(c["row"]))], col0)
|
||||
mid_y = (c1 + c_last) / 2.0
|
||||
add('<text transform="rotate(-90 %.1f %.1f)" x="%.1f" y="%.1f" font-size="%s" '
|
||||
'fill="%s" text-anchor="middle">%s</text>'
|
||||
% (BUS_X - 12, mid_y, BUS_X - 12, mid_y, LABEL_FONT, col0, esc("wait bit0")))
|
||||
|
||||
add('<text x="%d" y="%d" font-size="%d" fill="#94a3b8">%s</text>'
|
||||
% (PAD, total_h - 18, FOOT_FONT, esc(FOOT)))
|
||||
|
||||
svg = ('<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d" '
|
||||
'viewBox="0 0 %d %d" font-family="%s">\n%s\n</svg>\n'
|
||||
% (W, total_h, W, total_h, FONT, "\n".join(out)))
|
||||
return svg, total_h
|
||||
|
||||
|
||||
def main():
|
||||
svg, h = build()
|
||||
with open(OUT, "w", encoding="utf-8", newline="\n") as f:
|
||||
f.write(svg)
|
||||
import xml.etree.ElementTree as ET
|
||||
ET.fromstring(svg)
|
||||
print("OK %d bytes, canvas %dx%d -> %s" % (len(svg.encode("utf-8")), W, h, OUT))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user