#!/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'')
self.add(f'{escape(title)}')
def box(self, x, y, w, h, title, body, stroke, fill="#ffffff"):
self.add(f'')
self.add(f'{escape(title)}')
ty = y + 84
for line in wrap(body, max(12, int((w - 56) / 18))):
self.add(f'{escape(line)}')
ty += 31
def small(self, x, y, w, h, title, body, stroke, fill="#ffffff"):
self.add(f'')
self.add(f'{escape(title)}')
ty = y + 68
for line in wrap(body, max(10, int((w - 42) / 16))):
self.add(f'{escape(line)}')
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'')
if label:
lx, ly = (x1 + x2) / 2, (y1 + y2) / 2
self.add(f'')
self.add(f'{escape(label)}')
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'')
if label:
x, y = pts[len(pts)//2]
self.add(f'')
self.add(f'{escape(label)}')
def render(self):
defs = f"""
"""
return f'\n"
s = SVG()
s.add(f'')
s.add('LinuxCNC 数据系统核心原理')
s.add('INI / NML / Task / Interpreter / Motion Shared Memory / HAL / GUI')
# 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('')
s.add('')
with open("项目分析/LinuxCNC数据系统核心原理高清流程图.svg", "w", encoding="utf-8") as f:
f.write(s.render())