# FacereDataset 执行日志 时间倒序,最新在顶部。 --- ## 2026-04-29 02:30 KiCad 工程文件 + `--all` 一键导:双击 .kicad_pro 打开 GUI **Claude 会话** 接 `adc5dc5`。两件小事一起做:发 `.kicad_pro` + CLI 加 `--all`。目标是让消费者(咱们的 ML 训练代码 / 下游同学)双击就能在 KiCad GUI 里同时打开 schematic + PCB 配对。 ### 1. `tools/epro2/kicad/pro_writer.py` KiCad 8 的 .kicad_pro 是 JSON。最小可用集: ```json { "meta": {"filename": ".kicad_pro", "version": 1}, "sheets": [["", ""]], // 绑 schematic 根 sheet "board": {...}, "schematic": {...}, "net_settings": {...}, ... } ``` 只要 .kicad_pro / .kicad_sch / .kicad_pcb **三个文件同名同目录**,KiCad 自动配对——双击 .kicad_pro 同时弹两个编辑器。`sheets` 数组里的 uuid 必须和 .kicad_sch 的 `(uuid ...)` 对得上,所以 `write_root_sheet` 加了 `root_uuid` 参数让调用方注入确定值。 ### 2. CLI `--all` 按 BOARD uuid 分组 SCH 和 PCB(两边的 META 都带 `board: `,自动 1:1 对应),每个 BOARD 一个目录: ``` out/ ├── EchoEar-BaseBoard-V1_0/ │ ├── EchoEar-BaseBoard-V1_0.kicad_pro │ ├── EchoEar-BaseBoard-V1_0.kicad_sch (root) │ ├── EchoEar-BaseBoard-V1_0.kicad_pcb │ └── P1_45092758.kicad_sch (子页) ├── EchoEar-CoreBoard-V1_0/ │ ├── EchoEar-CoreBoard-V1_0.kicad_pro │ ├── EchoEar-CoreBoard-V1_0.kicad_sch │ ├── EchoEar-CoreBoard-V1_0.kicad_pcb │ ├── Overview_dc13d6d2.kicad_sch (4 个子页) │ ├── MCU_510cff33.kicad_sch │ ├── Interface_b336a7c7.kicad_sch │ └── codec_0b0163fa.kicad_sch ... (5 boards total) ``` basename 从 SCH/PCB title 剥掉 `SCH-`/`PCB-` 前缀(`SCH-EchoEar-CoreBoard-V1_0` → `EchoEar-CoreBoard-V1_0`),schematic 和 PCB 自然 collapse 到一个项目。 ### ESP-VoCat 实测 6 个 BOARD(含 LCD-BD 那个 SCH 被删的——只有 PCB,照样生成 `.kicad_pro` 和 `.kicad_pcb`,sheets:[]): | 项目 | sch 解析 | pcb 渲染 | |---|:-:|:-:| | EchoEar-BaseBoard-V1_0 | ✓ ERC 485 | ✓ | | EchoEar-CoreBoard-V1_0 | ✓ ERC 1205 | ✓ | | EchoEar-Rotating-Base-LCD-BD-V1_0 | (no sch) | ✓ | | EchoEar-Rotating-Base-Mainboard-V1_0 | ✓ ERC 412 | ✓ | | ESP-VoCat-MicBoard-V1_0 | ✓ ERC 133 | ✓ | | ESP-VoCat-Rotating-Base-Sub-board-V1_0 | ✓ ERC 30 | ✓ | `pro.sheets[0][0]` 跟 `.kicad_sch` 第一行 `(uuid ...)` 字符串相等(验证过 CoreBoard:`366d3e53-5167-4e17-9325-c2fccbe4330b`)。 ### 决策(Why) - **basename 对齐 = KiCad pairing 的全部**:KiCad 不读 .kicad_pro 里的 `boards`/`schematic` 字段去找文件,纯靠**同目录同 basename**自动配对。其它字段都是项目 settings,缺了 KiCad 用 default 兜底。 - **`sheets: []` 兜底 LCD-BD**:SCH 被 DELETE_DOC 的板子没 schematic root,pro 里空数组兜底,KiCad 打开时只显示 pcbnew,不会卡。 - **`--all` 不替代 `--all-sch` / `--all-pcb`**:保留两个细粒度命令——只想看 schematic 或只想 batch-export PCB 时不必生成 .kicad_pro 噪音。 ### 测试 68 → 71 单测全过:pro_writer 3 个(filename + root uuid 绑定 / sheets 空数组 fallback / KiCad 8 顶层 key 完整性)。 ### 下游交付 下游同学的 Wokwi pipeline 不吃的 5 个 Pro 项目(3 块 EPRO2 AES + 2 块 Pro 2.x)现在一条命令就能转: ``` uv run python -m tools.epro2.kicad data/raw/oshwhub/ --all --out ``` 每个项目目录拷贝到他们 corpus 即可。 --- ## 2026-04-29 02:00 PCB Phase-2:POUR → KiCad zone,CoreBoard unconnected -43% **Claude 会话** 接 `e614044`。Phase-1 PCB 6 板都解析了但 DRC 报很多 unconnected——大头是 GND/AGND 走 POUR 覆铜没导出来。补 zone 导出。 ### 做的 `pcb_writer.py` 加 `_decode_zone_path` 处理 EPRO2 POUR.path 三种形态: - `[['R', x, y, w, h, ...]]` — 矩形(实测最常见,CoreBoard/MicBoard 全是这个) - `[['CIRCLE', cx, cy, r]]` — 圆形(按 36 段近似成 polygon) - `[[x1, y1, 'L', x2, y2, ...]]` — polyline(ARC token 跳过 4 个 token,按弦近似) 每个 POUR 输出 `(zone (net N) (net_name "..." ) (layer "F.Cu") (polygon (pts ...)) (filled_polygon (pts ...)))`。 ### 关键坑:必须 emit `(filled_polygon)` 块 第一次只发 `(zone)` 带 `(polygon)` 边界的版本,DRC 完全不变——`kicad-cli pcb drc` **不**自动跑 zone fill,只有 GUI 的"填充覆铜"会跑。所以 file 必须自己声明已填充。简单做法:用 boundary polygon 当 filled_polygon(= "整个 pour 区域都是铜",忽略 pad clearance/thermal)。 ### ESP-VoCat 6 板 DRC 对比(unconnected_items count) | 板 | before zones | after zones | Δ | |---|---:|---:|---:| | BaseBoard | 227 | 227 | 0 | | CoreBoard | 358 | **205** | **-43%** | | MicBoard | 75 | 75 | 0 | | LCD-BD | 43 | 43 | 0 | | Mainboard | 179 | 179 | 0 | | Sub-board | 1 | 1 | 0 | | **TOTAL** | **883** | **730** | **-17%** | ### 为什么只 CoreBoard 改善明显 抽样 MicBoard 残留 75 unconnected: - AGND 94 个 item 里很多 pad 在 zone boundary 之外——POUR 矩形是 (72.3, 112.3)→(126.8, 126.0),但 AGND pad 在 y=107 上方 - 大量 `$1N1865` 这种内部网——根因是 via 没绑对网(不是 POUR 能解决的) EPRO2 用户画 POUR 时通常只覆元件密集区,不覆全板;外围 trace 自己接。zone 解决"靠 pour 接到 GND"的 pad,但解不了"trace 路由不通"或"via 网漂移"。 CoreBoard zones=7(4-layer,GND+POWER+AGND 各一对),覆盖面广,效果明显。其它板 zones 多是 2 个,覆盖小。 ### 决策(Why) - **filled_polygon = boundary 不做 clearance/thermal 计算**:自己实现 zone fill 算法工作量爆炸(KiCad 实现是 C++ 几千行)。boundary fill 是"连通性正确,clearance 不精确"——KiCad GUI 一键 refill 即可矫正。这条路保留 EPRO2 user-drawn boundary 作为 single source of truth。 - **不读 POURED.pourFill**:POURED 是 EPRO2 自己 fill 算法的输出,path 含 ARC 难解析、坐标系跟 POUR 不一定对齐。boundary 直接用更可靠。 - **ARC 在 polyline 里按弦近似**:跟 Phase-1 ARC 处理一致,KiCad 解析得了,几何稍偏(不会比 POUR 不导更糟)。 - **不强行优化 MicBoard 那种 zone 之外的 pad**:那是 EPRO2 source 本身的连通方式(trace + via),不是 zone 能修的。 ### 测试 65 → 68 单测全过:rectangle path → 4 corners + filled_polygon mirror / circle → 36-seg polygon / 非 copper layer skip。 ### 下一步建议 - **ARC 圆心反推**(中等工作量):消 invalid_outline 警告 + zone polyline 里 ARC 段更准。需要三点定圆。 - **schematic + PCB 同时跑**(小工作量):CLI 加 `--all` 同时输出两套,目录配对。 - **`.kicad_pro` 项目文件**(小工作量):双击就能打开 KiCad GUI,schematic 和 PCB 自动配对。 --- ## 2026-04-29 01:30 KiCad 导出 Phase 3 PCB:6/6 .kicad_pcb 全部 kicad-cli 通过 **Claude 会话** 接 `ff5553f` 后续。Forge 投影最后一块——schematic 已经够用,做 `.kicad_pcb` 导出。一上来就铺了 Phase-1 最小可解析 scope(要先有"能在 KiCad 8 里打开 + kicad-cli 处理"的底线),不追全保真。 ### 做的 1. **`tools/epro2/kicad/pcb_writer.py`** — 单 PCB doc → `.kicad_pcb` 主入口 - 数据驱动的层映射 `_build_layer_map`:扫所有 primitive 的 `layerId`,把真正用到的 SIGNAL 内层(id 15+)按 EPRO2-id 顺序铺成 `In1.Cu`/`In2.Cu`/...;F.Cu 永远 ordinal 0、B.Cu 永远 ordinal 31(KiCad 硬约定)。 - net 表:从 NET op 取名字,从 1 开始分配 id(0 留给 KiCad 的 "no net")。 - 走 LINE:copper 层 → `(segment ...)`,layer 11 OUTLINE → `(gr_line layer Edge.Cuts)`;走 VIA → `(via)`;走 layer-11 POLY → 把 path 拆 polyline 段 emit 成 Edge.Cuts 上的 gr_line 链(实测板子轮廓全在 POLY 不在 LINE,少这步 edge=0)。 - ARC 现在按弦近似(Phase 1 不解 EPRO2 ARC.path 的圆心/半径表示,KiCad 收得下,DRC 报 invalid_outline——下一轮改)。 2. **`tools/epro2/kicad/footprint_writer.py`** — inline `(footprint ...)` 块 - 走 FOOTPRINT.PAD:`RECT/ELLIPSE/OVAL/POLYGON` → KiCad `rect/circle/oval/custom`;hole 存在则 SMD→thru_hole + `(drill ...)`,SLOT 走 `(drill oval w h)` 二参形式。 - layer 映射:layerId 1 → `F.Cu+F.Mask+F.Paste`、2 → `B.Cu+B.Mask+B.Paste`、12 (MULTI/THT) → `*.Cu+*.Mask`。 - net 解析借 `pcb_rel.pad_nets_by_pad[pad_id]`(之前 `ProjectRelations` 已经为 PCB→FOOTPRINT cross-doc 攒过的索引现在派上用场)。 - bottom-side COMPONENT (`layerId=2`) 整个 footprint 走 `(layer B.Cu)`,Reference 标签同步落到 `B.SilkS`。 3. **CLI 加 `--all-pcb`**(`tools/epro2/kicad/__main__.py`):每个 PCB doc 一文件,文件名取 META.title。 ### ESP-VoCat 实测(6 个 PCB) | 板 | nets | footprints | segments | vias | edge_cuts | |---|---:|---:|---:|---:|---:| | BaseBoard | 38 | 59 | 391 | 126 | 4 | | CoreBoard | 84 | 87 | 1131 | 262 | 4 | | MicBoard | 13 | 17 | 88 | 47 | 16 | | LCD-BD | 7 | 5 | 66 | 35 | 2 | | Mainboard | 24 | 33 | 340 | 181 | 4 | | Sub-board | 4 | 5 | 10 | 0 | 4 | **6/6 都过 `kicad-cli pcb export svg`**——文件解析无 error、SVG 输出正常。footprint/track/via/edge cuts 全部可见。 `kicad-cli pcb drc` 对最小的 MicBoard 跑:145 violations + 75 unconnected。分布: - 28 clearance(trace/pad 间距)+ 19 track_dangling + 39 via_dangling + 75 unconnected — 真连接问题,部分是 EPRO2 源里靠 POUR 解决但我们 Phase 1 没导 - 21 silk_overlap + 8×2 edge_clearance + 3 invalid_outline — 边角 + silk 美化 - 17 lib_footprint_issues — facere 库没注册(cosmetic,跟 schematic 一样) ### Phase 1 砍掉的 / 下一轮再做 - **POUR / POURED** — 铜皮覆铜,导了 75 unconnected 的大头会消掉(GND 大面积在 POUR 里走,没导出来 trace 当然报 unconnected) - **FILL** — 元件下方手工 fill 块 - **ARC 圆心/半径解析** — 现在按弦近似,invalid_outline 警告就这来的 - **TEARDROP** — pad/via 接 trace 处的圆角,纯美化 - **STRING / IMAGE** — 板上文字和 logo 图 ### 决策(Why) - **板子原点平移到 (100, 100) mm**:EPRO2 板能在任意坐标(含负),KiCad 画布原点在 (0,0),不平移开 KiCad 时板子可能根本不在视野里。100mm 是经验值,留够 silkscreen 边距。 - **footprint inline 不走外部 .kicad_mod**:跟 schematic lib_symbol 同思路,自包含,能直接 `kicad-cli` 处理;缺点是同款 footprint 重复写多份,但 EPRO2 一个 SCH 内的 FOOTPRINT 数量级(几十)摊开来文件膨胀也不大。 - **ELLIPSE 当 circle 不当 oval**:实测 ELLIPSE 的 width/height 经常相等(=圆形 pad),KiCad 没有真椭圆 pad type,circle 取 max(w,h) 比 oval 更接近圆。 - **ARC 暂时按弦**:算 EPRO2 ARC 圆心要先反推三点定圆,浮点精度敏感。Phase 2 单独处理。 ### 测试 52 → 65 单测全过:pcb_writer 8 个(layer map / net id / segment / via / outline POLY / 零长 skip / 非 PCB doc reject)+ footprint_writer 5 个(SMD pad layers / 圆 vs slot drill / pad net 跨 doc 解析 / bottom-layer 翻转 / unresolved skip)。 ### 下一步建议 - **POUR/POURED 导 zone**(中工作量):消掉大部分 75 unconnected,CoreBoard 这种 4-layer + 大量 GND/POWER 覆铜的板真实连通性会贴近完整。 - **schematic + PCB 同时跑** 一个 export 命令(`--all`),生成完整 KiCad project 目录。 - **Phase 4: KiCad project 文件 .kicad_pro**:让用户双击就能在 KiCad GUI 里打开 schematic + PCB 配对。 --- ## 2026-04-29 01:00 科普文档:爬取 per-doc .epro2 vs 网页端 .epro2 ZIP 整包 **Claude 会话** 接 chain replay sleep 优化(commit `1e06ba6`)后续。同事看到 `data/raw/oshwhub//source/` 下面躺着 278 个 `.epro2` 而不是 1 个,会直觉以为抓错了——他们认知里的 `.epro2` 是网页端"下载工程包"那个 1.4 MB 单文件。 实际上: - **网页端 `.epro2`** = ZIP 容器(扩展名误导),里面 `.epru`(拼成一坨的 EPRO2 流)+ `project2.json` + `IMAGE/` 6 张组件预览图 - **爬取 `.epro2`** = 工程内每个文档(SYMBOL / FOOTPRINT / DEVICE / SCH_PAGE / PCB ……)自己的 EPRO2 消息流,per-doc 一文件 - 两者**信息量基本等价**(ESP-VoCat 我们 278 vs 网页 266,多的 12 个是 history chain 上演化掉的容器层旧版本);唯一真实差异是 IMAGE/ 二进制图(我们 blob 引用爬到了但没拉本体——已知 gap) - 我们走 per-doc 不走 ZIP 的硬约束:**ZIP 那条路服务端没有公开端点**,是纯前端 JS 现拼现压(三份 HAR 验证:导出按钮零 HTTP 流量) 写到 `docs/sources/pro_crawl_vs_export.md`,给同事看。结构:TL;DR → ESP-VoCat 实例 → docType 分布对比表 → 数量差异解释 → 体积对比 → 选型决策表。 --- ## 2026-04-29 00:30 KiCad 导出 Phase 3 hierarchical:root + global_label + 5-Voltage 电源端口 **Claude 会话** 接 `54f0173`。Handoff #3 多 sheet hierarchical:让 `kicad-cli sch erc` 在 project 视角而不是单 sheet 视角下校验,理论上能把 single-sheet ERC 看不见跨 sheet 的 248+111 残留压下去。 ### 三件事一起做 1. **`(label)` → `(global_label)`**(`tools/epro2/kicad/sch_writer.py`) EPRO2 的 NET 是项目全局——一个 GND 名字横跨整个 schematic 而且通过 PCB 走线到隔壁板子。KiCad 的 `(label)` 是 sheet-scoped,单 sheet ERC 看到一个名字只出现一次就报 dangling;`(global_label)` 才是项目级,hierarchical ERC 在 root 上能跨 sheet 解。 2. **`tools/epro2/kicad/root_sch_writer.py` 新模块** 按 EPRO2 的 `SCH_PAGE.META.schematic` 把页分组,每组 emit 一个 root `.kicad_sch`,里面 N 个 `(sheet ...)` 引子页。子页的 `(sheet_instances (path "/" ...))` 必须回引 root 给它分配的 uuid——少了这一步 ERC 把子页当孤岛走。 3. **5-Voltage 电源端口识别**(`sch_writer.py` COMPONENT 循环里加判断) 实测 ESP-VoCat 有 365 个 partId=`pid8a0e77bacb214e` 的 COMPONENT——挖了下发现这是 EPRO2 内部的 "Voltage" 占位符号,对应 KiCad 的 `(power_symbol)`。**网络名不在 PART 里,而是 placement 自己的 `Global Net Name` ATTR**(101 个有名字、264 个还是空的草稿态)。每次有名字就在 placement 位置 emit 一个 `(global_label)`。 ### CLI 改造 `uv run python -m tools.epro2.kicad --all-sch` 现在默认 hierarchical 输出:每个 SCH 一个目录,里面 root + 子页。`--flat` 兜回老行为(一张图一个文件)。`DELETE_DOC.isDelete=True` 的 SCH 直接跳——LCD-BD 那个被删了的没生成。 ### ESP-VoCat 实测 `kicad-cli sch erc ` 跨 5 个 root 累加: | Type | flat baseline | hier 后 | Δ | |---|---:|---:|---:| | wire_dangling | 52 | 52 | 0 | | pin_not_connected | 196 | 190 | -6 | | label_dangling → global_label_dangling | 111 | 105 | -6 | | pin_not_driven | 23 | 21 | -2 | | endpoint_off_grid | 1372 | 1340 | -32 (LCD-BD 移除带走的) | | lib_symbol_issues | 571 | 557 | -14 (同上) | | **TOTAL** | **2325** | **2265** | **-60** | **修得不够多——为什么**:EPRO2 的 6 个 SCH 里 5 个只有 1 page,hierarchical 对它们没用;只有 CoreBoard 是真 4-page 多 sheet。CoreBoard 自己里面也只有 GND / MCU_3V3 / VCC_3V3 是真跨 sheet 共享的网,其它(GPIO4, CHIP_PU, AUDIO_I2C_*, I2S_*)都是 sheet-local——名字虽然 unique 但只在一个 sheet 上有 wire 引用,hierarchical ERC 也救不了,依然 dangling。 **剩下 190 PNC 的真原因**:抽样发现 C44/C45/R19/R20 这类 0402 元件,wire 从一个 pin 出来直直穿过另一个 pin,但 pin 落在 wire **中段**而不是端点。KiCad ERC 要求 pin 落在 wire 端点或 explicit junction 处才认连接,wire 中段穿过的 pin 不自动连。EPRO2 源里这种连接合法但导出时丢了——要修需要做 wire-pin 几何相交,在中段 pin 位置 split wire 或 emit `(junction)`。下一轮再做。 ### 决策(Why) - **不走单 root 包全部 9 page**:6 块 PCB 是物理独立板子,merge 进一个 root 会把 BaseBoard 的 GND 和 CoreBoard 的 GND 误判成同一个网。EPRO2 已经按 SCH 分好组,按 SCH 拆 root 是结构对齐做法。 - **`(global_label (shape passive))`**:不知道方向,input/output/bidirectional 都会触发更多 ERC 检查(如 pin_not_driven),passive 最保守。power 网用 power_in 才理想,但需要 driver 元件,超出本轮范围。 - **保留占位符号 placement 同时再 emit global_label**:5-Voltage 占位符在 KiCad 里画出来虽然冗余但不影响 ERC,删了反而丢视觉信息。 - **不实现自动 junction**:geometry 计算成本明显高于本轮收益(<10 PNC 的预期降幅),做成下一轮独立改动更清晰。 ### 测试 47 → 52 单测全过:root_sch_writer 3 个 + power-port label 2 个 + sheet_path/page_num propagate 1 个 - test_named_wire 改 global_label 重写 1 个。 ### 下一步建议 - **wire-pin junction emission**(中等工作量):算每个 COMPONENT 的 abs pin 位置,对每条 wire 查"非端点 pin 命中",命中就 split wire 或 emit junction。预期把 PNC 从 190 再砍一半左右。 - 或者直接进 **Phase 3 真正的 .kicad_pcb 导出**——schematic 这边已经够用,PCB 才是 Forge 投影最后一块。 --- ## 2026-04-28 23:55 KiCad 导出修真实连接错:wire_dangling -88%, pin_not_connected -52% **Claude 会话** 接 `fb577cc` 后续。Handoff 选项 #1:用 ERC violation 数从 850 降到接近 0 作为目标。Bisect 出两个根因,都是结构性的 KiCad 语义错(不是浮点 / 精度问题,所以 grid round 不是答案): ### Bug A — sym_writer 漏 Y-flip KiCad lib symbol 是 Y-up,schematic 是 Y-down,placement 时 KiCad 会再翻一次 Y。我们把 EPRO2 (Y-down) 的 PIN/RECT/POLY/CIRCLE/TEXT 坐标直接当 lib coord 写,KiCad 的翻转就把整个 symbol body 上下镜像了。U8 4-pin 磁吸座实测:pin 1↔4 对调、2↔3 对调,结果 wire 端点跟错位的 pin tip 撞不上 → ERC 报 pin_not_connected + wire_dangling。修法:`lib_y = -epro2_y`,pin/text rotation 也镜像 `lib_rot = (360 - rot) % 360`。 ### Bug B — 没发 net label,KiCad 看不出 EPRO2 的命名网络 EPRO2 的 WIRE op 带 NET attr(`TXD` / `GND` / `VBUS` ……),多段 LINE 通过同名 NET 连成一个网,**不需要几何相邻**。KiCad 不知道这套,只看几何。修法:在 sch_writer 里查每条 LINE 的 `lineGroup → WIRE → NET attr`,命中就在 LINE 起点 emit 一个 `(label "")`。同名 label 在多条物理 wire 上 → KiCad ERC 才认这是同一个网。**per-LINE 不是 per-WIRE**:单个 WIRE id 下面的 LINE 段不一定共端点,每段都得有 label 才不被判 dangling。 ### ESP-VoCat 9 sheets ERC 对比 | Type | baseline | after | Δ | |---|---:|---:|---:| | wire_dangling | 444 | 52 | **−88%** | | pin_not_connected | 406 | 196 | **−52%** | | **real connectivity 合计** | **850** | **248** | **−71%** | | label_dangling | 0 | 111 | new (warn) | | pin_not_driven | 0 | 23 | new (connector pin 类型问题) | | endpoint_off_grid | 1372 | 1372 | unchanged (cosmetic, EPRO2 用 30/20/10 mil pitch,不在 KiCad 50 mil 网格上;不能 round——会把 < 50 mil 的 pin 间距压到一起) | | lib_symbol_issues | 571 | 571 | unchanged (没注册 facere lib,cosmetic) | 剩余 248 real-connectivity 错主要是 single-sheet ERC 的天然限制:很多网只在一个 sheet 上有这一个 pin,对端在别的 sheet。kicad-cli 一次只看一个 .kicad_sch,看不见跨 sheet。彻底修要 Phase 3(hierarchical 顶层 + sheet links)。 ### 关键决策(记 Why) - **不 round to grid**:50 mil grid 会塌缩 < 50 mil pin pitch(实测 4-pin 磁吸座是 10 mil pitch),破坏几何。EPRO2 源已经在整数 mil,浮点不是 root cause。 - **per-LINE label 不是 per-WIRE**:同 WIRE id 下两条 LINE 段不共端点是常态(不同地方各连一段),都得 label 才不孤立。 - **用 `(label)` 不是 `(global_label)`**:实验过两种语法,single-sheet ERC 都判 dangling(因为这个 sheet 上只出现一次);语义上 EPRO2 net 全局,但 single-sheet 校验视角看不见跨 sheet,换 global_label 帮不上。Phase 3 hierarchical 重构时再切。 - **不做 ERC config tweak**:KiCad 8 的 connection grid 是硬编码 50 mil,schematic 文件里没法配;想消 endpoint_off_grid 必须破坏 EPRO2 几何或者升级到 KiCad 9 + custom severity。 ### 测试 41 → 46 单测全过:新增 `test_pin_y_negated_for_kicad_lib_y_up_convention` / `test_pin_rotation_mirrored_to_compensate_y_flip` / `test_rect_y_negated` / `test_named_wire_emits_label_at_line_start` / `test_unnamed_wire_emits_no_label`。 ### 下一步建议 - Phase 3 hierarchical:写 root .kicad_sch 引用所有子 sheet + 把跨 sheet 的 NET 升级成 `(global_label)`,single-sheet ERC 残留的 248 + 111 大概率随之降到 < 100。 - (并行) 消 lib_symbol_issues 571:emit `sym-lib-table` + 独立 .kicad_sym。 --- ## 2026-04-28 23:30 oshwhub 全量 listing 索引落本地:33,695 项 / 28.4 MB **Claude 会话** 为了在"扩量到 top-30 / top-50 / 全量"前先量化候选池规模 + 质量分布,把 oshwhub listing API 全量扫一遍落地。 ### 关键收获(之前以为是黑箱) - **listing API 直接返回 `total` 字段**:Pro 21,202 / Std 12,493,**合计 33,695**。 - **`pageSize` 无上限**,实测 1000 工作良好;全量索引 = 35 次请求 / 71 秒 / 35 MB 流量。 - **`sort` 参数被服务端静默忽略**——传啥都返回相同顺序(grade desc → 隐式质量分 desc)。"按时间排序"必须先拉全集再本地排。 - **`origin` 默认 std**——不带参数永远看不到 Pro 池。 - **`license` 不在 listing 响应**,必须挨个抓详情页(QPS=0.5 → ~19 小时全量)。 ### 数据画像(写到 `docs/sources/oshwhub_listing_full.md`) - **Pro 长尾极重**:grade=1 占 82%,真正 A 档(grade≥3 & like≥10)只有 1,356 (6.4%) - **Std 高质量比例反而高**:A 档 1,450 (11.6%),因为平台老 7 年(2016 起 vs Pro 2021 起),项目有时间累积点赞 - **Std 已停滞**:2021-2022 见顶(3.4k/年),之后断崖(1.5k → 0.9k → 0.4k → 0.05k 2026Q1) - **Pro 还在快速膨胀**:2023 起线性增长,2025 全年 7.4k,2026Q1 已 1.1k - **作者长尾健康**:Pro 10,536 个 / Std 5,531 个唯一作者;top-1 占比 0.4% / 1.5% - **立创官方账号占据头部**(course-examples / li-chuang-kai-fa-ban / li-chuang-zhi-neng-ying-jian-bu) ### 实操含义 放量决策有了量化锚点:S 档 583 项 / A 档 2,806 项 / B 档 6,243 项 / 全量 33,695。Pro 工程源体积外推(基于 5 项实测均值),全 Pro 约 1 TB——超出 Gitea LFS 舒适区,必须配 size cap + license 白名单。 ### 下一步 1. 在本地 jsonl 上按 A 档过滤,做 license 详情页扫描(一次性 ~7 小时) 2. license 白名单 ∩ A 档 → 真候选清单 3. 然后再决定批量下载源 ### 文件 - `scripts/dump_listing_index.py` —— 一次性全量扫描脚本,可重抓 - `data/state/oshwhub_listing_full.jsonl` —— 28.4 MB,gitignore(可重建,不入库) - `docs/sources/oshwhub_listing_full.md` —— 给人看的简报 --- ## 2026-04-28 22:00 Pro 2.x 旧版工程源抓取链路打通,5/5 Pro 项目全部 ✅ **Claude 会话** 承接刚做的 3/5 modern Pro 批量。用户录了第三份 HAR(`tmp/prodownload3.har` 103 MB / 178 请求,目标是登录态打开**梁山派** legacy Pro 2.x 项目),让我把 RK3566 / 梁山派两个旧版项目也补上。 ### HAR 反推:Pro 2.x 用一套完全不同的端点 旧版(`editorVersion: 2.1.x`)没有 git-style branch + history 模型。HAR 里看到的关键端点: | 端点 | 作用 | |---|---| | `GET /api/projects/

