# -*- coding: utf-8 -*- """ gen-manual-placeholders.py —— 为新系列生成手册占位文件夹与占位图片 (尺寸图窗口占位用) 背景: 老 11 个系列有手册拆页 (catalog\\manual\\<系列>\\pXX.png), 新 6 个系列 (UCBM/ULP/USP/UGP/UAGP/LAE) 暂无手册 → 尺寸图窗口空白。 本脚本为每个新系列生成一张占位 PNG (A4 比例, 灰框+系列代码大字) 并写入 series\\<系列>.meta.json 的 manual 附件引用。真实手册到货后用 render-manual-pages.py 重新拆页覆盖 p01.png 即可。 纯标准库实现 (zlib+struct 写 PNG, 5x7 点阵字体), 无任何第三方依赖。 用法: python gen-manual-placeholders.py """ import json import os import struct import zlib BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) MANUAL_DIR = os.path.join(BASE, 'catalog', 'manual') SERIES_DIR = os.path.join(BASE, 'catalog', 'series') NEW_SERIES = ['UCBM', 'ULP', 'USP', 'UGP', 'UAGP', 'LAE'] # 5x7 点阵字体 (每字符 7 字节, 位=像素) FONT = { 'A': [0x0E, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11], 'B': [0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x1E], 'C': [0x0E, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0E], 'D': [0x1C, 0x12, 0x11, 0x11, 0x11, 0x12, 0x1C], 'E': [0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x1F], 'F': [0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x10], 'G': [0x0E, 0x11, 0x10, 0x17, 0x11, 0x11, 0x0F], 'H': [0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11], 'I': [0x0E, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E], 'J': [0x07, 0x02, 0x02, 0x02, 0x12, 0x12, 0x0C], 'K': [0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11], 'L': [0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1F], 'M': [0x11, 0x1B, 0x15, 0x15, 0x11, 0x11, 0x11], 'N': [0x11, 0x19, 0x15, 0x13, 0x11, 0x11, 0x11], 'O': [0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E], 'P': [0x1E, 0x11, 0x11, 0x1E, 0x10, 0x10, 0x10], 'Q': [0x0E, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0D], 'R': [0x1E, 0x11, 0x11, 0x1E, 0x14, 0x12, 0x11], 'S': [0x0F, 0x10, 0x10, 0x0E, 0x01, 0x01, 0x1E], 'T': [0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04], 'U': [0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E], 'V': [0x11, 0x11, 0x11, 0x11, 0x11, 0x0A, 0x04], 'W': [0x11, 0x11, 0x11, 0x15, 0x15, 0x1B, 0x11], 'X': [0x11, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x11], 'Y': [0x11, 0x11, 0x0A, 0x04, 0x04, 0x04, 0x04], 'Z': [0x1F, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1F], '0': [0x0E, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0E], '1': [0x04, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x0E], '2': [0x0E, 0x11, 0x01, 0x02, 0x04, 0x08, 0x1F], '3': [0x1F, 0x02, 0x04, 0x06, 0x01, 0x11, 0x0E], '4': [0x02, 0x06, 0x0A, 0x12, 0x1F, 0x02, 0x02], '5': [0x1F, 0x10, 0x1E, 0x01, 0x01, 0x11, 0x0E], '6': [0x06, 0x08, 0x10, 0x1E, 0x11, 0x11, 0x0E], '7': [0x1F, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08], '8': [0x0E, 0x11, 0x11, 0x0E, 0x11, 0x11, 0x0E], '9': [0x0E, 0x11, 0x11, 0x0F, 0x01, 0x02, 0x0C], '/': [0x01, 0x01, 0x02, 0x04, 0x08, 0x10, 0x10], '-': [0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00], '.': [0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C], ':': [0x00, 0x0C, 0x0C, 0x00, 0x0C, 0x0C, 0x00], ' ': [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], } W, H = 794, 1123 # A4 @96dpi 近似 def draw_text(buf, text, x0, y0, scale, color): """把 text 按 5x7 点阵放大 scale 倍画进 buf (0xFFFFFF 背景)""" for ch in text: glyph = FONT.get(ch.upper(), FONT[' ']) for row in range(7): bits = glyph[row] for col in range(5): if bits & (0x10 >> col): for dy in range(scale): for dx in range(scale): px = (x0 + col * scale + dx) * 3 py = y0 + row * scale + dy buf[py * W * 3 + px] = color[0] buf[py * W * 3 + px + 1] = color[1] buf[py * W * 3 + px + 2] = color[2] x0 += 6 * scale def make_png(path, series): buf = bytearray([0xF5, 0xF6, 0xF8] * (W * H)) # 浅灰底 gray = (0x9A, 0xA2, 0xAC) dark = (0x3A, 0x45, 0x52) # 图纸外框 + 四角标 for x in range(40, W - 40): for y in (38, 39, H - 40, H - 39): i = (y * W + x) * 3 buf[i], buf[i + 1], buf[i + 2] = gray for y in range(38, H - 38): for x in (40, 41, W - 42, W - 41): i = (y * W + x) * 3 buf[i], buf[i + 1], buf[i + 2] = gray draw_text(buf, series, 60, 90, 10, dark) # 系列代码大字 draw_text(buf, 'DRAWING PENDING', 60, 230, 3, gray) # 占位说明 draw_text(buf, 'SERIES: ' + series, 60, 270, 2, gray) draw_text(buf, 'REPLACE THIS PAGE', 60, 300, 2, gray) draw_text(buf, 'WITH REAL MANUAL', 60, 324, 2, gray) # 对角辅助线 (工程图纸感) for t in range(0, 300): for x in (t, t + 1): i = ((90 + t) * W + 60 + x) * 3 buf[i], buf[i + 1], buf[i + 2] = 0xE2, 0xE6, 0xEA # 写 PNG (无压缩优化, 标准库) def chunk(tag, data): c = struct.pack('>I', len(data)) + tag + data c += struct.pack('>I', zlib.crc32(tag + data) & 0xFFFFFFFF) return c raw = b''.join(b'\x00' + bytes(buf[y * W * 3:(y + 1) * W * 3]) for y in range(H)) png = (b'\x89PNG\r\n\x1a\n' + chunk(b'IHDR', struct.pack('>IIBBBBB', W, H, 8, 2, 0, 0, 0)) + chunk(b'IDAT', zlib.compress(raw, 6)) + chunk(b'IEND', b'')) with open(path, 'wb') as f: f.write(png) def update_meta(series): meta_path = os.path.join(SERIES_DIR, series + '.meta.json') with open(meta_path, 'r', encoding='utf-8') as f: obj = json.load(f) obj['attachments'] = [{'kind': 'manual', 'lang': '', 'source': 'manual/%s/p01.png' % series, 'model': None}] with open(meta_path, 'w', encoding='utf-8') as f: json.dump(obj, f, ensure_ascii=False) def main(): made = 0 for s in NEW_SERIES: d = os.path.join(MANUAL_DIR, s) if not os.path.isdir(d): os.makedirs(d) make_png(os.path.join(d, 'p01.png'), s) update_meta(s) made += 1 print('OK %s: manual/%s/p01.png + meta 附件引用' % (s, s)) print('完成 %d 个系列' % made) print('提示: 真实手册到货后跑 render-manual-pages.py 覆盖, 或直接替换 p01.png 同名文件') if __name__ == '__main__': main()