Files
linuxcncWebWASM/scripts/generate_rtcp_teaching_figures.py

168 lines
9.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Original teaching diagrams. Requires matplotlib; no application code changes."""
import math
from pathlib import Path
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib import font_manager
from matplotlib.patches import Circle, FancyArrowPatch, Rectangle
import numpy as np
from PIL import Image, ImageChops
ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / 'docs/teaching/rtcp-five-axis/assets'
OUT.mkdir(parents=True, exist_ok=True)
font_manager.fontManager.addfont('/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc')
plt.rcParams.update({'font.family': 'Noto Sans CJK JP', 'font.size': 15, 'axes.unicode_minus': False})
BLUE, TEAL, RED, GRAY, INK = '#2563b8', '#00877b', '#d45549', '#8a969f', '#23303a'
def save(fig, name):
path = OUT / f'{name}.png'
fig.savefig(path, dpi=190, facecolor='white', bbox_inches='tight', pad_inches=.18)
plt.close(fig)
if name == 'bc-frame':
image = Image.open(path).convert('RGB')
ink = ImageChops.difference(image, Image.new('RGB', image.size, 'white')).convert('L').point(lambda v: 255 if v > 8 else 0)
left, top, right, bottom = ink.getbbox()
image.crop((max(0,left-25),max(0,top-25),min(image.width,right+25),min(image.height,bottom+25))).save(path)
def axis2(ax, limit=125):
ax.set(xlim=(-30, limit), ylim=(-30, limit), aspect='equal')
ax.axis('off')
ax.annotate('', (limit-5, 0), (-22, 0), arrowprops={'arrowstyle': '-|>', 'color': BLUE, 'lw': 2})
ax.annotate('', (0, limit-5), (0, -22), arrowprops={'arrowstyle': '-|>', 'color': BLUE, 'lw': 2})
ax.text(limit-8, -12, '$X_M$', color=BLUE, ha='right')
ax.text(-12, limit-9, '$Y_M$', color=BLUE, va='top')
ax.text(-10, -13, 'O', color=INK)
def point(ax, x, y, label, color, offset=(7, 7), marker='o'):
ax.scatter([x], [y], s=150, c=color, marker=marker, zorder=5)
ax.annotate(label, (x, y), xytext=offset, textcoords='offset points', color=color, fontsize=14)
def arc(ax, radius, end=90):
angles = np.linspace(0, np.deg2rad(end), 101)
ax.plot(radius*np.cos(angles), radius*np.sin(angles), color=TEAL, lw=2, ls='--')
# The same marked workpiece point, before and after turning the C table.
for follow in [False, True]:
fig, axes = plt.subplots(1, 2, figsize=(10, 4.3))
for ax in axes: axis2(ax); ax.add_patch(Circle((0, 0), 100, fill=False, ec='#d8e1e6', lw=2))
axes[0].set_title('起点:C = 0°', pad=10, color=INK)
point(axes[0], 100, 0, '工件标记点 + 刀尖', RED, (-138, 17), '*')
axes[0].plot([0, 100], [0, 0], color=TEAL, lw=4)
axes[1].set_title('终点:C = 90°', pad=10, color=INK)
arc(axes[1], 100)
axes[1].plot([0, 0], [0, 100], color=TEAL, lw=4)
point(axes[1], 0, 100, '同一个工件点', TEAL, (12, -3), '*')
if follow:
axes[1].scatter([0], [100], s=380, facecolors='none', edgecolors=RED, linewidths=3)
axes[1].text(40, 45, '刀尖由 XYZ 移到这里\n相对工件仍是同一点', color=RED, ha='center')
else:
point(axes[1], 100, 0, '刀尖留在原位置', RED, (-125, -22))
axes[1].plot([0, 100], [100, 0], color=RED, lw=1.6, ls=':')
axes[1].text(43, 52, '相对位置改变', color=RED, rotation=-45, ha='center')
fig.subplots_adjust(wspace=.22)
save(fig, 'tcp-follow' if follow else 'identity-no-follow')
# Rotated workpiece axes while the machine axes stay fixed.
fig, ax = plt.subplots(figsize=(6.2, 5.5)); axis2(ax, 150)
origin = np.array([60., 45.]); theta = math.pi/4
u, v = np.array([math.cos(theta), math.sin(theta)]), np.array([-math.sin(theta), math.cos(theta)])
corners = [origin+u*a+v*b for a,b in [(-22,-22),(32,-22),(32,32),(-22,32),(-22,-22)]]
ax.fill(*np.array(corners).T, color='#e3f3ef', zorder=0)
for direction, label, offset in [(u, '$X_W$', (3,0)), (v, '$Y_W$', (-12,0))]:
end = origin+direction*70
ax.annotate('', end, origin, arrowprops={'arrowstyle':'-|>', 'color':TEAL,'lw':3})
ax.annotate(label,end,xytext=offset,textcoords='offset points',color=TEAL)
start = origin+u*13; end = start+u*40
ax.annotate('',end,start,arrowprops={'arrowstyle':'-|>','color':RED,'lw':4})
ax.plot([start[0],end[0],end[0]],[start[1],start[1],end[1]],ls=':',color=RED,lw=2)
ax.text(78,49,'+7.071',color=RED,fontsize=13)
ax.text(107,75,'+7.071',color=RED,fontsize=13)
ax.text(22,126,'C = 45°:工件 +X 方向倾斜',color=INK,fontsize=14)
ax.text(42,21,'W 原点',color=TEAL,fontsize=13)
save(fig,'rotated-direction')
# A point on a tilting BC trunnion; explicit right-hand matrix convention.
fig = plt.figure(figsize=(7.8,5.9)); ax=fig.add_subplot(111,projection='3d')
b,c=np.deg2rad([30,45]); ry=np.array([[np.cos(b),0,np.sin(b)],[0,1,0],[-np.sin(b),0,np.cos(b)]])
rz=np.array([[np.cos(c),-np.sin(c),0],[np.sin(c),np.cos(c),0],[0,0,1]])
r=ry@rz; p=np.array([100,0,20]); q=r@p
for idx,label in enumerate(['$X_M$','$Y_M$','$Z_M$']):
e=np.eye(3)[idx]*110;ax.quiver(0,0,0,*e,color=BLUE,arrow_length_ratio=.07,lw=1.5);ax.text(*e,label,color=BLUE,fontsize=13)
for idx,label in enumerate(['$X_W$','$Y_W$','$Z_W$']):
e=r[:,idx]*75;ax.quiver(0,0,0,*e,color=TEAL,arrow_length_ratio=.09,lw=2.5);ax.text(*(e*1.13),label,color=TEAL,fontsize=15)
phi=np.linspace(0,2*np.pi,150);ring=r@np.array([100*np.cos(phi),100*np.sin(phi),np.zeros(len(phi))]);ax.plot(*ring,color=TEAL,alpha=.45)
ax.scatter(*q,color=RED,s=65);ax.plot([0,q[0]],[0,q[1]],[0,q[2]],color=RED,lw=2)
ax.text(*(q+np.array([0,0,9])),'刀尖目标',color=RED,fontsize=14)
ax.set(xlim=(-110,130),ylim=(-110,130),zlim=(-65,120));ax.set_box_aspect((1,1,.8));ax.view_init(elev=23,azim=-62);ax.set_axis_off()
save(fig,'bc-frame')
# Parent-child hierarchy, two separate mechanical branches.
fig,ax=plt.subplots(figsize=(11.2,4.5));ax.set(xlim=(0,11.2),ylim=(0,4.5));ax.axis('off')
def node(x,y,label,color=BLUE,w=1.6):
ax.add_patch(Rectangle((x,y),w,.65,fc='#f1f6f8',ec=color,lw=1.8))
ax.text(x+w/2,y+.325,label,ha='center',va='center',color=color,fontsize=15)
def link(a,b,color=GRAY):
style = 'arc3,rad=0' if abs(a[1]-b[1]) < 1e-9 else 'angle,angleA=0,angleB=90,rad=8'
ax.annotate('',b,a,arrowprops={'arrowstyle':'-|>','color':color,'lw':2,'connectionstyle':style})
node(.3,1.9,'机床底座',INK,1.8)
for x,label in [(3,'X 轴'),(5.5,'Y 轴'),(8,'Z 轴 + 刀具')]:node(x,3,label,BLUE,2.4 if x==8 else 1.6)
for x,label in [(3,'A 或 B 轴'),(6,'C 轴 + 工件')]:node(x,.8,label,TEAL,2.2)
link((2.1,2.2),(3,3.325),BLUE);link((4.6,3.325),(5.5,3.325),BLUE);link((7.1,3.325),(8,3.325),BLUE)
link((2.1,2.2),(3,1.125),TEAL);link((5.2,1.125),(6,1.125),TEAL)
ax.text(5.8,4.08,'刀具侧:直线运动链',ha='center',color=BLUE,fontsize=17)
ax.text(6,.22,'工件侧:倾转轴带着 C 转台一起运动',ha='center',color=TEAL,fontsize=16)
save(fig,'hierarchy')
# Offset pivot: the same 90-degree rotation produces different positions.
fig,axes=plt.subplots(1,2,figsize=(10.2,4.3))
for ax in axes: axis2(ax,145)
for ax, o, title in [(axes[0],np.array([0,0]),'旋转中心 O = (0, 0)'),(axes[1],np.array([30,20]),'旋转中心 O = (30, 20)')]:
point(ax,*o,'O',TEAL,(-12,-20));start=np.array([100,20]);end=o+np.array([-(start-o)[1],(start-o)[0]])
ax.plot([o[0],start[0]],[o[1],start[1]],color=GRAY,ls='--');ax.plot([o[0],end[0]],[o[1],end[1]],color=TEAL,lw=2)
point(ax,*start,'起点 (100, 20)',GRAY,(-94,-22));point(ax,*end,f'终点 ({end[0]}, {end[1]})',RED,(8,4))
ax.set_title(title,color=INK,pad=14,fontsize=16)
save(fig,'pivot-offset')
# Correct TCP arc versus independently interpolated joint chord.
fig,ax=plt.subplots(figsize=(6.5,5.2));axis2(ax,125)
t=np.linspace(0,np.pi/2,121);ax.plot(100*np.cos(t),100*np.sin(t),color=TEAL,lw=3)
ax.plot([100,0],[0,100],color=RED,lw=2.5)
q=100/np.sqrt(2);ax.scatter([q,50],[q,50],color=[TEAL,RED],s=95)
ax.plot([50,q],[50,q],color=INK,ls=':',lw=2)
ax.text(67,84,'RTCP:跟随工件点的圆弧',color=TEAL,ha='center',fontsize=14)
ax.text(35,33,'非 RTCP:独立轴线性插补',color=RED,ha='center',fontsize=14)
ax.annotate('中途偏离 29.289 mm',(61,61),xytext=(4,113),arrowprops={'arrowstyle':'->','color':INK},fontsize=14)
save(fig,'arc-chord')
# Tilted table and fixed vertical spindle: tool vector relative to workpiece changes.
fig,axes=plt.subplots(1,2,figsize=(10.2,4.1))
for ax,angle in zip(axes,[0,30]):
ax.set(xlim=(-70,70),ylim=(-40,105),aspect='equal');ax.axis('off')
phi=np.deg2rad(angle);u=np.array([np.cos(phi),-np.sin(phi)]);v=np.array([np.sin(phi),np.cos(phi)])
ends=np.array([-60*u,60*u]);ax.plot(*ends.T,color=TEAL,lw=7)
ax.add_patch(Rectangle((-9,58),18,35,fc='#b5c1c9',ec=INK,lw=1.5))
ax.plot([0,0],[6,58],color=RED,lw=5);ax.scatter([0],[6],color=RED,s=45)
ax.annotate('',(0,32),(0,78),arrowprops={'arrowstyle':'-|>','lw':2,'color':BLUE})
ax.annotate('',tuple(v*57),(0,0),arrowprops={'arrowstyle':'-|>','lw':2,'color':TEAL})
ax.text(18,80,'主轴方向固定',color=BLUE,fontsize=14);ax.text(*(v*62),'工件法向',color=TEAL,fontsize=14)
ax.set_title(f'B = {angle}°',color=INK,pad=10)
save(fig,'orientation')
# Tool length: preserving spindle reference alone does not preserve the tip.
fig,ax=plt.subplots(figsize=(8.2,4));ax.set(xlim=(0,10),ylim=(0,5));ax.axis('off')
for x,length,label in [(1.7,1.1,'短刀'),(5,2.0,'长刀,主轴未补偿'),(8.3,2.0,'长刀,主轴上移')]:
gauge=3.7 if x<7 else 4.6
ax.add_patch(Rectangle((x-.45,gauge),.9,.3,fc='#bac8ce',ec=INK))
ax.plot([x,x],[gauge-length,gauge],color=RED,lw=8)
ax.scatter([x],[gauge-length],color=RED,s=70)
ax.text(x,.28,label,ha='center',fontsize=14,color=INK)
ax.text(x+.24,gauge-length/2,'L',color=RED,fontsize=17)
ax.axhline(2.6,color=TEAL,ls='--',lw=2);ax.text(.1,2.77,'目标刀尖高度',color=TEAL,fontsize=13)
ax.annotate('',(8.9,4.6),(8.9,3.7),arrowprops={'arrowstyle':'-|>','color':BLUE,'lw':2});ax.text(9.05,4.1,'ΔL',color=BLUE)
save(fig,'tool-length')
print('Created 9 original diagrams in',OUT)