/ticket?uuid=

&g_ticket=-1` | 完整项目 manifest(schematics / schs / pcbs / coppers / textpath / blobs / symbols / footprints / devices / block_symbol) | | `POST /api/schematic/lists {uuids:[]}` | 父 schematic 容器,含 `sort: [{uuid: , ticket}]` 即子图 UUIDs | | `POST /api/v2/documents/lists {uuids:[...], docType:1}` | 抓 schematic 子图(**plaintext** dataStr,跟 Std 一样的 `["DOCTYPE","SCH","1.1"]\n[HEAD,...]` 格式) | | `POST /api/v2/documents/lists {uuids:[...], docType:3}` | 抓 PCB(同样 plaintext dataStr) | | `POST /api/coppers/search {paths:[...]}` | 铺铜层数据(PCB 上独立分发的增量层) | | `POST /api/textpath/search {paths, project_uuid, path}` | 字体 / 文字路径(同上) | | `POST /api/v2/resources/search {hash, project_uuid}` | BLOB(嵌入图片) | | `POST /api/v2/components/searchByIds` / `/api/devices/searchByIds` | 元件库 metadata(其 `dataStrId` 仍走 modules.lceda.cn AES 加密 blob) | **关键差异**:旧版的工程**主体 plaintext**(无加密 / 无 history 重放),只有元件库走 Pro 3.x 的 AES 方案。这反而比 3.x 简单很多。 **判别规则**:`/api/v4/projects/

