chore: finalize remaining project artifacts
This commit is contained in:
184
项目分析/generate_linuxcnc_data_system_flowchart_svg.py
Normal file
184
项目分析/generate_linuxcnc_data_system_flowchart_svg.py
Normal file
@@ -0,0 +1,184 @@
|
||||
#!/usr/bin/env python3
|
||||
from html import escape
|
||||
|
||||
W = 5200
|
||||
H = 6600
|
||||
FONT = "Noto Sans CJK SC, DejaVu Sans, sans-serif"
|
||||
|
||||
|
||||
def units(s):
|
||||
return sum(2 if ord(ch) > 127 else 1 for ch in s)
|
||||
|
||||
|
||||
def wrap(text, max_units):
|
||||
result = []
|
||||
line = ""
|
||||
for token in text.split(" "):
|
||||
parts = [token]
|
||||
if units(token) > max_units:
|
||||
parts = []
|
||||
cur = ""
|
||||
for ch in token:
|
||||
if units(cur + ch) > max_units and cur:
|
||||
parts.append(cur)
|
||||
cur = ch
|
||||
else:
|
||||
cur += ch
|
||||
if cur:
|
||||
parts.append(cur)
|
||||
for part in parts:
|
||||
cand = part if not line else line + " " + part
|
||||
if units(cand) <= max_units:
|
||||
line = cand
|
||||
else:
|
||||
if line:
|
||||
result.append(line)
|
||||
line = part
|
||||
if line:
|
||||
result.append(line)
|
||||
return result
|
||||
|
||||
|
||||
class SVG:
|
||||
def __init__(self):
|
||||
self.out = []
|
||||
|
||||
def add(self, s):
|
||||
self.out.append(s)
|
||||
|
||||
def section(self, x, y, w, h, title, fill, stroke="#bdcbd8"):
|
||||
self.add(f'<rect x="{x}" y="{y}" width="{w}" height="{h}" rx="36" fill="{fill}" stroke="{stroke}" stroke-width="4"/>')
|
||||
self.add(f'<text x="{x+34}" y="{y+66}" class="section">{escape(title)}</text>')
|
||||
|
||||
def box(self, x, y, w, h, title, body, stroke, fill="#ffffff"):
|
||||
self.add(f'<rect x="{x}" y="{y}" width="{w}" height="{h}" rx="18" fill="{fill}" stroke="{stroke}" stroke-width="4"/>')
|
||||
self.add(f'<text x="{x+w/2}" y="{y+44}" class="boxtitle" text-anchor="middle">{escape(title)}</text>')
|
||||
ty = y + 84
|
||||
for line in wrap(body, max(12, int((w - 56) / 18))):
|
||||
self.add(f'<text x="{x+28}" y="{ty}" class="boxbody">{escape(line)}</text>')
|
||||
ty += 31
|
||||
|
||||
def small(self, x, y, w, h, title, body, stroke, fill="#ffffff"):
|
||||
self.add(f'<rect x="{x}" y="{y}" width="{w}" height="{h}" rx="14" fill="{fill}" stroke="{stroke}" stroke-width="3"/>')
|
||||
self.add(f'<text x="{x+w/2}" y="{y+36}" class="smalltitle" text-anchor="middle">{escape(title)}</text>')
|
||||
ty = y + 68
|
||||
for line in wrap(body, max(10, int((w - 42) / 16))):
|
||||
self.add(f'<text x="{x+22}" y="{ty}" class="smallbody">{escape(line)}</text>')
|
||||
ty += 27
|
||||
|
||||
def arrow(self, x1, y1, x2, y2, label=None, color="#203a53", dashed=False):
|
||||
dash = ' stroke-dasharray="18 12"' if dashed else ""
|
||||
self.add(f'<path d="M {x1} {y1} L {x2} {y2}" fill="none" stroke="{color}" stroke-width="6" marker-end="url(#arrow)"{dash}/>')
|
||||
if label:
|
||||
lx, ly = (x1 + x2) / 2, (y1 + y2) / 2
|
||||
self.add(f'<rect x="{lx-190}" y="{ly-48}" width="380" height="42" rx="10" fill="#ffffff" opacity="0.94"/>')
|
||||
self.add(f'<text x="{lx}" y="{ly-18}" class="label" text-anchor="middle">{escape(label)}</text>')
|
||||
|
||||
def poly(self, pts, label=None, color="#203a53", dashed=False):
|
||||
dash = ' stroke-dasharray="18 12"' if dashed else ""
|
||||
d = "M " + " L ".join(f"{x} {y}" for x, y in pts)
|
||||
self.add(f'<path d="{d}" fill="none" stroke="{color}" stroke-width="6" marker-end="url(#arrow)"{dash}/>')
|
||||
if label:
|
||||
x, y = pts[len(pts)//2]
|
||||
self.add(f'<rect x="{x-200}" y="{y-52}" width="400" height="42" rx="10" fill="#ffffff" opacity="0.94"/>')
|
||||
self.add(f'<text x="{x}" y="{y-22}" class="label" text-anchor="middle">{escape(label)}</text>')
|
||||
|
||||
def render(self):
|
||||
defs = f"""
|
||||
<defs>
|
||||
<marker id="arrow" markerWidth="18" markerHeight="18" refX="14" refY="6" orient="auto" markerUnits="strokeWidth">
|
||||
<path d="M2,2 L14,6 L2,10 Z" fill="#203a53"/>
|
||||
</marker>
|
||||
<style>
|
||||
svg {{ background: #ffffff; }}
|
||||
text {{ font-family: {FONT}; fill: #17212b; }}
|
||||
.title {{ font-size: 78px; font-weight: 850; }}
|
||||
.subtitle {{ font-size: 35px; fill: #53606c; }}
|
||||
.section {{ font-size: 43px; font-weight: 850; fill: #233d56; }}
|
||||
.boxtitle {{ font-size: 35px; font-weight: 850; }}
|
||||
.boxbody {{ font-size: 27px; fill: #33495d; }}
|
||||
.smalltitle {{ font-size: 29px; font-weight: 850; }}
|
||||
.smallbody {{ font-size: 24px; fill: #35495e; }}
|
||||
.label {{ font-size: 25px; font-weight: 750; fill: #25415d; }}
|
||||
.foot {{ font-size: 26px; fill: #52606d; }}
|
||||
</style>
|
||||
</defs>
|
||||
"""
|
||||
return f'<svg xmlns="http://www.w3.org/2000/svg" width="{W}" height="{H}" viewBox="0 0 {W} {H}">\n{defs}\n' + "\n".join(self.out) + "\n</svg>\n"
|
||||
|
||||
|
||||
s = SVG()
|
||||
s.add(f'<rect x="0" y="0" width="{W}" height="{H}" fill="#ffffff"/>')
|
||||
s.add('<text x="2600" y="118" class="title" text-anchor="middle">LinuxCNC 数据系统核心原理</text>')
|
||||
s.add('<text x="2600" y="176" class="subtitle" text-anchor="middle">INI / NML / Task / Interpreter / Motion Shared Memory / HAL / GUI</text>')
|
||||
|
||||
# Layers
|
||||
s.section(130, 260, 4940, 660, "1. 静态装配层:INI 决定系统如何启动", "#eef6ff", "#9fb9d2")
|
||||
s.box(250, 390, 960, 250, "INI 文件", "机器拓扑、KINS、JOINTS、TRAJ、DISPLAY、HALFILE、HALCMD、NML_FILE。INI 是启动装配说明,不是实时数据通道。", "#386d9d")
|
||||
s.box(1510, 390, 920, 250, "scripts/linuxcnc", "读取 INI,启动 linuxcncsvr、realtime、milltask、halui、HAL 配置和 GUI。", "#386d9d")
|
||||
s.box(2730, 390, 900, 250, "模块装配", "loadrt motmod / kinematics / pid / 驱动;loadusr halui / Vismach / GUI 组件。", "#386d9d")
|
||||
s.box(3910, 390, 920, 250, "初始参数落地", "INI 值被转写到 task 状态、motion config、HAL pin/param 或组件启动参数。", "#386d9d")
|
||||
s.arrow(1210, 515, 1510, 515)
|
||||
s.arrow(2430, 515, 2730, 515)
|
||||
s.arrow(3630, 515, 3910, 515)
|
||||
|
||||
s.section(130, 1020, 4940, 940, "2. NML:跨进程命令 / 状态 / 错误总线", "#f8f3ff", "#b59ad7")
|
||||
s.box(250, 1180, 930, 250, "emcCommand", "GUI、halui、外部客户端写入命令。典型命令:MDI、AUTO_RUN、SET_MODE、SET_STATE、ABORT。", "#7a4eb0")
|
||||
s.box(1520, 1180, 900, 250, "milltask", "读取 NML 命令,检查状态合法性,调度 interpreter、motion、IO、tool、spindle。", "#7a4eb0")
|
||||
s.box(2760, 1180, 930, 250, "emcStatus", "task 汇总 EMC_STAT:task、motion、io。GUI/halui 读取该状态。", "#7a4eb0")
|
||||
s.box(4030, 1180, 780, 250, "emcError", "错误、操作信息、诊断消息通道。", "#7a4eb0")
|
||||
s.arrow(1180, 1305, 1520, 1305, "命令")
|
||||
s.arrow(2420, 1305, 2760, 1305, "状态汇总")
|
||||
s.arrow(3690, 1305, 4030, 1305, "错误/信息")
|
||||
s.small(580, 1600, 1180, 190, "linuxcncsvr", "NML channel master/server,通常最先启动。", "#7a4eb0")
|
||||
s.small(2260, 1600, 1420, 190, "EMC_STAT", "EMC_TASK_STAT + EMC_MOTION_STAT + EMC_IO_STAT。", "#7a4eb0")
|
||||
|
||||
s.section(130, 2060, 4940, 1000, "3. Task / Interpreter:把人的意图转成规范动作", "#fff8eb", "#d49a35")
|
||||
s.box(250, 2220, 880, 260, "GUI / halui 意图", "按钮、MDI、自动运行、暂停、jog、模式切换。它们表达意图,不直接控制 servo 周期。", "#b36b00")
|
||||
s.box(1420, 2220, 900, 260, "Interpreter", "读取 G-code,维护模态组、坐标系、G92、刀补、参数、remap、子程序调用。", "#b36b00")
|
||||
s.box(2610, 2220, 920, 260, "Canonical Commands", "直线、圆弧、主轴、IO、M68 analog output、换刀等规范动作。", "#b36b00")
|
||||
s.box(3820, 2220, 900, 260, "taskintf.cc", "把规范动作转成 emcmot_command_t 或 IO/tool/spindle 命令。", "#b36b00")
|
||||
s.arrow(1130, 2350, 1420, 2350)
|
||||
s.arrow(2320, 2350, 2610, 2350)
|
||||
s.arrow(3530, 2350, 3820, 2350)
|
||||
s.small(640, 2700, 1240, 190, "例:M68 E3 Q1", "解释器 -> SET_AUX_OUTPUT_VALUE -> EMC_MOTION_SET_AOUT。", "#b36b00")
|
||||
s.small(2350, 2700, 1320, 190, "关键边界", "G-code 不直接写 HAL;它先进入 task/interpreter。", "#b36b00")
|
||||
|
||||
s.section(130, 3160, 4940, 1020, "4. Motion Shared Memory:非实时 task 与实时 motion 的边界", "#eefaf1", "#65a36f")
|
||||
s.box(250, 3320, 1050, 300, "emcmot_command_t", "task 写入,motion 读取。包含 command code、pos、vel、acc、tool_offset、AOUT/DOUT、spindle 等命令参数。", "#3f7d4a")
|
||||
s.box(1580, 3320, 1030, 300, "emcmot_status_t", "motion 周期更新,task 读取。包含 carte_pos_cmd/fb、joint/axis/spindle、queue、analog_output、heartbeat。", "#3f7d4a")
|
||||
s.box(2890, 3320, 920, 300, "emcmot_config_t", "实时 motion 配置:joint 数、kinematics type、速度/加速度限制等。", "#3f7d4a")
|
||||
s.box(4090, 3320, 760, 300, "head / tail", "读取 status 时检查 head == tail,避免读到半更新快照。", "#3f7d4a")
|
||||
s.arrow(1300, 3470, 1580, 3470, "执行后反馈")
|
||||
s.arrow(2610, 3470, 2890, 3470)
|
||||
s.arrow(3810, 3470, 4090, 3470)
|
||||
s.small(670, 3840, 1260, 190, "usrmotWriteEmcmotCommand()", "加 command_mutex,复制 command,等待 commandNumEcho。", "#3f7d4a")
|
||||
s.small(2530, 3840, 1320, 190, "usrmotReadEmcmotStatus()", "复制 status,检查 head/tail 一致性。", "#3f7d4a")
|
||||
|
||||
s.section(130, 4280, 4940, 1080, "5. Realtime Motion + HAL:周期执行与机器信号网络", "#eef9f8", "#4b9c9a")
|
||||
s.box(250, 4440, 920, 300, "servo-thread", "固定周期运行。执行 motion-controller、PID、驱动、仿真组件等 HAL function。", "#2b7a78")
|
||||
s.box(1450, 4440, 980, 300, "motion-controller", "轨迹取点、switchkins、forward/inverse kinematics、limits、probe、jog、homing、status。", "#2b7a78")
|
||||
s.box(2710, 4440, 900, 300, "HAL pins", "motion、joint、spindle、驱动、Vismach、halui 都通过 pin 暴露实时变量。", "#2b7a78")
|
||||
s.box(3890, 4440, 900, 300, "HAL signals", "net 命令将 pin 连接到同一 signal。通常一个 writer,多个 reader。", "#2b7a78")
|
||||
s.arrow(1170, 4590, 1450, 4590)
|
||||
s.arrow(2430, 4590, 2710, 4590)
|
||||
s.arrow(3610, 4590, 3890, 4590)
|
||||
s.small(520, 5020, 1200, 190, "HAL shared memory", "component list / pin list / signal list / param list / function list / thread list。", "#2b7a78")
|
||||
s.small(2080, 5020, 1200, 190, "net 的本质", "把 pin data pointer 指向同一个 signal value。", "#2b7a78")
|
||||
s.small(3620, 5020, 1040, 190, "实时原则", "控制闭环必须在 motion/HAL 内,不能依赖 GUI poll。", "#2b7a78")
|
||||
|
||||
s.section(130, 5460, 4940, 900, "6. 状态回流与 GUI 显示", "#f6f7f9", "#9da8b3")
|
||||
s.box(250, 5620, 920, 250, "motion status", "实时 motion 更新 emcmot_status_t。", "#5b6570")
|
||||
s.box(1470, 5620, 920, 250, "task update", "emcMotionUpdate() 将 motion status 汇总到 EMC_MOTION_STAT。", "#5b6570")
|
||||
s.box(2690, 5620, 920, 250, "EMC_STAT", "顶层状态:task + motion + io。写入 emcStatus NML。", "#5b6570")
|
||||
s.box(3910, 5620, 920, 250, "GUI / halui", "poll emcStatus,显示位置、模式、状态、错误、队列、模态。", "#5b6570")
|
||||
s.arrow(1170, 5745, 1470, 5745)
|
||||
s.arrow(2390, 5745, 2690, 5745)
|
||||
s.arrow(3610, 5745, 3910, 5745)
|
||||
s.poly([(4360, 5620), (4360, 5350), (390, 5350), (390, 4740)], "HAL/Vismach 也可直接读 HAL", dashed=True)
|
||||
|
||||
s.add('<text x="2600" y="6460" class="foot" text-anchor="middle">核心原则:NML 表达系统命令/状态;HAL 表达实时机器信号;motion shared memory 是 task 与实时控制的边界。</text>')
|
||||
s.add('<text x="2600" y="6505" class="foot" text-anchor="middle">输出文件:项目分析/LinuxCNC数据系统核心原理高清流程图.png;源文件:项目分析/LinuxCNC数据系统核心原理高清流程图.svg</text>')
|
||||
|
||||
with open("项目分析/LinuxCNC数据系统核心原理高清流程图.svg", "w", encoding="utf-8") as f:
|
||||
f.write(s.render())
|
||||
Reference in New Issue
Block a user