399 行
17 KiB
Python
399 行
17 KiB
Python
# -*- coding: utf-8 -*-
|
||
u"""把载板 PCB2 导成 **1:1 打印用的尺寸图 PDF**(拿真主板去比孔位 / 外形)。
|
||
|
||
/c/Python314/python .claude/pcb2_pdf.py # 读实盘几何 -> 出 PDF
|
||
/c/Python314/python .claude/pcb2_pdf.py --offline # 不连桥接,用上次的 _tmp_mech_dump.json
|
||
|
||
几何**全部从客户端实读**(板框多段线 layer 11、槽 layer 12 填充、
|
||
定位孔 / M3 的自由焊盘、主板投影丝印 layer 3);pcb2_place.py 的标称值只用来
|
||
交叉校验和写标注 —— 图上画的永远是实盘那一份。
|
||
|
||
## 三页
|
||
|
||
1. **A4 横向**:整块载板 1:1(板框 / 5 个主板投影 / 35 个 Φ2.5 定位孔 / M3 / 穿线槽)
|
||
2. **A4 纵向**:单个主板位 1:1 详图,两种朝向各一份(CH4/CH5 与 CH1~CH3)
|
||
3. **A4 纵向**:35 个孔的载板绝对坐标表 + 复核步骤
|
||
|
||
## 两个坑
|
||
|
||
- **字体**:`fitz` 内置的 `china-s` 会把 ASCII 排成全角,一行就撑出纸外。
|
||
这里嵌 `msyh.ttc`(微软雅黑)走 `TextWriter`,顺带能算真实文字宽度做换行。
|
||
- **打印**:必须选「实际大小 / 100%」。前两页各印了一条 100 mm 标尺,
|
||
先量标尺再量孔 —— 量出来不是 100.0 就是被缩放了,图别用。
|
||
"""
|
||
import json
|
||
import math
|
||
import os
|
||
import sys
|
||
|
||
sys.stdout.reconfigure(encoding='utf-8')
|
||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||
sys.path.insert(0, HERE)
|
||
|
||
import fitz
|
||
import pcb2_place as P
|
||
|
||
MIL = 39.3700787401575
|
||
PT = 72.0 / 25.4 # mm -> pt
|
||
DUMP = os.path.join(HERE, '_tmp_mech_dump.json')
|
||
OUT = os.path.join(os.path.dirname(HERE), 'shots', 'PCB2-carrier-1to1.pdf')
|
||
FONTFILE = 'C:/Windows/Fonts/msyh.ttc'
|
||
|
||
GRAY = (0.45, 0.45, 0.45)
|
||
RED = (0.75, 0, 0)
|
||
|
||
JS = u"""
|
||
await eda.dmt_EditorControl.openDocument('%s');
|
||
await new Promise(r=>setTimeout(r,1500));
|
||
const out = {pads: [], poly: [], fill: [], line3: []};
|
||
for (const p of await eda.pcb_PrimitivePad.getAll()) {
|
||
const num = p.getState_PadNumber();
|
||
let parent = null; try { parent = p.getState_ParentComponentPrimitiveId(); } catch(e) {}
|
||
if (parent) continue;
|
||
if (typeof num === 'string' && (/^P[1-5]H[1-7]$/.test(num) || /^MH\\d+$/.test(num)))
|
||
out.pads.push([num, p.getState_X(), p.getState_Y(), p.getState_Pad(), p.getState_Hole(), p.getState_Net()]);
|
||
}
|
||
for (const p of await eda.pcb_PrimitivePolyline.getAll())
|
||
if (p.getState_Layer() === 11) out.poly.push(p.getState_Polygon());
|
||
for (const f of await eda.pcb_PrimitiveFill.getAll())
|
||
if (f.getState_Layer() === 12) out.fill.push(f.getState_ComplexPolygon());
|
||
for (const l of await eda.pcb_PrimitiveLine.getAll())
|
||
if (l.getState_Layer() === 3)
|
||
out.line3.push([l.getState_StartX(), l.getState_StartY(), l.getState_EndX(), l.getState_EndY()]);
|
||
return out;
|
||
""" % P.PCB
|
||
|
||
|
||
# ---------------------------------------------------------------- 读几何
|
||
def fetch():
|
||
from eda import run
|
||
r = run(JS, timeout=180)
|
||
if not r.get('success'):
|
||
sys.exit(u'读几何失败: ' + json.dumps(r, ensure_ascii=False)[:400])
|
||
json.dump(r['result'], open(DUMP, 'w', encoding='utf-8'), ensure_ascii=False)
|
||
return r['result']
|
||
|
||
|
||
def poly_pts(src, step=2.0):
|
||
u"""EasyEDA 多边形源数组 -> [(x_mm, y_mm), ...],圆弧按 step 度采样。
|
||
|
||
源格式:[x0, y0, ('L', x, y | 'ARC', 角度, x, y)...],角度**逆时针为正**(y 向上)。
|
||
"""
|
||
if isinstance(src, dict):
|
||
src = src.get('polygon', src.get('outline'))
|
||
if src and isinstance(src[0], (list, dict)): # 复合多边形:外环在前
|
||
return poly_pts(src[0], step)
|
||
x, y = src[0] / MIL, src[1] / MIL
|
||
pts = [(x, y)]
|
||
i = 2
|
||
while i < len(src):
|
||
op = src[i]
|
||
if op == 'L':
|
||
x, y = src[i + 1] / MIL, src[i + 2] / MIL
|
||
pts.append((x, y))
|
||
i += 3
|
||
elif op == 'ARC':
|
||
ang = float(src[i + 1])
|
||
ex, ey = src[i + 2] / MIL, src[i + 3] / MIL
|
||
pts += arc_pts((x, y), (ex, ey), ang, step)
|
||
x, y = ex, ey
|
||
i += 4
|
||
else: # 少见:直接跟一对坐标
|
||
x, y = src[i] / MIL, src[i + 1] / MIL
|
||
pts.append((x, y))
|
||
i += 2
|
||
return pts
|
||
|
||
|
||
def arc_pts(a, b, ang, step=2.0):
|
||
u"""从 a 到 b、扫过 ang 度(逆时针为正)的圆弧采样点(不含起点)。"""
|
||
ax, ay = a
|
||
bx, by = b
|
||
vx, vy = bx - ax, by - ay
|
||
L = math.hypot(vx, vy)
|
||
half = math.radians(ang) / 2.0
|
||
if L < 1e-9 or abs(math.tan(half)) < 1e-9:
|
||
return [b]
|
||
h = (L / 2.0) / math.tan(half)
|
||
cx = (ax + bx) / 2.0 - vy / L * h
|
||
cy = (ay + by) / 2.0 + vx / L * h
|
||
r = math.hypot(ax - cx, ay - cy)
|
||
a0 = math.atan2(ay - cy, ax - cx)
|
||
n = max(2, int(abs(ang) / step) + 1)
|
||
return [(cx + r * math.cos(a0 + math.radians(ang) * k / n),
|
||
cy + r * math.sin(a0 + math.radians(ang) * k / n)) for k in range(1, n + 1)]
|
||
|
||
|
||
# ---------------------------------------------------------------- 画布
|
||
FONT = fitz.Font(fontfile=FONTFILE)
|
||
|
||
|
||
class Sheet(object):
|
||
u"""一页纸:图纸 mm(y 向上)-> PDF 点(y 向下)。ox/oy 是图纸原点在纸上的位置,
|
||
可以中途 `origin()` 改,用来在一页上摆几个坐标块。"""
|
||
|
||
def __init__(self, doc, w_mm, h_mm, ox=0.0, oy=0.0):
|
||
self.page = doc.new_page(width=w_mm * PT, height=h_mm * PT)
|
||
self.h = h_mm
|
||
self.w = w_mm
|
||
self.ox, self.oy = ox, oy
|
||
self.sh = self.page.new_shape()
|
||
self.tw = fitz.TextWriter(self.page.rect)
|
||
self.colors = [] # [(color, [(point, text, size)...])]
|
||
|
||
def origin(self, ox, oy):
|
||
self.ox, self.oy = ox, oy
|
||
|
||
def p(self, x, y):
|
||
return fitz.Point((x + self.ox) * PT, (self.h - (y + self.oy)) * PT)
|
||
|
||
# ---- 图元 ----
|
||
def line(self, x1, y1, x2, y2, w=0.35, color=(0, 0, 0), dash=None):
|
||
self.sh.draw_line(self.p(x1, y1), self.p(x2, y2))
|
||
self.sh.finish(width=w, color=color, dashes=dash)
|
||
|
||
def polyline(self, pts, w=0.35, color=(0, 0, 0), dash=None, close=True):
|
||
q = [self.p(*t) for t in pts]
|
||
for i in range(len(q) - 1):
|
||
self.sh.draw_line(q[i], q[i + 1])
|
||
if close and q[0] != q[-1]:
|
||
self.sh.draw_line(q[-1], q[0])
|
||
self.sh.finish(width=w, color=color, dashes=dash)
|
||
|
||
def rect(self, x0, y0, x1, y1, w=0.35, color=(0, 0, 0), dash=None):
|
||
self.polyline([(x0, y0), (x1, y0), (x1, y1), (x0, y1)], w, color, dash)
|
||
|
||
def circle(self, x, y, d, w=0.35, color=(0, 0, 0), dash=None):
|
||
self.sh.draw_circle(self.p(x, y), d / 2.0 * PT)
|
||
self.sh.finish(width=w, color=color, dashes=dash)
|
||
|
||
def cross(self, x, y, arm=1.6, w=0.25, color=(0, 0, 0)):
|
||
self.line(x - arm, y, x + arm, y, w, color)
|
||
self.line(x, y - arm, x, y + arm, w, color)
|
||
|
||
# ---- 文字(嵌微软雅黑,走 TextWriter,宽度可算)----
|
||
def text(self, x, y, s, size=7.0, color=(0, 0, 0), anchor='l'):
|
||
pt = self.p(x, y)
|
||
if anchor == 'c':
|
||
pt = fitz.Point(pt.x - FONT.text_length(s, size) / 2.0, pt.y)
|
||
elif anchor == 'r':
|
||
pt = fitz.Point(pt.x - FONT.text_length(s, size), pt.y)
|
||
for c, items in self.colors:
|
||
if c == color:
|
||
items.append((pt, s, size))
|
||
break
|
||
else:
|
||
self.colors.append((color, [(pt, s, size)]))
|
||
|
||
def para(self, x, y, s, size=7.0, color=(0, 0, 0), width=None, lead=4.2):
|
||
u"""按纸面宽度自动折行,返回下一行的 y。"""
|
||
width = width or (self.w - 2 * self.ox - 2.0)
|
||
line, out = u'', []
|
||
for ch in s:
|
||
if FONT.text_length(line + ch, size) > width * PT and line:
|
||
out.append(line)
|
||
line = ch
|
||
else:
|
||
line += ch
|
||
out.append(line)
|
||
for t in out:
|
||
self.text(x, y, t, size, color)
|
||
y -= lead
|
||
return y
|
||
|
||
def done(self):
|
||
self.sh.commit()
|
||
for color, items in self.colors:
|
||
tw = fitz.TextWriter(self.page.rect)
|
||
for pt, s, size in items:
|
||
tw.append(pt, s, font=FONT, fontsize=size)
|
||
tw.write_text(self.page, color=color)
|
||
|
||
|
||
def scalebar(s, x, y, length=100.0):
|
||
u"""带 10 mm 刻度的标尺,用来验证打印没被缩放。"""
|
||
s.line(x, y, x + length, y, 0.5)
|
||
for k in range(0, int(length) + 1, 10):
|
||
s.line(x + k, y, x + k, y + (4.0 if k % 50 == 0 else 2.5), 0.5)
|
||
if k % 50 == 0:
|
||
s.text(x + k, y + 5.2, '%d' % k, 6, anchor='c')
|
||
s.text(x, y - 3.6, u'标尺:这一段必须量出 %.0f mm,量不出就是打印被缩放了' % length, 7)
|
||
|
||
|
||
NOTE = (u'打印必须选「实际大小 / 100%」,不能选「适合页面」「缩小以适合可打印区域」。'
|
||
u'印完先量标尺,再量孔。')
|
||
|
||
|
||
# ---------------------------------------------------------------- 第 1 页:整板
|
||
def page_full(doc, geo, holes, mh):
|
||
s = Sheet(doc, 297.0, 210.0, (297.0 - P.BW) / 2.0, (210.0 - P.BH) / 2.0 + 6.0)
|
||
for src in geo['poly']:
|
||
s.polyline(poly_pts(src), 0.5)
|
||
for src in geo['fill']:
|
||
s.polyline(poly_pts(src), 0.35, (0.55, 0.55, 0.55))
|
||
for x1, y1, x2, y2 in geo['line3']:
|
||
s.line(x1 / MIL, y1 / MIL, x2 / MIL, y2 / MIL, 0.3, GRAY, dash='[1.5 1.5] 0')
|
||
for name, (x, y, d) in sorted(mh.items()):
|
||
s.circle(x, y, P.MH_PAD, 0.25, GRAY, dash='[1 1] 0')
|
||
s.circle(x, y, d, 0.5)
|
||
s.cross(x, y, 2.2, 0.25)
|
||
s.text(x + 3.6, y + 1.0, name, 5.5)
|
||
for name, (x, y, d) in sorted(holes.items()):
|
||
s.circle(x, y, d, 0.5)
|
||
s.cross(x, y, 2.0, 0.25)
|
||
for n, (x0, y0, rho) in sorted(P.SITES.items()):
|
||
s.text(x0 + P.PW / 2.0, y0 + P.PH / 2.0 - 1.0, u'CH%d 主板' % n, 8, (0.4, 0.4, 0.4), anchor='c')
|
||
for cx, cy in [(0, 0), (P.BW, 0), (0, P.BH), (P.BW, P.BH)]:
|
||
s.cross(cx, cy, 4.0, 0.4)
|
||
|
||
s.text(0, P.BH + 9.0, u'PhoneRail 载板 PCB2 · 1:1 尺寸图 · %.0f × %.0f mm(顶视)'
|
||
% (P.BW, P.BH), 11)
|
||
s.text(0, P.BH + 3.6,
|
||
u'主板定位孔 %d 个 Φ%.1f NPTH · M3 孔 %d 个 Φ%.1f(外圈虚线是 Φ%.1f 焊盘) · '
|
||
u'穿线槽 %d 条 · 灰虚线框 = 小米8 主板本体投影 %.2f × %.2f mm'
|
||
% (len(holes), P.HOLE_D, len(mh), P.MH_HOLE, P.MH_PAD,
|
||
len(geo['fill']), P.PW, P.PH), 7)
|
||
scalebar(s, 0, -16.0)
|
||
y = s.para(0, -25.0, NOTE, 7, RED)
|
||
s.para(0, y - 1.0, u'主板按装配朝向(触点面朝下)平放到纸上比对;单站详图和坐标表见第 2 / 3 页。', 7)
|
||
s.done()
|
||
|
||
|
||
# ---------------------------------------------------------------- 第 2 页:单站详图
|
||
def site_block(s, ox, oy, flip, caption):
|
||
u"""一个主板位 1:1。flip=False 接口边朝下(CH4/CH5),True 转 180°(CH1~CH3)。"""
|
||
s.origin(ox, oy)
|
||
m = (lambda u, v: (P.PW - u, P.PH - v)) if flip else (lambda u, v: (u, v))
|
||
s.rect(0, 0, P.PW, P.PH, 0.5)
|
||
ey = P.PH - 1.5 if flip else 1.5
|
||
s.line(2.0, ey, P.PW - 2.0, ey, 0.4, GRAY, dash='[1.5 1.5] 0')
|
||
s.text(P.PW / 2.0, P.PH + 2.6 if flip else -4.0,
|
||
u'接口边(J600 电池座 / J1401 尾插在这一边)', 6, GRAY, anchor='c')
|
||
for hn, (u, v) in sorted(P.HOLES.items()):
|
||
x, y = m(u, v)
|
||
s.circle(x, y, P.HOLE_D, 0.5)
|
||
s.cross(x, y, 2.4, 0.25)
|
||
right = x < P.PW - 22.0 # 右边放不下就把标签甩到左边
|
||
up = y < P.PH - 5.0 # 顶边附近的标签改放下方,别压着板边
|
||
s.text(x + (3.0 if right else -3.0), y + (2.4 if up else -4.4),
|
||
'%s (%.1f, %.1f)' % (hn, u, v), 5.5, anchor='l' if right else 'r')
|
||
for i, ((u, v), tag) in enumerate(zip(P.POGO_XY, P.POGO_TAG)):
|
||
x, y = m(u, v)
|
||
s.circle(x, y, 1.2, 0.35, RED)
|
||
s.text(x + 1.2, y + 1.4, '%d' % (i + 1), 5.0, RED)
|
||
for cx, cy in [(0, 0), (P.PW, 0), (0, P.PH), (P.PW, P.PH)]:
|
||
s.cross(cx, cy, 4.0, 0.4)
|
||
s.text(0, -8.5, caption, 8)
|
||
|
||
|
||
def page_site(doc):
|
||
ox = (210.0 - P.PW) / 2.0
|
||
TX, TW = 18.0, 174.0 # 文字块的左边和折行宽度
|
||
s = Sheet(doc, 210.0, 297.0, TX, 0.0)
|
||
s.text(0, 280.0, u'小米8 主板位 · 1:1 详图(五站几何相同)', 11)
|
||
s.para(0, 274.0, u'本体 %.2f × %.2f mm(实测),原点 = 本体左下角,顶视,孔 Φ%.1f NPTH。'
|
||
u'把真主板按装配朝向(触点面朝下)压进框里:先看四条边,再看 7 个孔。'
|
||
% (P.PW, P.PH, P.HOLE_D), 7, width=TW)
|
||
|
||
site_block(s, ox, 213.0, False, u'① 接口边朝下 —— 载板上 CH4 / CH5 就是这个朝向')
|
||
site_block(s, ox, 143.0, True, u'② 转 180°,接口边朝上 —— 载板上 CH1 / CH2 / CH3 是这个朝向')
|
||
|
||
s.origin(TX, 0.0)
|
||
y = 126.0
|
||
s.text(0, y, u'红圈 = 侧键顶针触点(Φ1.2 示意),本体坐标:', 7); y -= 4.6
|
||
for i, ((u, v), tag) in enumerate(zip(P.POGO_XY, P.POGO_TAG)):
|
||
s.text(4.0, y, u'%d %s (%.2f, %.2f)' % (i + 1, tag, u, v), 6.5)
|
||
y -= 4.2
|
||
y -= 3.0
|
||
y = s.para(0, y, u'⚠ 孔位是从用户标注的照片提取的,标称公差 ±0.5 mm;本体尺寸 %.2f × %.2f 是卡尺实测。'
|
||
u'这张图就是拿来把那 ±0.5 消掉的。' % (P.PW, P.PH), 7, RED, width=TW)
|
||
y = s.para(0, y - 1.0, u'孔对不上就量出真孔的本体坐标 (u, v),填回 .claude/mi8_mech.json 的 holes,'
|
||
u'再跑 pcb2_place.py --mech 重打那 35 个孔(其它布局不动)。', 7, width=TW)
|
||
scalebar(s, 0, y - 14.0)
|
||
s.para(0, y - 23.0, NOTE, 7, RED, width=TW)
|
||
s.done()
|
||
|
||
|
||
# ---------------------------------------------------------------- 第 3 页:坐标表
|
||
def page_table(doc, holes, mh, dev):
|
||
s = Sheet(doc, 210.0, 297.0, 18.0, 0.0)
|
||
y = 278.0
|
||
s.text(0, y, u'载板绝对坐标表(mm,原点 = 板左下角,顶视)', 11); y -= 8
|
||
y = s.para(0, y, u'主板本体坐标 (u, v):' + u' '.join(
|
||
'%s(%.1f, %.1f)' % (k, v[0], v[1]) for k, v in sorted(P.HOLES.items())), 6.5, lead=4.0)
|
||
y = s.para(0, y, u'站位左下角 / 旋转:' + u' '.join(
|
||
'CH%d(%.2f, %.2f, %d°)' % (n, a, b, r) for n, (a, b, r) in sorted(P.SITES.items())),
|
||
6.5, lead=4.0)
|
||
y = s.para(0, y, u'实盘孔位与 pcb2_place.py 标称值最大偏差 %.3f mm;孔 Φ%.1f NPTH。'
|
||
% (dev, P.HOLE_D), 6.5, lead=4.0)
|
||
y -= 5
|
||
|
||
cols = [0, 36, 72, 108, 144]
|
||
rows = sorted(holes.items())
|
||
per = (len(rows) + len(cols) - 1) // len(cols)
|
||
top = y
|
||
for i, (name, (x, yy, d)) in enumerate(rows):
|
||
s.text(cols[i // per], top - (i % per) * 5.2,
|
||
'%s %7.2f %7.2f' % (name, x, yy), 6.5)
|
||
y = top - per * 5.2 - 9
|
||
|
||
s.text(0, y, u'载板自己的 M3 孔 Φ%.1f(金属化,接 GND,焊盘 Φ%.1f)' % (P.MH_HOLE, P.MH_PAD), 9)
|
||
y -= 6
|
||
for i, (name, (x, yy, d)) in enumerate(sorted(mh.items())):
|
||
s.text(cols[i % 4], y - (i // 4) * 5.2, '%s %7.2f %7.2f' % (name, x, yy), 6.5)
|
||
y -= ((len(mh) + 3) // 4) * 5.2 + 9
|
||
|
||
s.text(0, y, u'复核步骤', 9); y -= 6
|
||
for t in [u'1. A4 打印第 1 / 2 页,先量那条 100 mm 标尺 —— 不是 100.0 就重印。',
|
||
u'2. 真主板按装配朝向(触点面朝下)压进第 2 页的框里,先看四条边,再看 7 个孔。',
|
||
u'3. 对不上就量出真孔的本体坐标 (u, v),填回 .claude/mi8_mech.json 的 holes,'
|
||
u'再跑 pcb2_place.py --mech 重打这 35 个孔。',
|
||
u'4. 顺带量孔径:现在按 Φ%.1f 孔 / M2.5 尼龙立柱做的(金属立柱会把五台手机的地并起来)。'
|
||
% P.HOLE_D]:
|
||
y = s.para(4.0, y, t, 7, lead=4.6) - 1.0
|
||
s.done()
|
||
|
||
|
||
# ---------------------------------------------------------------- main
|
||
def main():
|
||
geo = json.load(open(DUMP, encoding='utf-8')) if '--offline' in sys.argv else fetch()
|
||
|
||
holes, mh = {}, {}
|
||
for name, x, y, pad, hole, net in geo['pads']:
|
||
rec = (round(x / MIL, 3), round(y / MIL, 3), round(hole[1] / MIL, 3))
|
||
(mh if name.startswith('MH') else holes)[name] = rec
|
||
|
||
dev = 0.0 # 实盘孔位 vs pcb2_place.py 标称
|
||
for n in P.SITES:
|
||
for hn, (u, v) in P.HOLES.items():
|
||
bx, by, _ = P.to_board(n, u, v)
|
||
hx, hy, _ = holes['P%d%s' % (n, hn)]
|
||
dev = max(dev, math.hypot(bx - hx, by - hy))
|
||
|
||
print(u'实盘:定位孔 %d 个 Φ%s · M3 %d 个 Φ%s · 板框 %d 条 · 槽 %d 个 · 投影丝印 %d 段'
|
||
% (len(holes), sorted(set(v[2] for v in holes.values())),
|
||
len(mh), sorted(set(v[2] for v in mh.values())),
|
||
len(geo['poly']), len(geo['fill']), len(geo['line3'])))
|
||
print(u'孔位与标称值最大偏差 %.4f mm' % dev)
|
||
if len(holes) != 35:
|
||
print(u'!! 定位孔不是 35 个')
|
||
if len(mh) != len(P.MH):
|
||
print(u'!! 板上 M3 孔 %d 个,pcb2_place.py 的 MH 表有 %d 个 —— 图按实盘画'
|
||
% (len(mh), len(P.MH)))
|
||
|
||
doc = fitz.open()
|
||
page_full(doc, geo, holes, mh)
|
||
page_site(doc)
|
||
page_table(doc, holes, mh, dev)
|
||
doc.set_metadata({'title': 'PhoneRail 载板 PCB2 1:1 尺寸图',
|
||
'subject': u'220x135 载板 / 35 个小米8 主板定位孔 / 1:1 打印核对'})
|
||
if not os.path.isdir(os.path.dirname(OUT)):
|
||
os.makedirs(os.path.dirname(OUT))
|
||
doc.subset_fonts()
|
||
doc.save(OUT, garbage=4, deflate=True)
|
||
print(u'写出 %s(%d 页,%.0f KB)' % (OUT, doc.page_count, os.path.getsize(OUT) / 1024.0))
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main()
|