chore: finalize remaining project artifacts

This commit is contained in:
wangdequan
2026-07-05 22:13:40 -04:00
parent 4224f835dc
commit 6b937a038d
30 changed files with 14093 additions and 46 deletions

View 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_STATtask、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())