Add AC/BC RTCP programs and 3D machine manager
This commit is contained in:
290
scripts/generate_rtcp_teaching_html.py
Normal file
290
scripts/generate_rtcp_teaching_html.py
Normal file
@@ -0,0 +1,290 @@
|
||||
"""Build the printable, editable Chinese RTCP teaching deck from verified data."""
|
||||
import html
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
OUT = ROOT / 'docs/teaching/rtcp-five-axis'
|
||||
data = json.loads((OUT / 'numeric-evidence.json').read_text())
|
||||
slides = []
|
||||
|
||||
def fig(name, caption='', cls=''):
|
||||
return f'<figure class="{cls}"><img src="assets/{name}.png" alt="{html.escape(caption)}"><figcaption>{caption}</figcaption></figure>'
|
||||
|
||||
def page(chapter, title, body, takeaway='', sources='', cls=''):
|
||||
slides.append({'chapter': chapter, 'title': title, 'body': body, 'takeaway': takeaway, 'sources': sources, 'cls': cls})
|
||||
|
||||
def table(headers, rows, cls=''):
|
||||
return '<table class="'+cls+'"><thead><tr>'+''.join('<th>'+h+'</th>' for h in headers)+'</tr></thead><tbody>'+''.join('<tr>'+''.join('<td>'+str(x)+'</td>' for x in row)+'</tr>' for row in rows)+'</tbody></table>'
|
||||
|
||||
def fmt(x):
|
||||
return f'{0 if abs(x)<0.0005 else x:.3f}'
|
||||
|
||||
def xyz(values): return '('+', '.join(fmt(v) for v in values[:3])+')'
|
||||
|
||||
page('教学课件 · 2026.09', '五轴机床与 RTCP',
|
||||
'<div class="cover-grid"><div><p class="subtitle">从“各轴独立”到“刀尖跟随”</p><p>看懂两个坐标系、一棵结构树、<br>一条运动学计算链。</p><div class="rule"></div><p class="small">适用对象:数控入门、五轴调试与仿真教学<br>主线机型:XYZAC / XYZBC 双转台桁架机床<br>建议课时:60–90 分钟,可拆成两讲</p><p class="badge">原理图解 + 数值演算 + 浏览器实验 + 练习答案</p></div>'+fig('machine-bc','原创 BC 桁架模型:XYZ 移动刀具,B–C 转动工件','cover-model')+'</div>',
|
||||
'核心问题:程序中的 XYZ,究竟是在告诉谁“去哪里”?', '图:本项目原创 GLB 模型;数值:LinuxCNC WASM 实算。', 'cover')
|
||||
|
||||
page('01 · 先统一理解', '你的两点理解,怎样说得更准确?',
|
||||
'<div class="cols"><div><h3>① 非 RTCP:直接轴映射</h3><p>在本课的双转台结构中,XYZ 带动刀具,A/C 或 B/C 带动工件。</p><p><b>只改变旋转轴指令,不自动触发 XYZ 的刀尖保持补偿。</b>但多个轴仍可在同一段 G 代码中协调插补。</p><p class="small">“独立”描述几何映射,不是运动时间完全无关。</p></div><div><h3>② RTCP:保持刀尖相对工件的轨迹</h3><p>控制器根据编程点、转台角度、旋转中心和标定参数,计算实际关节位置。</p><p><b>工件坐标方向随转台变换,XYZ 关节按需联动。</b>计算还包括平移与偏置,不能只旋转方向箭头。</p><p class="small">“跟随”是相对工件满足目标关系,不是世界坐标中的刀尖永远不动。</p></div></div>',
|
||||
'课程约定:先用零工件偏置、零刀长差的简化模型讲清原理,再加入实际参数。', '[S1][S2];结合本项目 AC/BC 模型。')
|
||||
|
||||
page('01 · 结构与轴名', '五轴 = 三个直线轴 + 两个旋转轴',
|
||||
table(['名称','通常的轴线含义','本课中的执行机构'],[
|
||||
['X / Y / Z','沿三个正交方向平移','刀具侧的直线滑台、滑枕'],
|
||||
['A','绕 X 方向的轴线转动','AC 机床的外层倾转轴'],
|
||||
['B','绕 Y 方向的轴线转动','BC 机床的外层倾转轴'],
|
||||
['C','绕 Z 方向的轴线转动','装在倾转台上的内层旋转轴'],
|
||||
])+'<div class="pair"><p><b>AC 型:</b>X、Y、Z、A、C</p><p><b>BC 型:</b>X、Y、Z、B、C</p></div><p class="small">XYZABC 六个独立轴同时配置,通常应称六轴。A/B/C 是旋转轴的通用名称,本课不是三只转台轴同时工作。C 的轴线会被父级 A/B 倾转,倾转后不再固定平行于机床 Z。</p>',
|
||||
'从正方向看懂箭头前,先确认观察视角、父级坐标系和机床正负号约定。', '[S3] 轴命名;图中数值采用本项目原生变换约定。')
|
||||
|
||||
page('01 · 机械拓扑', '两条运动链,共用一个底座',
|
||||
fig('hierarchy','本课桁架结构:X 带动整个横梁,Y 带动滑座,Z 带动滑枕与刀具。','wide-diagram')+
|
||||
'<div class="cols tight"><p><b class="blue">刀具分支</b><br>底座 → X → Y → Z<br>运动从父节点传给所有子节点。</p><p><b class="teal">工件分支</b><br>底座 → A/B → C → 工件<br>A/B 转动时,C 的轴线也随之倾斜。</p></div>',
|
||||
'这是一种五轴结构,不是所有五轴机床的通用拓扑;摆头、头台混合型需要不同模型。', '本项目 trt-xyz-gantry/1;旧标准型在 Z 下另有固定主轴箱节点。')
|
||||
|
||||
page('01 · 真实模型', 'AC 与 BC:外层倾转轴不同',
|
||||
'<div class="cols model-pair">'+fig('machine-ac','AC 桁架:A 绕 X,内层 C 随 A 倾转')+fig('machine-bc','BC 桁架:B 绕 Y,内层 C 随 B 倾转')+'</div><p class="small">两者都能实现刀尖相对工件的五轴轨迹,但同一角度数值对应的空间姿态不同;不能只把程序里的 A 字母换成 B,就认为加工姿态完全相同。</p>',
|
||||
'先识别“哪根轴装在哪根轴上”,再选择运动学模型。', '图:原创可下载 GLB;不是制造商的标定尺寸图。')
|
||||
|
||||
page('02 · 三种位置', '不要把“程序点、关节位置、实际反馈”混为一谈',
|
||||
table(['量','回答的问题','典型表示'],[
|
||||
['编程位置 p','刀尖相对所用编程参考系要到哪里?','(X, Y, Z),加 A/C 或 B/C'],
|
||||
['关节指令 q','各执行轴需要到哪个位置?','(J0, J1, J2, J3, J4)'],
|
||||
['实际反馈 q_actual','编码器报告电机/机械轴在哪里?','真实设备中可能存在跟随误差'],
|
||||
['三维刀尖位置 r','把关节和模型组合后,刀尖在场景哪里?','显示坐标,不是独立运动控制源'],
|
||||
])+'<p class="small">理想仿真中“关节指令 = 关节反馈”常成立,但真实机床两者仍须区分。RTCP 的运动学把 p 转成 q;模型再把 q 投影成画面。</p>',
|
||||
'“实际位置”一词要说明是在说编程坐标、关节指令,还是编码器反馈。')
|
||||
|
||||
page('02 · G 代码含义', '非 RTCP 时,G 代码也不总等于机械绝对位置',
|
||||
'<div class="flow"><span>程序字值</span><i>→</i><span>单位 / G90–G91</span><i>→</i><span>工件零点 / 补偿</span><i>→</i><span>运动学</span><i>→</i><span>关节指令</span></div>'+table(['因素','为什么不能直接等同'],[
|
||||
['G90 与 G91','G90 通常给绝对目标;G91 给相对增量,X10 不一定是到 X=10。'],
|
||||
['G54 等工件坐标系','工件零点可能偏离机床零点;程序 X10 可能对应机床 X30。'],
|
||||
['G92、刀长等补偿','控制器还需处理偏置和刀具参考点。'],
|
||||
['Identity 直接映射','在完成以上处理后,所选坐标与关节才按配置直接对应。'],
|
||||
])+'<p class="small">简化例:G54 原点位于机床 (20,30,0),无其他偏置/补偿,程序点 (10,0,5) 对应机床点 (30,30,5)。</p>',
|
||||
'只有把偏置、单位、增量方式、刀具基准都约定好,才能说“程序值直接对应轴位置”。', '[S2][S3];本页示例为无旋转的 Identity 情形。')
|
||||
|
||||
page('03 · 非 RTCP', '只转 C,工件点会离开刀尖',
|
||||
fig('identity-no-follow','俯视图:蓝色为固定机床坐标;绿色为工件;红色为刀尖。Z 保持 20 mm。','large-diagram')+
|
||||
'<p>起点刀尖对准转台上半径 100 mm 的标记点。只命令 C 从 0° 转到 90°,<b>XYZ 关节不补偿</b>,标记点转到另一处,刀尖仍在原来的机床位置。</p>',
|
||||
'轴可以独立驱动,不代表“刀具与工件的相对位置”不受其他轴影响。')
|
||||
|
||||
page('03 · RTCP', '同样只改变角度,刀尖跟住工件点',
|
||||
fig('tcp-follow','同一个工件局部点 p=(100,0,20),C=0° 与 C=90°;旋转中心和偏置取零。','large-diagram')+
|
||||
'<p>保持编程点 p 不变,只改变 C。控制器计算所需 XYZ 关节,让刀尖到达标记点的新机床位置。<b>点在工件坐标中不变,在机床坐标中沿圆弧移动。</b></p>',
|
||||
'判断“跟随”的参考系是工件,而不是屏幕或机床底座。')
|
||||
|
||||
page('03 · 数值算例 1', 'C 转 90°:同一编程点,不同关节位置',
|
||||
'<p class="intro">零偏置、零刀长差,p = (100, 0, 20) mm;A/B = 0°。</p>'+table(['C 角度','RTCP 编程 XYZ','RTCP 所需关节 XYZ','非 RTCP:只转 C 的关节 XYZ'],[
|
||||
[f'{s["c"]}°','(100, 0, 20)',xyz(s['joints']),'(100, 0, 20)'] for s in data['cases'][0]['samples'][:3]
|
||||
])+'<div class="equation">q<sub>x</sub> = 100 cos C q<sub>y</sub> = 100 sin C q<sub>z</sub> = 20</div><p class="small">C=90° 时,XYZ 关节目标是 (0,100,20)。这不是控制器偷偷修改了程序点,而是同一点在另一参考系中的正确位置。</p>',
|
||||
'RTCP:程序坐标可以不变,执行轴位置必须按姿态变化。', 'numeric-evidence.json;原生 WASM IK/FK 双向核对,显示保留3位小数。')
|
||||
|
||||
page('04 · 坐标方向', 'C = 45° 时,“沿工件 X 走”意味着什么?',
|
||||
'<div class="cols image-right"><div><p>蓝色 X<sub>M</sub>、Y<sub>M</sub> 是机床固定方向;绿色 X<sub>W</sub>、Y<sub>W</sub> 随工件转动。</p><p>姿态保持 C=45°,编程增量:</p><div class="equation">Δp<sub>W</sub> = (10, 0, 0)</div><p>对应的机床直线轴增量:</p><div class="equation">Δq = (7.071, 7.071, 0)</div><p class="small">不是机床 X 导轨真的旋转了,而是 X、Y 导轨合成出所需空间运动方向。</p></div>'+fig('rotated-direction','图中工件原点为便于观看作了平移;增量不受原点平移影响。')+'</div>',
|
||||
'你的“XYZ 方向随旋转改变”可理解为工件基向量在机床坐标中的方向改变。')
|
||||
|
||||
bc = data['cases'][1]['samples'][4]
|
||||
page('04 · 数值算例 2', 'BC 倾转后,沿工件 X 会同时改变机床 X、Y、Z',
|
||||
'<div class="cols image-right"><div><p>采用本课的正向变换约定:</p><div class="equation small-eq">R = R<sub>Y</sub>(B) · R<sub>Z</sub>(C)</div><p>当 B=30°、C=45° 时:</p><div class="equation small-eq">X<sub>W</sub> 在 M 中的方向<br>(0.6124, 0.7071, −0.3536)</div><p>所以沿 X<sub>W</sub> 前进 10 mm:</p><p class="number">Δq ≈ (6.124, 7.071, −3.536) mm</p><p class="small">倾斜后的坐标方向并不与任何一根直线导轨重合,需要三根直线轴合成。</p></div>'+fig('bc-frame','B=30°、C=45°;红点为工件点 p=(100,0,20) 的机床位置。')+'</div>',
|
||||
'RTCP 中“沿工件 X”不是“只让 X 电机动”。')
|
||||
|
||||
ac = data['cases'][0]['samples'][4]
|
||||
page('04 · AC 与 BC 对照', '同样的 30° / 45°,不是同一个姿态',
|
||||
'<p class="intro">仍取 p=(100,0,20) mm,所有旋转中心、偏置和刀长差为零。</p>'+table(['结构','父级倾转 × 子级旋转','倾转30°、C45°时的关节XYZ'],[
|
||||
['AC','R<sub>X</sub>(A) · R<sub>Z</sub>(C)',xyz(ac['joints'])],
|
||||
['BC','R<sub>Y</sub>(B) · R<sub>Z</sub>(C)',xyz(bc['joints'])],
|
||||
])+'<div class="cols"><div><h3>AC:绕 X 倾转</h3><p>C 先改变平面位置,A 再把局部 Y/Z 方向倾斜。</p></div><div><h3>BC:绕 Y 倾转</h3><p>C 先改变平面位置,B 再把局部 X/Z 方向倾斜。</p></div></div><p class="small">矩阵从右向左作用于列向量;这是坐标转换次序,不是要求 G 代码先运动 C 再运动 A/B。插补时两轴可以同时运动。</p>',
|
||||
'运动学类型选错,即使每个轴都会动,刀尖也不会沿正确的相对轨迹走。', 'numeric-evidence.json;零偏置原生 WASM 结果。')
|
||||
|
||||
page('05 · 为什么还需要几何参数', '同样转 90°,绕不同的中心会到不同的位置',
|
||||
fig('pivot-offset','二维俯视例:起点相同,旋转角度相同,仅旋转中心不同。','large-diagram')+
|
||||
'<div class="equation">p<sub>M</sub> = O + R · (p − O)</div><p class="small">计算顺序:先减去旋转中心 → 旋转 → 再加回中心。不能只计算 R·p,否则相当于错误地绕坐标零点转。</p>',
|
||||
'RTCP 不只是“方向变换”,还包括“绕哪里转”的平移关系。')
|
||||
|
||||
page('05 · 轴间偏置与变换顺序', 'C 轴装在倾转轴上:父子关系不能交换',
|
||||
'<div class="cols"><div><h3>先看几何,再写方程</h3><p><b>P:</b>C 轴零位参考点。<br><b>D:</b>倾转轴与 C 轴之间的偏置。<br><b>T=P+D:</b>倾转轴参考位置。</p><p>C 旋转后的点还要随 A/B 倾转;偏置也必须放在正确的父子层级中。</p></div><div><h3>URDF / GLB 里的对应关系</h3>'+table(['节点','本项目零位 origin'],[
|
||||
['A/B,父节点为底座','P + D'],['C,父节点为 A/B','−D'],['work,父节点为 C','−P'],
|
||||
],'compact')+'</div></div><div class="equation small-eq">q<sub>XYZ</sub> = P + D + R<sub>tilt</sub> [ R<sub>C</sub> (p − P) − D ] (刀长差为零)</div><p class="small">这是本项目 AC/BC TRT 约定。更一般的机床使用完整的刚体变换链;不能把这三个参数关系硬套到摆头机床。长度在运行配置中为 mm,URDF 为 m。</p>',
|
||||
'R<sub>tilt</sub>R<sub>C</sub> 通常不等于 R<sub>C</sub>R<sub>tilt</sub>;机械安装顺序决定计算顺序。', '本地 trtfuncs.c;本项目 URDF profile / D11 / D12。')
|
||||
|
||||
page('05 · 刀长与受控点', '控制的是刀尖,不只是主轴箱',
|
||||
fig('tool-length','侧视示意:同一主轴参考位置,刀长不同会产生不同刀尖位置。','large-diagram')+
|
||||
'<p>刀长改变时,为保持同一个刀尖目标,需要重新计算主轴参考点的位置。必须明确刀具长度的测量基准、正负号、刀补状态,以及模型上“tool”节点代表哪里。</p><p class="small">教学主算例采用刀长差为零。本项目原生 TRT 使用相对于 master tool 的 tool-offset 约定,不能把它直接当作任意模型网格的总长度。</p>',
|
||||
'同一组关节位置 + 不同刀具标定,不一定得到同一个刀尖位置。', '[S1][S3];实际使用以机床和控制器的刀具标定约定为准。')
|
||||
|
||||
page('06 · 姿态的物理意义', '双转台没有转动主轴,却改变了相对刀轴方向',
|
||||
fig('orientation','侧视:主轴在机床中保持竖直,工件随转台倾斜,二者的相对夹角改变。','large-diagram')+
|
||||
'<p>在本课双转台机床中,刀轴在机床坐标里的方向固定;工件转动后,<b>刀轴在工件坐标里的方向改变</b>。RTCP 根据指定角度协调刀尖位置,不会自动推断曲面法向。</p>',
|
||||
'RTCP 不是“自动让刀轴垂直于任意曲面”;姿态仍由程序、CAM 或操作者给定。')
|
||||
|
||||
page('06 · 三种常见联动情形', '分清“保持点”“沿方向走”“同时改变位置和姿态”',
|
||||
table(['输入变化','RTCP 应做什么','观察重点'],[
|
||||
['XYZ 不变;A/B、C 改变','让刀尖保持同一工件编程点;XYZ关节可能联动。','刀尖与工件标记点不分离。'],
|
||||
['角度不变;XYZ 改变','沿当前工件参考系定义的方向移动。','机床多轴合成编程方向。'],
|
||||
['XYZ 与角度同时改变','执行变化的编程轨迹,同时维持角度对应的几何关系。','全过程相对轨迹正确,不只看终点。'],
|
||||
['XYZ 不变;点恰在 C 轴线上;只转 C','该点机床位置可以不变。','XYZ 不动不一定是故障。'],
|
||||
])+'<p class="small">“五轴联动”不要求每个时刻五轴速度都非零。非 RTCP 也能多轴同步加工:CAM/后置可以预先按指定机床几何计算轴目标,但这不等于控制器开启了实时刀尖补偿。</p>',
|
||||
'每次解释一个动作,先说清哪些程序量保持不变、哪些程序量在改变。')
|
||||
|
||||
page('06 · 全过程的重要性', '终点都正确,不代表中间轨迹正确',
|
||||
'<div class="cols image-right"><div><p>同一个工件点绕 C 转动,理想半径为 100 mm。</p><p><b class="teal">RTCP:</b>刀尖跟随圆弧。<br><b class="red">不做 RTCP:</b>若只让 X/Y 线性插补到相同终点,刀尖可能走弦线。</p>'+table(['中间位置','X / Y'],[['圆弧,C=45°','70.711 / 70.711'],['弦线,50%进度','50.000 / 50.000']],'compact')+'<p class="small">图示采用共同线性进度比较几何路径,不是断言所有控制器的加减速过程相同。</p></div>'+fig('arc-chord','中途差距 29.289 mm;起点和终点却完全相同。')+'</div>',
|
||||
'验收 RTCP 要看连续采样、刀尖相对轨迹和关节约束,不能只比首末坐标。')
|
||||
|
||||
switch = data['switch_example']
|
||||
page('07 · 模式切换', 'M429 ↔ M428:坐标可以变,机床不能无故跳动',
|
||||
'<p class="intro">本项目 BC 示例:保持同一组物理关节,切换坐标解释。</p>'+table(['量','M429 / Identity','M428 / TCP'],[
|
||||
['物理关节 XYZ',xyz(switch['physical']),xyz(switch['physical'])],
|
||||
['物理关节 B / C','4.8675° / 179.88°','4.8675° / 179.88°'],
|
||||
['显示/解释的 XYZ',xyz([switch['identity_pose'][a] for a in 'xyz']),xyz([switch['tcp_pose'][a] for a in 'xyz'])],
|
||||
['三维刀尖','同一空间位置','同一空间位置'],
|
||||
])+'<div class="flow"><span>取当前关节 q</span><i>→</i><span>切换运动学</span><i>→</i><span>新模式正解 p</span><i>→</i><span>同步 TP / 解释器</span></div><p class="small">本例使用真实 BC 模型参数 P=(−18,14,12)、D=(22,0,−40) mm,刀长差0。切换之后再给运动指令,才产生运动。</p>',
|
||||
'若切换时强行保持旧 XYZ 数字并重新逆解,就可能错误地改变物理关节。', '[S2] 同步原则;本地 control.c::handle_kinematicsSwitch;WASM实算。')
|
||||
|
||||
page('07 · 控制器功能边界', 'RTCP、旋转工作平面、刀补,不是同一个开关',
|
||||
table(['功能','主要解决的问题','不要误认为'],[
|
||||
['RTCP / TCP 运动学','姿态变化时,按机床结构实现目标刀尖相对轨迹。','所有控制器都会以完全相同方式解释XYZ。'],
|
||||
['工件坐标系 / 倾斜工作平面','选择编程参考原点或定义编程方向。','只旋转工作平面就自动具备完整五轴RTCP。'],
|
||||
['刀长补偿','把刀具参考点换算到实际受控刀尖。','更换刀具后只更新网格外观就够了。'],
|
||||
['机床标定','确定中心、偏置、轴向、正负号和长度。','只要支持RTCP,错误几何参数也会自动纠正。'],
|
||||
])+'<p class="small">不同控制器可能分别使用 TCP、TCPM、RTCP 等名称,并把倾斜平面/刀补组合进不同指令。本课明确讲本项目的 AC/BC 双转台语义,不推定所有品牌一致。</p>',
|
||||
'换控制器时先核对“参考系、受控点、角度定义、指令组合”,不要只对照功能名称。')
|
||||
|
||||
page('08 · 程序对照', '同样写 C90,开关状态决定 XYZ 是否补偿',
|
||||
'<div class="cols"><div><h3>M429:直接映射</h3><pre>G21 G90 G94\nM429\nG1 X100 Y0 Z20 A0 C0 F300\nG1 C90 F60</pre><p>后一段只改变 C。<br>XYZ 关节保持 (100,0,20),<br>工件标记点会离开刀尖。</p></div><div><h3>M428:RTCP</h3><pre>G21 G90 G94\nM428\nG1 X100 Y0 Z20 A0 C0 F300\nG1 C90 F60</pre><p>后一段保持编程 XYZ。<br>XYZ 关节变到 (0,100,20),<br>刀尖仍对准同一个工件点。</p></div></div><p class="small">仅为零偏置/零刀长差 AC 教学片段,起始角度和当前模态需已知;BC 用 B0。现有下载模型参数非零,不能期待相同数字。M428/M429 为本项目重映射,不是通用标准M码;F值只作插补示意,实际需按机床的旋转/混合进给规则设定。</p>',
|
||||
'程序字值相同,不代表开关前后表达的是同一个机械目标。', '本项目 xyzac/xyzbc-trt.ini,428remap.ngc / 429remap.ngc。')
|
||||
|
||||
page('08 · 浏览器课堂实验', '用 AC/BC 桁架模型验证,而不是只看公式',
|
||||
'<ol class="steps"><li><b>加载:</b>打开“导入机床模型”,选 AC 或 BC 桁架示例;记录旋转中心与偏置。</li><li><b>准备:</b>确认空闲,开机并回零;选一个离 C 轴线有一定距离的刀尖位置。</li><li><b>Identity:</b>选择 M429,只 Jog A/B 或 C;观察 XYZ 关节是否保持。</li><li><b>切换:</b>停止后选择 M428;观察关节/刀尖不跳,XYZ 坐标可能变化。</li><li><b>TCP:</b>再次 Jog 旋转轴;观察 XYZ 关节按需补偿、刀尖相对工件点保持。</li><li><b>对照:</b>先停止,再切回 M429;重复方向相同的小幅 Jog,比较两种行为。</li></ol><p class="small">实验先用小幅角度并远离限位。记录的是“每次试验起点相对工件的同一点”;切换或重新定位后,不要把两个不同起点混作同一个目标。</p>',
|
||||
'同时观察三项:程序XYZ、J0…J4、刀尖与工件的相对关系。', '页面 http://127.0.0.1:5174/;浏览器仿真教学,不是机床现场操作规程。')
|
||||
|
||||
page('08 · 参数化实验', '改几何参数后,为什么仍应保留同一个模型?',
|
||||
'<div class="cols"><div><h3>正确的数据流</h3><div class="vertical-flow"><span>参数表:中心 / 偏置</span><b>↓</b><span>有效 URDF:节点零位关系</span><b>↓</b><span>INI → Worker → WASM</span><b>↓</b><span>关节 → 原导入模型</span></div></div><div><h3>课堂观察</h3><ul><li>同一 GLB 的网格、材质和文件身份保持。</li><li>新的轴心与偏置应进入运行结构和运动学。</li><li>相同程序点和角度,可能得到不同的 XYZ 关节值。</li><li>参数改变后需重新回零并重新解析程序。</li></ul><p class="small">源GLB不自动改写;可保存INI记录参数。改变轴心不等于自动重建支架、铸件等CAD外形,也不提供碰撞检测。</p></div></div>',
|
||||
'模型保留和参数生效必须同时成立;只改外观或只改计算都会造成误导。', '本项目 D11 / E09:AC、BC各18个姿态/刀长样本验证。')
|
||||
|
||||
page('09 · 约束与故障', '“工件点可达”不等于“每个关节都没到限位”',
|
||||
'<div class="cols"><div><h3>两层约束</h3>'+table(['约束','检查什么'],[['坐标限位','编程轴/坐标允许的范围'],['关节限位','实际执行机构的物理行程'],['动力学约束','速度、加速度等能力']],'compact')+'<p>RTCP 旋转时,即使编程 XYZ 不变,补偿出来的 X/Y/Z 关节仍可能触及边界。</p></div><div><h3>本项目排障实例</h3><p>BC 配置允许较大的 B 坐标范围,但对应 JOINT_3 上限为 <b>50°</b>。</p><p>旧问题:越界 → 运动计算失败 → 事件时钟不同步 → Connection ERROR。</p><p>修复后:按关节约束停止,保留正确时钟,可松开并反向 Jog。</p></div></div><p class="small">本仿真对 RTCP 越界采用最后有效伺服样本停止,不等同于工业控制器的全路径限位预测与减速保护。范围和行为必须分别验证。</p>',
|
||||
'正常限位阻止是一条运动诊断;序号缺口或连接错误是软件故障,不是RTCP原理。', '本项目 R07 / E11;不据此宣称硬件安全或碰撞防护。')
|
||||
|
||||
page('09 · 常见误解', '六个问题,快速判断是否真正理解',
|
||||
table(['说法','准确解释'],[
|
||||
['“非RTCP时各轴互相没有影响。”','没有自动补偿;但工件转动当然会改变它与刀具的相对位置。'],
|
||||
['“RTCP把X/Y/Z导轨转了。”','导轨不转,是工件基方向改变,由固定导轨合成空间运动。'],
|
||||
['“RTCP时XYZ永远不变。”','只在保持同一编程点时程序XYZ不变;关节XYZ通常变化。'],
|
||||
['“只要开RTCP,刀尖在空间就不动。”','刀尖可在机床空间移动,以保持它相对工件的目标关系。'],
|
||||
['“C转了但XYZ没动,一定坏了。”','目标点恰在C轴线上时可能正确,应先检查几何。'],
|
||||
['“切换模式时XYZ数字跳,必须修好。”','要检查关节和物理刀尖是否跳;新参考系里的数值改变可能正确。'],
|
||||
],'compact'),
|
||||
'先说参考系,再说位置;先看结构,再说联动。')
|
||||
|
||||
page('10 · 课堂练习', '请先画箭头,再写结论',
|
||||
'<ol class="exercises"><li><b>点追踪:</b>零偏置,C=0°,工件点 p=(80,0,10)。只转 C 到90°,分别写出 Identity 和 RTCP 的 XYZ 关节目标。</li><li><b>方向合成:</b>C=30°且倾转角为0°,沿工件 +X 方向走20mm,机床 X/Y 各应增加多少?</li><li><b>模式切换:</b>M429切到M428时,XYZ显示值变化、J0…J4不变、刀尖不动,是否正常?为什么?</li><li><b>偏置识别:</b>BC模型 C 的局部 origin=(-0.022,0,0.040)m,倾转轴 origin=(0.004,0.014,-0.028)m。求本项目 P、D,单位mm。</li><li><b>结构判断:</b>C节点错误地挂在Z滑枕下。只Jog X时会发生什么?为什么不能靠RTCP开关解决?</li></ol>',
|
||||
'建议分组讨论 8–10 分钟;答案在下一页。')
|
||||
|
||||
page('10 · 参考答案', '用“相对谁”检查每一步',
|
||||
'<ol class="answers"><li><b>Identity:</b>(80,0,10);<b>RTCP:</b>(0,80,10)。前者刀尖留在机床原位置,后者继续对准同一工件点。</li><li><b>ΔX=20cos30°=17.321mm;ΔY=20sin30°=10.000mm。</b>方向向量长度仍为20mm,机床Z不变。</li><li><b>正常。</b>运动学切换重新解释当前物理状态。坐标可以变,关节和刀尖应保持。</li><li><b>P=T+U=(-18,14,12)mm;D=−U=(22,0,−40)mm。</b>先统一单位,再按父子关系相加,不能直接拿URDF的米当毫米。</li><li><b>C及工件会错误地随XYZ移动。</b>这是结构树错误,不是开关模式能修复的;需把A/B→C分支正确挂回底座。</li></ol>',
|
||||
'检查顺序:轴数 → 层级 → 单位/正负号 → 几何参数 → 运动学 → 程序与状态。')
|
||||
|
||||
page('附录 A · 数学推导', '从一根 C 轴,推到两根旋转轴',
|
||||
'<div class="cols"><div><h3>绕 C 的平面变换</h3><pre class="math">x′ = x cos C − y sin C\ny′ = x sin C + y cos C\nz′ = z</pre><h3>AC:再绕 X 倾转 A</h3><pre class="math">qₓ = x′\nqᵧ = y′ cos A − z′ sin A\nq𝓏 = y′ sin A + z′ cos A</pre></div><div><h3>BC:再绕 Y 倾转 B</h3><pre class="math">qₓ = x′ cos B + z′ sin B\nqᵧ = y′\nq𝓏 = −x′ sin B + z′ cos B</pre><p>这里所有旋转中心、轴间偏置及刀长差为零,列向量采用右手正向变换。</p><p class="small">式中的 q 是关节目标;若导入的是其他结构、符号约定或参考点,必须重新建立变换链。</p></div></div>',
|
||||
'先学会解释“这一步在谁的坐标系中发生”,再记忆公式。', '与本地 trtfuncs.c 零偏置逆解一致;三角函数实际以弧度计算。')
|
||||
|
||||
page('附录 B · 本项目完整参数式', '加入旋转中心、偏置与 master-tool 刀长差',
|
||||
'<div class="equation">D<sub>L</sub> = D + L e<sub>z</sub></div><div class="equation">q<sub>XYZ</sub> = P + D<sub>L</sub> + R<sub>tilt</sub> [ R<sub>C</sub> (p − P) − D<sub>L</sub> ]</div>'+table(['符号','AC','BC'],[
|
||||
['R<sub>tilt</sub>','R<sub>X</sub>(A)','R<sub>Y</sub>(B)'],['D','(0, D<sub>y</sub>, D<sub>z</sub>)','(D<sub>x</sub>, 0, D<sub>z</sub>)'],['旋转关节','q<sub>A</sub>=A,q<sub>C</sub>=C','q<sub>B</sub>=B,q<sub>C</sub>=C'],
|
||||
],'compact')+'<p class="small">P为原生rot-point;L为本项目/原生TRT的tool-offset标定量;e<sub>z</sub>=(0,0,1)。本式返回关节XYZ,不是未经处理的网格刀尖世界坐标。G54等解释器偏置在输入p形成前已按控制器规则处理。</p><p class="small">这一刀长表达是本项目约定,不能替代任意控制器的通用刀补说明。主课例子L=0;实际机床应以标定模型及其完整前后向验证为准。</p>',
|
||||
'工程实现不在画面中“补一个偏移”;应让同一组结构数据贯穿解释器、运动学和模型。', '原生源码:linuxcnc/src/emc/kinematics/trtfuncs.c,xyzac/xyzbcKinematicsInverse。')
|
||||
|
||||
page('附录 C · 教学安排与依据', '怎样讲、怎样验证、怎样继续学习',
|
||||
'<div class="cols references"><div><h3>建议授课节奏</h3><p>10分钟:结构、轴名与三种位置。<br>20分钟:C轴点跟踪、方向合成、AC/BC倾转。<br>15分钟:旋转中心、偏置、刀长与模式切换。<br>15分钟:浏览器实验与错误诊断。<br>10分钟:练习、答案和模型参数核对。</p><h3>本课验算</h3><p>12个姿态由真实WASM逆解计算,并通过正解回代;另核对C45°方向增量、BC切换示例及弧弦差。详见同目录 <code>numeric-evidence.json</code>。</p><p>图形为原创教学示意及本项目GLB截图,不用于制造尺寸标定。</p></div><div><h3>官方资料与本地实现</h3><p id="S1"><b>[S1]</b> <a href="https://linuxcnc.org/docs/html/motion/5-axis-kinematics.html">LinuxCNC · 5-Axis Kinematics</a><br>AC/BC结构、运动学及偏置。</p><p id="S2"><b>[S2]</b> <a href="https://linuxcnc.org/docs/html/motion/switchkins.html">LinuxCNC · Switchable Kinematics</a><br>模式切换、identityfirst、同步要求。</p><p id="S3"><b>[S3]</b> <a href="https://linuxcnc.org/docs/html/gcode/machining-center.html">LinuxCNC · CNC Machine Overview</a><br>轴名、坐标、受控点与协调运动。</p><p><b>本地:</b>trtfuncs.c;control.c;428remap.ngc / 429remap.ngc;docs/glb-machine/。M428=TCP、M429=Identity仅适用于本项目配置。</p><p class="small">资料核对日期:2026-09-18。不同版本/品牌的指令与坐标约定应另行核查。</p></div></div>',
|
||||
'一句话记忆:程序描述相对工件的目标,运动学把目标换算成各关节应该怎样动。')
|
||||
|
||||
css = r'''
|
||||
@page { size: 338.6667mm 190.5mm; margin: 0; }
|
||||
* { box-sizing: border-box; }
|
||||
html,body { margin:0; padding:0; background:#dce3e7; color:#23303a; font-family:"Noto Sans CJK SC","Noto Sans CJK JP",sans-serif; }
|
||||
.slide { width:1280px; height:720px; padding:34px 54px 28px; background:#fff; position:relative; overflow:hidden; break-after:page; page-break-after:always; }
|
||||
.slide:last-child { break-after:auto; page-break-after:auto; }
|
||||
.chapter { color:#00877b; font-size:15px; margin:0 0 10px; font-weight:600; }
|
||||
h1 { font-size:36px; font-weight:700; line-height:1.3; margin:0 0 24px; letter-spacing:0; }
|
||||
h3 { font-size:23px; line-height:1.4; margin:0 0 14px; color:#00877b; }
|
||||
p { font-size:22px; line-height:1.55; margin:0 0 17px; }
|
||||
.body { height:498px; overflow:hidden; }
|
||||
.cols { display:grid; grid-template-columns:1fr 1fr; gap:36px; }
|
||||
.image-right { grid-template-columns: .95fr 1.1fr; gap:24px; }
|
||||
.tight p { margin-bottom:0; font-size:20px; }
|
||||
.small { font-size:17px; line-height:1.6; color:#53636d; }
|
||||
.intro { font-size:22px; padding-bottom:8px; }
|
||||
.takeaway { position:absolute; left:54px; right:54px; bottom:62px; background:#eaf5f2; border-left:5px solid #00877b; padding:10px 15px; font-size:19px; line-height:1.5; }
|
||||
.footer { position:absolute; bottom:20px; left:54px; right:54px; display:flex; align-items:flex-end; justify-content:space-between; gap:20px; color:#6b7880; font-size:12px; line-height:1.5; border-top:1px solid #d8e1e6; padding-top:8px; }
|
||||
.footer .source { max-width:1030px; }
|
||||
.page-number { font-size:16px; color:#00877b; white-space:nowrap; }
|
||||
figure { margin:0; min-width:0; }
|
||||
figure img { width:100%; height:380px; object-fit:contain; display:block; }
|
||||
figcaption { font-size:14px; line-height:1.5; margin-top:5px; color:#617079; text-align:center; }
|
||||
.large-diagram img { height:306px; }
|
||||
.large-diagram { margin-bottom:15px; }
|
||||
.wide-diagram img { height:280px; }
|
||||
.model-pair img { height:373px; }
|
||||
.model-pair { margin-bottom:18px; }
|
||||
table { border-collapse:collapse; width:100%; margin:8px 0 24px; font-size:19px; line-height:1.55; }
|
||||
th { background:#eaf0f3; font-weight:600; text-align:left; border-bottom:2px solid #a5b8c1; padding:12px 14px; }
|
||||
td { padding:13px 14px; border-bottom:1px solid #d8e1e6; vertical-align:top; }
|
||||
td:first-child { font-weight:600; color:#2563b8; }
|
||||
.compact { font-size:18px; }
|
||||
.compact td,.compact th { padding:9px 12px; }
|
||||
.equation { text-align:center; background:#f1f5f7; padding:14px 18px; font-size:26px; line-height:1.5; margin:14px 0 20px; }
|
||||
.small-eq { font-size:23px; }
|
||||
.flow { display:flex; gap:12px; align-items:center; margin:8px 0 25px; font-size:18px; }
|
||||
.flow span { flex:1; background:#edf4f5; padding:13px 8px; text-align:center; }
|
||||
.flow i { color:#00877b; font-size:25px; font-style:normal; }
|
||||
.pair { display:flex; justify-content:space-around; }
|
||||
.pair p { color:#00877b; }
|
||||
.number { font-size:23px; color:#d45549; }
|
||||
.blue { color:#2563b8; } .teal { color:#00877b; } .red { color:#d45549; }
|
||||
pre { font-family:"DejaVu Sans Mono","Noto Sans Mono CJK SC",monospace; white-space:pre-wrap; background:#edf2f5; border-left:4px solid #2563b8; padding:18px; font-size:21px; line-height:1.8; margin:12px 0 20px; }
|
||||
.math { font-family:"Noto Sans CJK SC",sans-serif; font-size:23px; line-height:1.65; }
|
||||
ul,ol { margin:8px 0 14px; padding-left:29px; font-size:22px; line-height:1.6; }
|
||||
li { margin-bottom:12px; }
|
||||
.steps { font-size:21px; line-height:1.6; }
|
||||
.steps li { padding-left:6px; margin-bottom:15px; }
|
||||
.exercises,.answers { font-size:22px; line-height:1.65; }
|
||||
.exercises li,.answers li { margin-bottom:19px; padding-left:7px; }
|
||||
.vertical-flow { display:flex; flex-direction:column; align-items:center; gap:4px; font-size:22px; }
|
||||
.vertical-flow span { padding:12px 16px; background:#f0f5f7; width:100%; text-align:center; }
|
||||
.vertical-flow b { color:#00877b; }
|
||||
.references p { font-size:18px; line-height:1.6; margin-bottom:13px; }
|
||||
.references h3 { margin-bottom:10px; }
|
||||
a { color:#136fb0; text-decoration:none; }
|
||||
sub { font-size:.7em; }
|
||||
.cover h1 { font-size:48px; margin-top:8px; margin-bottom:18px; }
|
||||
.cover-grid { display:grid; grid-template-columns:1.12fr .88fr; gap:55px; }
|
||||
.cover .subtitle { font-size:30px; color:#00877b; line-height:1.4; margin-bottom:22px; }
|
||||
.cover-model img { height:402px; background:#242a2a; }
|
||||
.rule { width:85px; height:5px; background:#d45549; margin:25px 0; }
|
||||
.badge { color:#2563b8; font-size:17px; margin-top:21px; }
|
||||
.slide[data-page="12"] figure img { height:345px; }
|
||||
.slide[data-page="12"] .cols p { margin-bottom:12px; }
|
||||
.slide[data-page="12"] .equation { margin:10px 0 14px; padding:10px 14px; }
|
||||
.slide[data-page="30"] .equation { margin:8px 0; padding:8px 14px; font-size:24px; }
|
||||
.slide[data-page="30"] table { margin-bottom:14px; }
|
||||
.slide[data-page="30"] p { margin-bottom:10px; }
|
||||
@media screen { .slide { margin:24px auto; box-shadow:0 3px 14px #0002; } }
|
||||
@media print { html,body { background:#fff; } .slide { margin:0; box-shadow:none; } * { -webkit-print-color-adjust:exact; print-color-adjust:exact; } }
|
||||
'''
|
||||
# Avoid exotic script glyphs in equations so CJK PDF fonts remain reliable.
|
||||
parts = []
|
||||
for index, slide in enumerate(slides, 1):
|
||||
body = slide['body'].replace('q𝓏','q_z').replace('qₓ','q_x').replace('qᵧ','q_y')
|
||||
parts.append(f'''<section class="slide {slide['cls']}" data-page="{index}">
|
||||
<div class="chapter">{slide['chapter']}</div><h1>{slide['title']}</h1>
|
||||
<div class="body">{body}</div>
|
||||
<div class="takeaway">{slide['takeaway']}</div>
|
||||
<footer class="footer"><span class="source">{slide['sources'] or '五轴机床与 RTCP · 原创教学示意 · 以本课双转台结构为限'}</span><span class="page-number">{index:02d} / {len(slides):02d}</span></footer>
|
||||
</section>''')
|
||||
document = '<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>五轴机床与 RTCP:从各轴独立到刀尖跟随</title><style>'+css+'</style></head><body>'+''.join(parts)+'</body></html>'
|
||||
(OUT / 'course.html').write_text(document)
|
||||
(OUT / 'outline.json').write_text(json.dumps([{'page':i+1,'chapter':s['chapter'],'title':s['title']} for i,s in enumerate(slides)], ensure_ascii=False,indent=2)+'\n')
|
||||
print(f'Built {len(slides)} teaching pages:', OUT / 'course.html')
|
||||
Reference in New Issue
Block a user