` 返回的 `branch_uuid` 是不是 null。null 即旧版。 ### 实现 `crawlers/oshwhub/crawler.py` refactor: - `fetch_pro_source()` 拆成 dispatcher(先 GET project meta → 检查 `branch_uuid`) - `_fetch_pro_modern()` —— 原 EPRO2 chain 流程,去掉重复的 project meta 调用 - `_fetch_pro_legacy()` —— 新增,按上面 9 步流程拉所有 doc + 辅助层 - `_pro_post_json()` —— POST helper(与 GET helper 对称) 落盘约定(旧版): - `source/ticket.json` —— 完整 manifest(保留备 lib 重建) - `source/.json` —— 每张原理图 - `source/pcb_.json` —— 每块 PCB - `source/coppers.json` / `textpath.json` / `blobs.json` —— 辅助 PCB 层资源 - `source/manifest.json` —— 索引 + structure_summary schema:`source_format` enum 加 `easyeda-pro-legacy`。 ### 实测:2/2 legacy 项目打通 | 项目 | editor | sheets | pcbs | sym | fp | dev | coppers | textpath | blobs | size | |---|---|---|---|---|---|---|---|---|---|---| | 立创·梁山派 | 2.1.30 | 2 | 1 | 78 | 191 | 128 | 29 | 3 | 1 | 1.0 MB | | 立创·泰山派 RK3566 | 2.1.40 | 29 | 1 | 299 | 524 | 295 | 0 | 0 | 32 | 0.8 MB | **旧版项目比新版小两个数量级**(梁山派 1 MB vs RK3576 66 MB)—— 没有增量 history、组件库走单独端点、本身就是当前快照。 ### 5/5 Pro 项目终极汇总 | # | 项目 | source_format | editor | docs | size | |---|---|---|---|---|---| | 1 | X86 主板 | easyeda-pro | 3.2.15 | 7374 | 481 MB | | 2 | 立创·泰山派 RK3566 | easyeda-pro-legacy | 2.1.40 | 30 | 0.8 MB | | 3 | 立创·梁山派 | easyeda-pro-legacy | 2.1.30 | 3 | 1.0 MB | | 4 | 220V 桌面电源 | easyeda-pro | 3.2.69 | 771 | 26 MB | | 5 | ESP-VoCat 喵伴 | easyeda-pro | 3.2.91 | 278 | 7.5 MB | 合计 8456 docs / ~516 MB plain 源数据,5/5 端到端打通。 ### 下一步 1. EPRO2 → KiCad 转换器(仍是 Forge 投影硬门槛,ESP-VoCat 7.5 MB 是不错的小样本起手) 2. 旧版 dataStr → KiCad 复用 Std 转换链(同格式,已有 `easyeda2kicad.py` 支持) 3. 阶梯放量到 50 / 500 项目时做风控压测 --- ## 2026-04-28 21:35 Pro 工程源(EPRO2)批量抓取打通:3/5 modern Pro 项目 ✅,2/5 legacy 2.x ❌ **Claude 会话** 承接刚做完 Std 链路;用户给了浏览器 HAR (`tmp/prodownload.har`,174 请求,目标 = 立创·泰山派 RK3576 现代 Pro 项目) 验证 endpoint 形态,要求"找 5 个专业版项目,看能不能批量抓 EPRO2"。 ### 5 个候选 按 `oshwhub /api/project?origin=pro&sort=hot` 取前 5(详情 API 拿 license): | # | UUID | License | 项目 | 结果 | |---|---|---|---|---| | 1 | `b77840665e2e...` | GPL 3.0 | 【全网首发】X86电脑主板 | ✅ | | 2 | `7360e73de5dd...` | GPL 3.0 | 立创·泰山派 RK3566 开发板 | ❌ legacy 2.x | | 3 | `0c4675983733...` | GPL 3.0 | 立创·梁山派开发板 | ❌ legacy 2.x | | 4 | `dc91a91e6693...` | CC BY-NC-SA 4.0 | (高颜值)220V 300W 桌面电源 | ✅ | | 5 | `ba64bd6f1c9c...` | GPL 3.0 | ESP-VoCat 喵伴 AI 萌宠 | ✅ | License 全是限制性(CC-NC-SA / GPL)— Pro 用户群是立创团队/教育机构,默认上 NC-SA。和用户对齐:本仓库为研究用、不再分发,license 字段忠实落库;下游 Forge 投影时再用白名单过滤。 ### 关键发现:Pro 2.x ≠ Pro 3.x(重要!) 立创开发板的旗舰板(RK3566 / 梁山派)抓 `/api/v4/projects/` 返回 **`branch_uuid: null`** + `editorVersion: "2.1.40"`。 这是 **Pro 编辑器旧版(2.x)**:没有 git-style branch/history 模型,文档直接通过 `boards: [{sch, name, pcb}]` 字段定位。我们之前调研的全是 3.x(泰山派 RK3576 / 无界 PLUS),3.x 才有 `/branches//structures` + `/histories/` 全套。 旧版的访问端点暂未挖通:`/api/v4/documents/` 404;`/api/documents/` 401;`/api/v4/projects/

