chore: initial commit — import worldmodel workspace (plans/, research/)
This commit is contained in:
@@ -0,0 +1,324 @@
|
||||
"""
|
||||
Generate the A4 cover PDF for the PRISM project.
|
||||
|
||||
Output: plans/PRISM/PRISM_Cover.pdf
|
||||
|
||||
Layout (A4, 210 × 297 mm):
|
||||
┌──────────────────────────────────────────┐
|
||||
│ 彩色棱镜装饰 (左上 / 右下) │
|
||||
│ │
|
||||
│ 🌈 │
|
||||
│ PRISM │
|
||||
│ Prior-Registered Integrated │
|
||||
│ Spatial Memory │
|
||||
│ ──── 一行 tagline ──── │
|
||||
│ │
|
||||
│ [一句话摘要框] │
|
||||
│ │
|
||||
│ 项目介绍 (3-4 段) │
|
||||
│ │
|
||||
│ 版本 / 日期 / 作者 / 仓库 │
|
||||
└──────────────────────────────────────────┘
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from pathlib import Path
|
||||
from reportlab.lib.pagesizes import A4
|
||||
from reportlab.lib.units import mm
|
||||
from reportlab.lib.colors import HexColor, white, Color
|
||||
from reportlab.pdfgen import canvas
|
||||
from reportlab.pdfbase import pdfmetrics
|
||||
from reportlab.pdfbase.ttfonts import TTFont
|
||||
from reportlab.platypus import Paragraph, Frame
|
||||
from reportlab.lib.styles import ParagraphStyle
|
||||
from reportlab.lib.enums import TA_LEFT, TA_CENTER
|
||||
|
||||
# ────────────── 元数据 ──────────────
|
||||
PROJECT_NAME = "PRISM"
|
||||
PROJECT_FULL = "Prior-Registered Integrated Spatial Memory"
|
||||
PROJECT_SUBTITLE = "机器人大脑空间记忆架构"
|
||||
TAGLINE = "融合 iPhone 先验地图 与 ZED 2i 在线感知 的四层空间记忆系统"
|
||||
|
||||
ONE_LINER = ("iPhone 是教科书,ZED 2i 是眼睛,"
|
||||
"PRISM 大脑是一名既会查书又会观察、还会在书页空白处做笔记的学生。")
|
||||
|
||||
DESCRIPTION_PARAS = [
|
||||
("<b>PRISM</b> 是一种面向室内服务机器人(酒店 / 家居 / 办公)的"
|
||||
"<b>空间记忆架构</b>。它把<b>偶发的、高质量的离线扫描数据</b>"
|
||||
"(iPhone RoomPlan + LiDAR)与<b>持续的、视锥级的在线感知数据</b>"
|
||||
"(ZED 2i 双目+IMU)融合进同一套<b>四层记忆体系</b>:"
|
||||
"L1 感知缓冲 → L2 度量 → L3 拓扑 → L4 语义,"
|
||||
"并通过 <b>CLIP+ICP 配准</b>与<b>充电时的记忆巩固</b>形成闭环。"),
|
||||
|
||||
("<b>关键创新</b>:(1) 首次把消费级 iPhone RoomPlan 作为先验地图源;"
|
||||
"(2) 分层而非分库,所有数据共享 SpatialMemory 单一根对象;"
|
||||
"(3) 仿人脑睡眠的延迟巩固机制,避免单次错误观测污染长期记忆;"
|
||||
"(4) 通过锚点持续校正 VIO 长时漂移。"),
|
||||
|
||||
("<b>文档结构</b>:15 个 Markdown 文件(README + 14 章),含可立即复制运行的 "
|
||||
"Python schema、ROS 2 节点编排、8 周路线图、国产替代矩阵、"
|
||||
"风险登记表、评测指标、以及一份周末可跑通的最小 MVP。"),
|
||||
|
||||
("<b>预期产出</b>:8 周原型 → 4 个月生产 · MTBF ≥ 48h · "
|
||||
"冷启动重定位成功率 > 90% · 任务成功率 > 90%。"),
|
||||
]
|
||||
|
||||
VERSION = "v1.1"
|
||||
DATE = "2026-05-16"
|
||||
AUTHOR = "项目规划组"
|
||||
LICENSE = "CC BY-NC 4.0"
|
||||
REPO = "github.com/<org>/prism (TBD)"
|
||||
|
||||
# ────────────── 字体注册(macOS 中文) ──────────────
|
||||
def register_chinese_fonts() -> tuple[str, str]:
|
||||
"""Returns (regular_font_name, bold_font_name)."""
|
||||
candidates = [
|
||||
# (regular_path, regular_subfontidx, bold_path, bold_subfontidx, name_base)
|
||||
("/System/Library/Fonts/PingFang.ttc", 2,
|
||||
"/System/Library/Fonts/PingFang.ttc", 5, "PingFang"),
|
||||
("/System/Library/Fonts/STHeiti Light.ttc", 0,
|
||||
"/System/Library/Fonts/STHeiti Medium.ttc", 0, "STHeiti"),
|
||||
("/System/Library/Fonts/Hiragino Sans GB.ttc", 0,
|
||||
"/System/Library/Fonts/Hiragino Sans GB.ttc", 1, "Hiragino"),
|
||||
]
|
||||
for reg, ri, bold, bi, base in candidates:
|
||||
try:
|
||||
pdfmetrics.registerFont(TTFont(f"{base}-R", reg, subfontIndex=ri))
|
||||
pdfmetrics.registerFont(TTFont(f"{base}-B", bold, subfontIndex=bi))
|
||||
return f"{base}-R", f"{base}-B"
|
||||
except Exception as e:
|
||||
print(f" 字体 {base} 注册失败: {e}")
|
||||
continue
|
||||
# 全失败:回退到内置 Helvetica(可能无法显示中文)
|
||||
print(" ⚠️ 未找到中文字体,中文可能显示不全")
|
||||
return "Helvetica", "Helvetica-Bold"
|
||||
|
||||
|
||||
# ────────────── 色彩(棱镜光谱) ──────────────
|
||||
PRISM_COLORS = [
|
||||
HexColor("#E63946"), # red
|
||||
HexColor("#F77F00"), # orange
|
||||
HexColor("#FCBF49"), # yellow
|
||||
HexColor("#2A9D8F"), # teal-green
|
||||
HexColor("#1D7AB8"), # blue
|
||||
HexColor("#5A189A"), # purple
|
||||
]
|
||||
INK_DARK = HexColor("#0E1116")
|
||||
INK_MUTED = HexColor("#4A5560")
|
||||
INK_BG = HexColor("#FBFAF7")
|
||||
ACCENT = HexColor("#1D7AB8")
|
||||
GOLD_LINE = HexColor("#C9A227")
|
||||
|
||||
|
||||
def draw_prism_decoration(c: canvas.Canvas, page_w: float, page_h: float):
|
||||
"""左上角:三角形棱镜分光示意 + 右下角:光谱条带"""
|
||||
# ── 左上 棱镜三角形 + 入射/出射光 ──
|
||||
cx, cy = 30 * mm, page_h - 35 * mm
|
||||
size = 18 * mm
|
||||
# 入射"白光"
|
||||
c.setLineWidth(1.4); c.setStrokeColor(INK_DARK)
|
||||
c.line(cx - size*1.5, cy + 2*mm, cx - 1*mm, cy + 1*mm)
|
||||
# 三角棱镜
|
||||
c.setFillColor(HexColor("#EAEAEA")); c.setStrokeColor(INK_DARK)
|
||||
p = c.beginPath()
|
||||
p.moveTo(cx, cy + size)
|
||||
p.lineTo(cx + size*0.866, cy - size*0.5)
|
||||
p.lineTo(cx - size*0.866, cy - size*0.5)
|
||||
p.close()
|
||||
c.drawPath(p, stroke=1, fill=1)
|
||||
# 七色出射
|
||||
for i, col in enumerate(PRISM_COLORS):
|
||||
c.setStrokeColor(col); c.setLineWidth(1.6)
|
||||
y_off = (i - len(PRISM_COLORS)/2 + 0.5) * 2.0 * mm
|
||||
x_start = cx + size*0.6
|
||||
x_end = cx + size*3.0
|
||||
y_start = cy
|
||||
y_end = cy + y_off
|
||||
c.line(x_start, y_start, x_end, y_end)
|
||||
|
||||
# ── 右下 光谱条带(放在 footer 上方,避免与 footer 文字重叠)──
|
||||
bar_w = 60 * mm
|
||||
bar_h = 3 * mm
|
||||
bx = page_w - 20*mm - bar_w
|
||||
by = 21 * mm # 上移到 meta 与 footer 之间的留白处
|
||||
seg = bar_w / len(PRISM_COLORS)
|
||||
for i, col in enumerate(PRISM_COLORS):
|
||||
c.setFillColor(col); c.setStrokeColor(col)
|
||||
c.rect(bx + i*seg, by, seg, bar_h, fill=1, stroke=0)
|
||||
|
||||
|
||||
def draw_letter_glyphs(c: canvas.Canvas, x: float, y: float,
|
||||
letters: list[tuple[str, str]],
|
||||
font: str, font_size: float, gap: float):
|
||||
"""彩色大字母 P R I S M ,每个字母带颜色,下方写英文 token"""
|
||||
for (letter, _), col, i in zip(letters, PRISM_COLORS, range(len(letters))):
|
||||
# 这里我们让 P R I S M 取前 5 个颜色;Hex 不取第 6 个
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
out = Path(__file__).resolve().parents[1] / "PRISM_Cover.pdf"
|
||||
print(f"→ output: {out}")
|
||||
|
||||
reg, bold = register_chinese_fonts()
|
||||
print(f" fonts: regular={reg}, bold={bold}")
|
||||
|
||||
page_w, page_h = A4
|
||||
c = canvas.Canvas(str(out), pagesize=A4)
|
||||
c.setTitle("PRISM — Prior-Registered Integrated Spatial Memory")
|
||||
c.setAuthor(AUTHOR)
|
||||
c.setSubject(PROJECT_SUBTITLE)
|
||||
c.setKeywords("PRISM, spatial memory, robot, iPhone RoomPlan, ZED 2i, SLAM, world model")
|
||||
c.setCreator("PRISM make_cover_pdf.py")
|
||||
|
||||
# ── 背景纸色 ──
|
||||
c.setFillColor(INK_BG)
|
||||
c.rect(0, 0, page_w, page_h, fill=1, stroke=0)
|
||||
|
||||
# ── 装饰 ──
|
||||
draw_prism_decoration(c, page_w, page_h)
|
||||
|
||||
# ── 顶部小标签 ──
|
||||
c.setFont(reg, 9)
|
||||
c.setFillColor(INK_MUTED)
|
||||
c.drawString(20*mm, page_h - 15*mm, "ROBOT · SPATIAL · MEMORY")
|
||||
c.drawRightString(page_w - 20*mm, page_h - 15*mm,
|
||||
f"VERSION {VERSION} · {DATE}")
|
||||
|
||||
# ── 主标题 PRISM ──
|
||||
title_y = page_h - 75 * mm
|
||||
c.setFont(bold, 78)
|
||||
title = "PRISM"
|
||||
# 等距彩色 5 个字母
|
||||
spectrum5 = PRISM_COLORS[:5]
|
||||
title_total_w = c.stringWidth(title, bold, 78)
|
||||
start_x = (page_w - title_total_w) / 2
|
||||
cur_x = start_x
|
||||
for ch, col in zip(title, spectrum5):
|
||||
c.setFillColor(col)
|
||||
c.drawString(cur_x, title_y, ch)
|
||||
cur_x += c.stringWidth(ch, bold, 78)
|
||||
|
||||
# ── 副标题英文全称 ──
|
||||
c.setFont(bold, 14)
|
||||
c.setFillColor(INK_DARK)
|
||||
sub_en = PROJECT_FULL
|
||||
c.drawCentredString(page_w/2, title_y - 13*mm, sub_en)
|
||||
|
||||
# ── 中文副标题 ──
|
||||
c.setFont(reg, 16)
|
||||
c.setFillColor(INK_DARK)
|
||||
c.drawCentredString(page_w/2, title_y - 23*mm, PROJECT_SUBTITLE)
|
||||
|
||||
# ── 分隔线 + tagline ──
|
||||
rule_y = title_y - 33*mm
|
||||
c.setStrokeColor(GOLD_LINE); c.setLineWidth(0.8)
|
||||
rule_half = 60*mm
|
||||
c.line(page_w/2 - rule_half, rule_y, page_w/2 + rule_half, rule_y)
|
||||
c.setFont(reg, 11); c.setFillColor(INK_MUTED)
|
||||
c.drawCentredString(page_w/2, rule_y - 7*mm, TAGLINE)
|
||||
|
||||
# ── 字母含义注解 P-R-I-S-M ──
|
||||
legend_y = rule_y - 23*mm
|
||||
legend_items = [
|
||||
("P", "Prior"),
|
||||
("R", "Registered"),
|
||||
("I", "Integrated"),
|
||||
("S", "Spatial"),
|
||||
("M", "Memory"),
|
||||
]
|
||||
item_w = 28*mm
|
||||
total_w = item_w * len(legend_items)
|
||||
start_x = (page_w - total_w) / 2
|
||||
for i, (letter, word) in enumerate(legend_items):
|
||||
x = start_x + i*item_w + item_w/2
|
||||
c.setFont(bold, 22); c.setFillColor(PRISM_COLORS[i])
|
||||
c.drawCentredString(x, legend_y, letter)
|
||||
c.setFont(reg, 9); c.setFillColor(INK_DARK)
|
||||
c.drawCentredString(x, legend_y - 6*mm, word)
|
||||
|
||||
# ── 一句话摘要框 ──
|
||||
box_y_top = legend_y - 18*mm
|
||||
box_h = 28*mm
|
||||
box_x = 25*mm
|
||||
box_w = page_w - 50*mm
|
||||
# 浅色背景
|
||||
c.setFillColor(HexColor("#F0EEE6")); c.setStrokeColor(ACCENT)
|
||||
c.setLineWidth(0.6)
|
||||
c.roundRect(box_x, box_y_top - box_h, box_w, box_h, 4*mm, fill=1, stroke=1)
|
||||
# 标签
|
||||
c.setFont(bold, 9); c.setFillColor(ACCENT)
|
||||
c.drawString(box_x + 6*mm, box_y_top - 6*mm, "一句话摘要 · ONE-LINER")
|
||||
# 引言文字
|
||||
quote_style = ParagraphStyle(
|
||||
"quote", fontName=reg, fontSize=12.5, leading=18,
|
||||
textColor=INK_DARK, alignment=TA_LEFT,
|
||||
firstLineIndent=0, leftIndent=0, rightIndent=0)
|
||||
para = Paragraph(f"“{ONE_LINER}”", quote_style)
|
||||
frame = Frame(box_x + 6*mm, box_y_top - box_h + 3*mm,
|
||||
box_w - 12*mm, box_h - 12*mm,
|
||||
showBoundary=0, leftPadding=0, rightPadding=0,
|
||||
topPadding=0, bottomPadding=0)
|
||||
frame.addFromList([para], c)
|
||||
|
||||
# ── 项目介绍正文 ──
|
||||
intro_y_top = box_y_top - box_h - 8*mm
|
||||
intro_x = 25*mm
|
||||
intro_w = page_w - 50*mm
|
||||
intro_h = intro_y_top - 50*mm # 底部留 50mm 给元信息
|
||||
|
||||
body_style = ParagraphStyle(
|
||||
"body", fontName=reg, fontSize=9.5, leading=14,
|
||||
textColor=INK_DARK, alignment=TA_LEFT,
|
||||
spaceAfter=5, firstLineIndent=0)
|
||||
|
||||
# 给底部 meta 块预留 45mm:meta 顶在 45mm,文字行底 40mm,值底 33mm
|
||||
META_TOP = 45 * mm
|
||||
INTRO_BOT = META_TOP + 5*mm # intro frame 底部留 5mm 空白
|
||||
intro_h = intro_y_top - INTRO_BOT
|
||||
|
||||
paragraphs = [Paragraph(p, body_style) for p in DESCRIPTION_PARAS]
|
||||
frame = Frame(intro_x, INTRO_BOT,
|
||||
intro_w, intro_h,
|
||||
showBoundary=0,
|
||||
leftPadding=0, rightPadding=0,
|
||||
topPadding=0, bottomPadding=0)
|
||||
frame.addFromList(paragraphs, c)
|
||||
|
||||
# ── 底部元信息块 ──
|
||||
meta_y = 40 * mm # 标签行
|
||||
val_y = meta_y - 5.5*mm # 值行
|
||||
c.setStrokeColor(INK_MUTED); c.setLineWidth(0.4)
|
||||
c.line(20*mm, META_TOP, page_w - 20*mm, META_TOP)
|
||||
|
||||
meta_items = [
|
||||
("VERSION", VERSION),
|
||||
("DATE", DATE),
|
||||
("AUTHOR", AUTHOR),
|
||||
("LICENSE", LICENSE),
|
||||
]
|
||||
col_w = (page_w - 40*mm) / len(meta_items)
|
||||
for i, (k, v) in enumerate(meta_items):
|
||||
x = 20*mm + i*col_w
|
||||
c.setFont(bold, 8); c.setFillColor(INK_MUTED)
|
||||
c.drawString(x, meta_y, k)
|
||||
c.setFont(reg, 10); c.setFillColor(INK_DARK)
|
||||
c.drawString(x, val_y, v)
|
||||
|
||||
# ── 底部 footer(左:仓库;右:slogan;两者避开右下光谱条所在的 21mm 处)──
|
||||
c.setFont(reg, 7.5); c.setFillColor(INK_MUTED)
|
||||
c.drawString(20*mm, 12*mm,
|
||||
"plans/PRISM/ · 15 documents (README + Ch.00–14)")
|
||||
c.drawString(20*mm, 8*mm,
|
||||
"Repo: " + REPO)
|
||||
# 右侧 slogan 放在光谱条上方(避免覆盖)
|
||||
c.setFont(reg, 8); c.setFillColor(INK_MUTED)
|
||||
c.drawRightString(page_w - 20*mm, 12*mm,
|
||||
"Prism splits perception into memory.")
|
||||
|
||||
c.showPage()
|
||||
c.save()
|
||||
print(f"✅ done: {out} ({out.stat().st_size/1024:.1f} KB)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user