/snapshots` 200 但响应体是 project meta 不是 doc。需要录一份"在 pro.lceda.cn 编辑器里打开 RK3566"的 HAR 才能继续。已记入 `docs/sources/easyeda_pro_source.md §1.1`。 ### EPRO2 解析坑:行末单 `|`(找了 2 轮才看到) 第一轮跑 5 个项目结果:X86 board 7374 docs 抽出来只剩 **2 个**,220V 电源和 ESP-VoCat 都是 **0 docs**。 定位过程: 1. 直接 dump 一条 history 的 lines,看到 DOCHEAD payload **行末有单个 `|`**,例如 `{"docType":"BOARD",...,"version":"..."}|`。 2. 我的解析 `json.loads(ln.split(b"||")[1])` 拿到带尾随 `|` 的字符串 → `Extra data: line 1 column 127`。 3. silently swallow exception → `cur_doc` 没设 → 全 chain 的 message 被丢弃。 4. 修复:解析前先 `ln.rstrip(b"|").split(b"||")`。已在 `docs/sources/easyeda_pro_source.md §3.1` 记录"行末单 `|` 是行终止符不是字段分隔符"。 ### 修复后批量结果 3 个 modern Pro 项目完整解出来: | 项目 | chain | docs | plain | blob | editor | |---|---|---|---|---|---| | ESP-VoCat | 12 | 278 | 7.5 MB | 1.1 MB | 3.2.91 | | 220V 电源 | 28 | 771 | 26.3 MB | 7.4 MB | 3.2.69 | | X86 主板 | 85 | 7374 | **481 MB** | 61 MB | 3.2.15 | X86 主板(5123 FOOTPRINT + 1243 DEVICE + 837 SYMBOL = 7203 个组件库 doc)数据量惊人,是个超复杂工程。 ### docType 取值表(实测扩展) 之前 doc 只列了 `BOARD/PCB/SCHEMATIC/SHEET`。实测 EPRO2 流里 docType 实际取值更细: - 用户级文档:`BOARD`(板物理边框)+ `PCB`(板内容)+ `SCH`(原理图)+ `SCH_PAGE`(子图)。一个完整 PCB 板 = 一对 `BOARD` + `PCB`,不是命名变化。 - 组件库 / 资源:`SYMBOL` / `FOOTPRINT` / `DEVICE` / `BLOB` / `FONT` / `CONFIG`。每个独特组件 / 字体 / 项目配置都是独立 doc。 - 抓 EPRO2 = 抓项目 + **完整的局部组件库快照**。下游做 EPRO2 → KiCad 转换时必须先把 lib doc 加载进 symbol cache。 已更新到 `docs/sources/easyeda_pro_source.md §3.4`。 ### 代码改动 - `crawlers/oshwhub/crawler.py`: - 新增 `make_pro_source_client()` —— 加载 `~/.secrets/pro-lceda-cookie-header.txt`,自动配 `Editor-Version` / `Referer` / per-request `path` header - 新增 `fetch_pro_source(client, project_uuid, proj_dir, sleep)` —— 4 步流程:project meta → branch HEAD → structures → history chain,然后逐 history 解密(AES-128-GCM, 16 字节 IV)+ gunzip + 按 DOCHEAD 切 per-doc - 新增 `_order_history_chain()` —— 沿 parent 链从 root 到 HEAD 排序 - 新增 `_pro_get_json()` —— 包装 `/api/v4` GET 调用,自动加 `path` header + 校验 success - 扩展 `crawl_one()` 接 `pro_source_client` 参数,按 `list_item.origin` 自动 dispatch - 新增 CLI flag `--with-pro-source` / `--backfill-pro-source` / `--pro-cookie` / `--origin` - 新增 `_run_backfill_pro_source()`(filter on `raw_fields.origin == "pro"`) - `schemas/project.schema.json`:`docType` 类型从 `integer` 放宽到 `["integer","string","null"]`(兼容 Std 的 1/3 + Pro 的 BOARD/PCB/SCH 等字符串),新增 `message_count` 字段 - `docs/sources/easyeda_pro_source.md` rev 3:加 §1.1 Pro 2.x vs 3.x、§3.1 行末 `|` 警告、§3.4 docType 实测表、§2.2 单 history endpoint 即返完整 chain ### 数据落地 ``` data/raw/oshwhub/ b77840665e2e48148c1b04ce84b5f7e7/ # X86 主板(modern Pro 3.2.15) source/ manifest.json # 7374 docs index structure.json # 项目树(boards/schematics/sheets/pcbs) .epro2 # 7374 个 EPRO2 文件 dc91a91e669349898d709a5ba02f5b5f/ # 220V 电源(modern Pro 3.2.69) ba64bd6f1c9c467ba3b674a54943557d/ # ESP-VoCat(modern Pro 3.2.91) 7360e73de5dd428e9f29e10573f2d8ac/ # legacy Pro 2.x,无 source/ 0c46759837334318aa4882d6d37f96fa/ # legacy Pro 2.x,无 source/ ``` ### 下一步 1. (重要)legacy Pro 2.x 抓取链:录 HAR 看 RK3566 / 梁山派 在 pro.lceda.cn 编辑器打开时走什么 endpoint 2. 想跑量到 50 / 500 项目时,先做风控测试:阶梯放量,监控 403 / 429 / 1111111 3. EPRO2 → KiCad 转换器是 Forge 投影前置硬门槛 4. 可考虑 cookie 轮换 / 多账号 pool(Pro 风控相对 Std 严) --- ## 2026-04-28 19:50 Std 工程源链路打通 + 10 板子 schematic/PCB 全部回填 **Claude 会话** 承接计划:把已抓 10 个板子的"需登录才能下载的原理图 + PCB"补齐。 ### 关键发现:根本不需要登录 10 个板子全是 `origin: "std"`(EasyEDA 标准版)。原 `plan.md §1.6` 假设源数据要登录 — 实际**公开项目的 `dataStr` 匿名可访**。 调研路径(4 轮探测,留痕在 `data/state/std_probe[1-5]/`): 1. `/api/user`、`/api/projects` 401(cookie 已过期,但和源抓取无关) 2. **`oshwhub.com/api/project/` 浏览器 UA 匿名 200**,返回 `version_documents[]`(含 doc uuid + master + history chain) 3. `modules.lceda.cn/histories/.json` 仍 403(与 Pro 同结构,但 Std 不走这条路) 4. 翻 Std 编辑器 `/editor/6.5.51/js/main.min.js`(5 MB)grep `/api/`,找到 76 个端点。关键的 `ajaxDetail = '/api/documents/{uuid}'` 5. 命中:**`https://lceda.cn/api/documents/?uuid=&path=`** 匿名 200,body 是完整 EasyEDA JSON(`dataStr.{head,canvas,shape,BBox,colors[layers,objects,DRCRULE,...]}`) **两种响应 shape**(按 docType 区分): - docType=1 (Schematic):返"项目视图",`result.schematics[0].dataStr` - docType=3 (PCB):返"文档视图",`result.dataStr` 直接在顶层 ### 与 Pro 的差异 | 维度 | Std (本轮) | Pro (`docs/sources/easyeda_pro_source.md`) | |---|---|---| | 鉴权 | **无需** | `lceda_pro_session` 必须 | | 加密 | **无** | AES-128-GCM + gzip | | 源格式 | 扁平 EasyEDA JSON(`shape[]`) | EPRO2 消息流(事件溯源) | | 多 doc | `version_documents[]` 逐个 GET | `/structures` + history chain 重放 | ### 实施 - `docs/sources/easyeda_std_source.md`:完整调研(含 dataStr 字段、抓取伪代码、附录重跑脚本) - `crawlers/oshwhub/crawler.py`: - 新增 `make_source_client()` —— 浏览器 UA + Referer,规避 oshwhub `/api/project/` 端点对 `FacereDataset/0.1` UA 的 reject(在 commit message 注明 UA 例外原因) - 新增 `fetch_std_source()`:项目 → version_documents → 逐文档 dataStr → 落 `source/.json` + `source/manifest.json` - 新增 `--with-source` 标志(爬新项目时一并抓源)和 `--backfill-source`(仅扫已有项目补源) - QPS ≤ 0.2(`SLEEP_SOURCE = 5.0s`) - `schemas/project.schema.json`:加 `source_format`/`source_path`/`source_documents`/`editor_version` 字段(前 3 个进 enum 锁定,后续新源好对齐) ### 跑批结果(dev1,QPS 0.2) 10/10 全成功;schema 验证 10/10 pass: | 项目 | docs | docTypes | 大小 | editor | |---|---|---|---|---| | ST-LINK V2-1 | 2 | [1,3] | 682 KB | 6.5.39 | | USB 电压电流表 | **4** | **[3]** | 1.2 MB | 6.5.15 | | 红外热成像 | 2 | [1,3] | 1.6 MB | 6.5.22 | | t12-858d 焊台 | **11** | [1,3] | 6.1 MB | 6.5.15 | | 加热台量产计划 | 6 | [1,3] | **12.0 MB** | 6.5.43 | | ESP32-S3 智能手表 | 4 | [1,3] | 1.4 MB | 6.5.41 | | RT300-MKV 可调电源 | 3 | [1,3] | 3.3 MB | 6.5.23 | | YuzuMaix V831 | 4 | [1,3] | 5.4 MB | 6.5.37 | | 盖革计数器 | 6 | [1,3] | 1.2 MB | 6.5.47 | | ZVS 感应加热 | 3 | [1,3] | 990 KB | 6.5.40 | **合计**:45 个文档 / 33.2 MB(中位 ~1.5 MB / 项目,附件主体约为附件主流量的 6%) 观察: - USB 电压电流表只有 PCB 文档(4 个:主板 + 盖板 + 底板 + 面板,作者未上传原理图源) - t12 焊台 11 个文档(拆得碎,估计含多个独立模块) - editor 版本散布在 6.5.15 - 6.5.47(取决于作者上一次保存项目时的客户端版本) ### 落盘结构(per project) ``` data/raw/oshwhub// ├── metadata.json # ★ 新增字段:source_format/source_path/source_documents/editor_version ├── description.md ├── cover.* ├── _urls.json ├── files/ # 用户附件(已存在,LFS) └── source/ # ★ 新:EasyEDA Std 工程源 ├── manifest.json # 文档清单 + 抓取时间 + upstream version_documents 留档 └── .json # 完整 dataStr 响应(普通 git,文件 1-5 MB 量级) ``` `source/*.json` **走普通 git** 而不是 LFS(10 项目共 33 MB 完全够用;放量时再考虑加 LFS 规则)。 ### 安全 / 合规 - 无登录态、无凭据使用 → 无账号封禁风险,无 cookie 泄漏顾虑 - UA 例外:源抓取使用浏览器 UA 而非 `FacereDataset/0.1`,原因写入 `docs/sources/easyeda_std_source.md §3` 与本 commit message - License:与原项目附件相同的 license 字段已在 metadata,下游 whitelist 过滤逻辑不变 ### 未决 / 下一步 1. dev1 上的 `~/.secrets/lceda.json` cookie 已过期(XSRF 4-22 失效,今天 4-28),但**本任务已不依赖**它。是否保留待定 —— Pro 流程可能仍要 2. `easyeda2kicad.py` 转换:现有 45 个 dataStr 是关键测试样本,可立刻跑(`plan.md §1.7`) 3. 放量决策:从 10 → 50 → 全量 12,493 时,按 33 MB / 10 ≈ 3.3 MB/proj 估算,全量源 ~40 GB(不含附件本体) 4. 多账号轮询、Pro 链路打通仍是 `plan.md §1.6` 的开放项(仅当遇到 Pro 项目时才用得上) ### 改动清单 - 新增:`docs/sources/easyeda_std_source.md`、`scripts/probe_std_api[1-5].py`、`data/raw/oshwhub//source/`(10 项目) - 修改:`crawlers/oshwhub/crawler.py`、`schemas/project.schema.json`、10 项目的 `metadata.json` - 待人工:`projects.md` 重生成(脚本未跑);`plan.md §1.6` 状态从 ⏳ → ✅,§1.7 unblocked --- ## 2026-04-24 00:25 打通 pro.lceda.cn 工程源完整链 + EPRO2 格式解析 **Claude 会话** 核心成果:**立创 EDA Pro 工程源的 API + 加密 + 格式三层全打通**。 ### 完整链路 ``` 1. GET /api/v4/projects/ → branch_uuid 2. GET /api/v4/projects//branches/ → history_uuid 3. GET /api/v4/projects//branches//histories/ → {key, iv, dataStrUrl} 4. GET (modules.lceda.cn) → 417 KB 加密 blob 5. AES-128-GCM decrypt (tag=blob[-16:]) → 417 KB gzip 6. gunzip → 2.7 MB EPRO2 源流 ``` 关键 headers:`Editor-Version: 3.2.127` / `path: ` / `Referer: https://pro.lceda.cn/editor` / `Cookie: lceda_pro_session=...`(与 u.lceda.cn 的 session 不共享) ### 加密细节 - 算法: AES-128-GCM(从 `modules.lceda.cn/pro-mgr/.../project-worker.js` 里 `this.tool.decrypt({name:"AES-GCM",iv:this.iv,tagLength:128},...)` 反查确认) - key / iv 都是 32 hex = 16 byte - WebCrypto 约定:ciphertext || 16-byte-authTag(末尾附) - 解密后 gzip magic `1f 8b 08`,gunzip 得最终源流 ### EPRO2 格式 立创 EDA Pro 2 的**事件溯源**格式:消息流按 `\n` 分行,每行 `{"type":...,"ticket":N,"id":...}||{payload}||[extra]`。 示例样本(`无界PLUS` BOARD 文档)**8 357 条消息**,**40 种 type**: - PART / COMPONENT / ATTR / PIN(零件与属性) - PAD / VIA / WIRE / NET / PAD_NET(PCB 电气) - LINE / POLY / RECT / ARC / CIRCLE / ELLIPSE / TEXT(几何) - LAYER (1572) / LAYER_PHYS / ACTIVE_LAYER(层堆叠) - FILL / POUR / POURED(铺铜) - RULE / RULE_SELECTOR / RULE_TEMPLATE(设计规则) ### 与 Std 版对比 | | u.lceda.cn (Std) | pro.lceda.cn (Pro) | |---|---|---| | Cookie | `lceda_session` | `lceda_pro_session` | | 源 API | 单一 `/api/projects/` | 4 步 `/api/v4/...` 链 | | 版本控制 | 无 | branches + histories | | 加密 | 待验证 | AES-128-GCM | | 源格式 | EasyEDA JSON(扁平) | EPRO2 消息流 | | 工具 | `easyeda2kicad.py` 第三方 | **无**现成 KiCad 转换器 | ### 落地 - 新建 `docs/sources/easyeda_pro_source.md`(完整调研,见该文档附录 A 一键重跑) - `pyproject.toml` 加 `pycryptodome>=3.23.0` - 清理:dev1 上 `/tmp/source.blob` 与 `/tmp/source.json`(后者含 Charles 私人工程源 2.7 MB) ### 待验证 / 下一步 1. **他人公开 Pro 工程**能否同样 4 步通 —— 需 HAR 2. **SCHEMATIC docType** 的 API 入口(本次只解出 BOARD) 3. **多 document 枚举**(project → documents 列表端点) 4. Pro 编辑器"**导出 KiCad**"功能的 API 端点(若存在,能省自写转换器的工作) 5. 对齐 `OSHWHUB_INGEST_SPEC.md`(Forge 消费侧要求 `.kicad_sch` + 更严 license whitelist) ### ⚠️ 安全 Charles 在聊天里粘过两次 cookie(u.lceda.cn 一次 + pro.lceda.cn 一次),已写入 dev1 `~/.secrets/`。 当前会话 transcript 含明文 —— 本轮验证完 Charles 应登出再重登一次,让测试期间暴露过的 session invalidate。 --- ## 2026-04-23 20:10 策略大调:登录内容入场 + 云服务器 + EDA→KiCad 转换 **Claude 会话** 四项变更落实到文档(暂不写代码,等云服务器到位): ### 1. 登录态内容纳入范围 原则(`CLAUDE.md`): - 合法账号登录后抓,**禁止**盗号 / 共享号 - 凭据集中云服务器 `~/.secrets/` (mode 700),**不入** git / 日志 / metadata - 仍不绕付费墙、不破 DRM、不抓站点明确禁抓的内容 - 换号 / 重登事件记 `docs/secrets.md`(只事件、不含值) ### 2. 云服务器(广州,待交付) 新增 plan.md §0.5 基础设施段: - 0.5.1 机器初始化:git / git-lfs / uv / python 3.11+,非 root SSH,`~/.secrets/` - 0.5.2 调度:tmux/nohup 长跑 + systemd timer 增量 - 0.5.3 登录态获取:cookie 导出流程 ### 3. 存储分级演进 plan.md §1.4 改写: - 前期 < 50 GB:云服务器磁盘 + Gitea LFS - 中期 50–200 GB:评估 Gitea 容量压力;扩容 or 分仓 - 后期 > 200 GB:迁对象存储(OSS / COS / MinIO),Gitea 只存元数据 + 指针 - 50 GB 是决策评估点,**不**过早迁移 ### 4. 立创 EDA → KiCad 转换 新增 plan.md §1.6(登录态工程源抓取)+ §1.7(EDA→KiCad 转换): - §1.6 用登录账号抓 `u.lceda.cn/api/project/` 工程源 JSON,存 `source.json` - §1.7 写 `scripts/convert_to_kicad.py`,候选工具 `easyeda2kicad.py`(pypi,活跃维护) - 批处理扫 `data/raw/oshwhub/` → 输出 `data/processed/oshwhub//kicad/` - 跑 `kicad-cli sch erc / pcb drc` 做语法校验,失败样本记 `data/state/convert_failed.jsonl` - 目的:打通 oshwhub (EasyEDA) 与 bshada/open-schematics (KiCad) 两个生态的训练语料 ### 同步改动 - `docs/sources/oshwhub.md` §3.5 从"未开放"改为"需登录,纳入范围";R4 风险更新 - `README.md` 数据源表加「登录态」列,加运行环境说明 ### 等待 - 广州云服务器到位 → 启动 Phase 0.5 - 账号登录凭据由 Charles 提供 --- ## 2026-04-23 19:55 oshwhub.md 重写成完整调研文档 **Claude 会话** Charles 要求把 12 493 总数验证、90 项目采样结果合进主调研文档。 `docs/sources/oshwhub.md` 重写为 9 节 + 2 附录的完整调研: 1. 一页纸 TL;DR 表 2. 站点架构 3. robots.txt 与合规 4. API 与抓取入口(列表 / SSR 详情 / 附件 CDN / 排除项 / 未开放端点) 5. **项目总数验证(新)**:三路 sort 一致 + 分页二分搜索(250 × 50 = 12 500 吻合)+ grade 覆盖抽样 6. **抽样语料特征(从 oshwhub_corpus_estimate.md 并入)**:体积 / 文件类型 / license 分布 7. Schema 映射 8. 速率与礼貌 9. 目录输出约定 10. 风险与未解决(7 条) 11. 附录:重跑命令、变更历史 删除重复文件 `oshwhub_corpus_estimate.md`(内容已并入 §5)。 --- ## 2026-04-23 19:50 加入 HF bshada/open-schematics 计划 **Claude 会话** Charles 点名把 https://huggingface.co/datasets/bshada/open-schematics 纳入第一批。 调研结论: - 这是**已预处理**的 HF 数据集,非待爬网站 - 78 parquet shards **6.4 GB**;CC-BY-4.0(商用友好) - 10K+ 条记录,每条含 `.kicad_sch` 源 / PNG / 组件列表 / JSON / YAML / name / description - 与 oshwhub (EasyEDA) 互补,补 KiCad 生态 决定: - **整包镜像**到 `data/external/huggingface/bshada--open-schematics/`,**不**拆 per-project 目录 - 用 `huggingface-cli download ... --repo-type dataset` 拉;parquet 走 LFS - 维护单独的 `datasets.md`,不与 per-project 的 `projects.md` 混 改动: - 新增 `docs/sources/hf_bshada_open_schematics.md` 完整调研 - `plan.md` 加 Phase 1.5 - `README.md` 数据源表加一行 **未下载**,等拍板 6.4 GB LFS 预算。 --- ## 2026-04-23 19:30 Phase 1 MVP:10 个高质量 oshwhub 项目入库 **Claude 会话**:承接仓库初始化 ### API 调研结论 - 列表 API:`GET https://oshwhub.com/api/project?page=N&pageSize=M&sort=hot`,无鉴权,返回 12493 个项目元数据(含 grade / likes / stars / views / forks) - 详情:`GET https://oshwhub.com/` 是 SSR HTML,嵌入 escaped JSON,含 `license` + `attachments[]`(每个带 name / src / size / md5 / ext / mime / download_count) - 附件 CDN:`https://image.lceda.cn{src}` — 已验证无鉴权直接下载 - EasyEDA 工程源 JSON(`u.lceda.cn`)需登录,v0.1 不抓 - 详细调研见 `docs/sources/oshwhub.md` ### 选 10 个高质量项目 判据:`grade == 4`(平台精品徽章) + `likes ≥ 100` + 应用领域多样(避免同类堆叠)+ 排除 `_copy` 派生仓。 10 个项目覆盖:调试器、加热台、盖革计数器、可调电源、焊台、智能手表、USB 测电流、ZVS 感应加热、AI 开发板、红外热成像。 ### MVP 爬虫 位置:`crawlers/oshwhub/crawler.py` - `list_projects()` — 列表 API 分页 - `pick_top()` — 按 like×3 + star + fork×2 + views/100 + comments×2 + grade×50 排序 - `parse_detail_html()` — 从 SSR HTML 提取 title / license / description / attachments - `crawl_one()` — 每项目产出:`metadata.json` / `description.md` / `cover.*` / `files/*` / `_urls.json` - QPS ≤ 0.5(`SLEEP_BETWEEN = 2.0`),UA 显式声明 `FacereDataset/0.1` ### 抓取与入库 - 10/10 成功,52 个附件,**524 MB** - Gitea LFS(v25.4.3 原生支持)+ 本地 `git-lfs 3.5.1`(用户态二进制装在 `~/.local/bin/`) - `.gitattributes` 规则:`data/raw/**/files/**` 一律走 LFS;元数据(metadata.json / description.md / \_urls.json / cover.\*)走普通 git - 每项目目录结构: ``` data/raw/oshwhub// ├── metadata.json # 按 schemas/project.schema.json ├── description.md ├── cover.{jpg,png,jpeg} ├── _urls.json # 所有原始 URL 清单 └── files/* # 原始附件(LFS) ``` ### 改动汇总 - 新增:`crawlers/oshwhub/{__init__,__main__,crawler}.py`、`schemas/project.schema.json`、`docs/sources/oshwhub.md`、`pyproject.toml` - 修改:`.gitattributes`(缩窄到 `data/raw/**/files/**`)、`.gitignore`(移除 `data/raw/*` 排除) ### 下一步建议 1. 验收 10 个项目元数据质量(随机抽 2-3 条对照原站) 2. 决定 Phase 1.4 放量目标(50?500?全量 12493?) 3. Phase 2 准备:GitHub KiCad repo 调研 --- ## 2026-04-23 19:40 fs-web-stream 排查 + schema 自动校验 **Claude 会话**(自主推进) ### fs-web-stream.jlc.com 定性 重新抓 `/CYIIOT/ST_LINK-V2_1` 并看 13 个 `fs-web-stream.jlc.com` 链接的上下文:全部是嘉立创服务侧栏/推广图标(3D 打印、发热片、Ican、EDA 扩展广场、开源硬件平台 badge 等),**与项目本身无关**。`image.lceda.cn/attachments/` 就是项目附件的唯一入口,已确认闭环。`docs/sources/oshwhub.md` 对应章节已更新。 ### scripts/validate.py jsonschema 做两层校验: - 默认:所有 `data/raw/**/metadata.json` 对 `schemas/project.schema.json` 的结构校验 - `--check-files`:另外验证每条 file 的本地 path 存在且 sha256 匹配 **结果**:10/10 项目两项全通过。 ### 新增 - `scripts/validate.py` - `pyproject.toml` 加 `jsonschema>=4.26` ### 待决策 - 放量规模 —— 已提供实测数据:**median ≈ 110 GB,p90 上界 ≈ 660 GB,建议预算 150–180 GB**(见 `docs/sources/oshwhub_corpus_estimate.md`) - 是否需要抓 `u.lceda.cn` 的 EasyEDA 源 JSON(需登录,v0.1 跳过) --- ## 2026-04-23 19:45 全量规模实测 + License 分布 **Claude 会话**(自主推进) 写 `scripts/estimate_size.py`,只抓详情 HTML 解析 `attachments[].size`,不下载;采样 90 个 hot 项目(3 页 × 30)。 **关键发现**: - 单项目 median 9 MB / mean 22 MB / p90 54 MB / max 204 MB;12493 全量 median 估算 **110 GB**,p90 上界 660 GB - **视频 (.mp4 + .qt) 占 54% 存储**!如果训练只要 PCB/原理图/BOM,加 `--skip-ext mp4,qt` 存储直接砍半 - License 分布健康:GPL 3.0 占 49%,Public Domain 21%,CC 系列 ~20%,CERN/TAPR OHL 6%;样本内无闭源 - **NC (Non-Commercial) 占 ~11%**,商用场景必须过滤 结果固化到 `docs/sources/oshwhub_corpus_estimate.md`,可随时重跑验证。 ### 建议 1. 存储预算定 **180 GB**(median + 15% buffer) 2. Phase 1.4 前给 crawler 加 `--skip-ext` 开关滤视频 3. 下游建立 license whitelist 过滤 NC / 未知 --- ## 2026-04-23 18:50 仓库初始化 & 数据源调研 **Claude 会话**:初始化 完成: - 从 `git.deepknow.site/Facere/FacereDataset` 克隆空仓到 `~/repo/FacereDataset` - 调研立创开源平台(oshwhub.com)初步数据: - `robots.txt` 仅 Disallow `/posts`,其他路径允许 - 存在 `sitemap.xml`(首页 + explore + activities + market 等入口已列出) - 项目详情页路径为 `/detail/`(示例 `f0652fd2ae3e40b8a0ecc8dc773e3512`) - 图片 CDN:`image.lceda.cn/oshwhub/pullImage/...` - 文件下载:`fs-web-stream.jlc.com/fs-web-stream/file-operation/download/` - 页面是 Next.js SPA,首屏 HTML 800KB,但数据加载具体 API 入口需要浏览器 trace(留给 Phase 1.1) - 创建项目骨架: - `README.md` — 项目简介与数据源表 - `CLAUDE.md` — 项目级 Claude 指令(爬虫规约、合规红线、schema 要求) - `plan.md` — 6 阶段建设计划(Phase 0 骨架 → Phase 5 数据清洗 → Phase 6 持续运营) - `log.md` — 本文件 - `.gitignore` — 排除 `data/raw` `data/processed` `data/state` Python 缓存等 - 目录骨架 `crawlers/ schemas/ scripts/ data/{raw,processed,state} docs/{sources,}` - 每个空目录放 `.gitkeep` - 首次提交 & 推送到 `origin main` **下一步建议**: 1. 拍板存储方案(本地盘 / Gitea LFS / 外部 OSS)—— 影响 Phase 1.4 放量时机 2. 目标规模(1 万 / 10 万 / 全量) 3. 决定是否保留二进制附件或只存 URL 4. 完成上述 3 项后启动 Phase 1.1(用 `chrome-devtools` MCP 录 oshwhub 的 network trace 定位真实 API)