mirror of
https://github.com/jacobinwwey/obsidian-NotEMD.git
synced 2026-07-22 05:48:27 +00:00
feat: add cmos nor circuitikz family
This commit is contained in:
parent
6081a2d93f
commit
9c515d4fa4
16 changed files with 273 additions and 22 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -167,6 +167,7 @@ The repository now includes maintainer fixtures for every supported golden famil
|
|||
| `docs/maintainer/fixtures/circuitikz/common-source-nmos-v1.json` | `common-source-amplifier` |
|
||||
| `docs/maintainer/fixtures/circuitikz/cmos-inverter-v1.json` | `cmos-inverter` |
|
||||
| `docs/maintainer/fixtures/circuitikz/cmos-nand2-v1.json` | `cmos-nand2` |
|
||||
| `docs/maintainer/fixtures/circuitikz/cmos-nor2-v1.json` | `cmos-nor2` |
|
||||
|
||||
Run every fixture through the same explicit renderer configuration with:
|
||||
|
||||
|
|
@ -216,6 +217,7 @@ This is not a generic TikZ generator. The current prototype supports only golden
|
|||
| `common-source-amplifier` | `common-source-nmos-v1` | NMOS common-source amplifier with `R_D`, `VDD`, `vin`, `vout`, and grounded source |
|
||||
| `cmos-inverter` | `cmos-inverter-v1` | PMOS-over-NMOS CMOS inverter with shared gate input and shared drain output |
|
||||
| `cmos-nand2` | `cmos-nand2-v1` | Two-input CMOS NAND with parallel PMOS pull-up devices, series NMOS pull-down devices, `va`, `vb`, and `vout` |
|
||||
| `cmos-nor2` | `cmos-nor2-v1` | Two-input CMOS NOR with series PMOS pull-up devices, parallel NMOS pull-down devices, `va`, `vb`, and `vout` |
|
||||
|
||||
The adapter validates the structural invariant first, then emits a fixed layout. For example, the CMOS inverter requires:
|
||||
|
||||
|
|
@ -228,6 +230,8 @@ The adapter validates the structural invariant first, then emits a fixed layout.
|
|||
|
||||
The CMOS NAND template adds a stronger digital-logic invariant: `MPA` and `MPB` must be PMOS devices in the parallel pull-up network from `VDD` to `vout`; `MNA` and `MNB` must be NMOS devices in a series pull-down stack from `vout` to `GND`; `va` must drive `MPA.G` and `MNA.G`; and `vb` must drive `MPB.G` and `MNB.G`. `layoutHints.inputSide` and `layoutHints.outputSide` only move the input/output ports and presentation routing; they do not change the topology signature.
|
||||
|
||||
The CMOS NOR template mirrors the digital-logic constraint in the opposite networks: `MPA` and `MPB` must be PMOS devices in a series pull-up stack from `VDD` to `vout`; `MNA` and `MNB` must be NMOS devices in the parallel pull-down network from `vout` to `GND`; `va` must drive `MPA.G` and `MNA.G`; and `vb` must drive `MPB.G` and `MNB.G`. The same layout-hint projection rule applies: port placement can move, but the topology signature cannot.
|
||||
|
||||
## Why `CircuitSpec` Is Separate
|
||||
|
||||
`DiagramSpec` is intentionally not widened for circuit diagrams yet. Circuit diagrams need topology, terminal references, layout lanes, and package conventions that generic flowcharts and data charts do not use.
|
||||
|
|
@ -284,4 +288,4 @@ The tests verify:
|
|||
|
||||
## Non-Goals
|
||||
|
||||
This prototype does not bundle LaTeX, call TikZJax as an Obsidian runtime dependency, OCR path-only glyph text, run precise pixel-level overlap detection, cover unsupported SVG path geometry, or use rendered-image feedback for automatic repair. Those are later gates. It also does not accept arbitrary natural-language circuit requests. The important current claim is narrower: validated `CircuitSpec` input can produce stable, readable circuitikz for two high-value golden families, existing compile logs can be converted into actionable diagnostics, and an explicitly configured local renderer can be executed without shell-specific command parsing while optionally proving that a concrete output artifact was created and, for SVG or PNG output, structurally renderable enough for later visual inspection. SVG output now includes transform-aware geometry for common SVG transforms, exact arc bounds for A/a arc extrema, exact Bezier curve bounds for C/S/Q/T curve extrema, positioned `tspan` label geometry, path-only label classification, path-only glyph overlap detection, and conservative label-vs-drawing overlap detection; PNG output exposes foreground bounds, foreground density, and format-specific unsupported-export guidance needed to reject obvious canvas clipping, first-pass pixel crowding, Adam7 interlaced PNGs, and unsupported indexed-color bit depths before a topology-preserving repair loop.
|
||||
This prototype does not bundle LaTeX, call TikZJax as an Obsidian runtime dependency, OCR path-only glyph text, run precise pixel-level overlap detection, cover unsupported SVG path geometry, or use rendered-image feedback for automatic repair. Those are later gates. It also does not accept arbitrary natural-language circuit requests. The important current claim is narrower: validated `CircuitSpec` input can produce stable, readable circuitikz for four golden families, existing compile logs can be converted into actionable diagnostics, and an explicitly configured local renderer can be executed without shell-specific command parsing while optionally proving that a concrete output artifact was created and, for SVG or PNG output, structurally renderable enough for later visual inspection. SVG output now includes transform-aware geometry for common SVG transforms, exact arc bounds for A/a arc extrema, exact Bezier curve bounds for C/S/Q/T curve extrema, positioned `tspan` label geometry, path-only label classification, path-only glyph overlap detection, and conservative label-vs-drawing overlap detection; PNG output exposes foreground bounds, foreground density, and format-specific unsupported-export guidance needed to reject obvious canvas clipping, first-pass pixel crowding, Adam7 interlaced PNGs, and unsupported indexed-color bit depths before a topology-preserving repair loop.
|
||||
|
|
|
|||
|
|
@ -167,6 +167,7 @@ runner 位于 `src/diagram/adapters/circuitikz/circuitikzCompileRunner.ts`。它
|
|||
| `docs/maintainer/fixtures/circuitikz/common-source-nmos-v1.json` | `common-source-amplifier` |
|
||||
| `docs/maintainer/fixtures/circuitikz/cmos-inverter-v1.json` | `cmos-inverter` |
|
||||
| `docs/maintainer/fixtures/circuitikz/cmos-nand2-v1.json` | `cmos-nand2` |
|
||||
| `docs/maintainer/fixtures/circuitikz/cmos-nor2-v1.json` | `cmos-nor2` |
|
||||
|
||||
使用同一套显式 renderer 配置跑完所有 fixture:
|
||||
|
||||
|
|
@ -216,6 +217,7 @@ npm run diagram:smoke-circuitikz -- \
|
|||
| `common-source-amplifier` | `common-source-nmos-v1` | 带 `R_D`、`VDD`、`vin`、`vout` 和源极接地的 NMOS 共源放大器 |
|
||||
| `cmos-inverter` | `cmos-inverter-v1` | PMOS 在上、NMOS 在下,共用 gate 输入、共用 drain 输出的 CMOS 反相器 |
|
||||
| `cmos-nand2` | `cmos-nand2-v1` | 两输入 CMOS NAND,包含并联 PMOS 上拉、串联 NMOS 下拉、`va`、`vb` 与 `vout` |
|
||||
| `cmos-nor2` | `cmos-nor2-v1` | 两输入 CMOS NOR,包含串联 PMOS 上拉、并联 NMOS 下拉、`va`、`vb` 与 `vout` |
|
||||
|
||||
adapter 会先验证结构 invariant,再输出固定布局。例如 CMOS 反相器要求:
|
||||
|
||||
|
|
@ -228,6 +230,8 @@ adapter 会先验证结构 invariant,再输出固定布局。例如 CMOS 反
|
|||
|
||||
CMOS NAND template 进一步固定数字逻辑 invariant:`MPA` 与 `MPB` 必须是从 `VDD` 到 `vout` 的并联 PMOS pull-up network;`MNA` 与 `MNB` 必须是从 `vout` 到 `GND` 的串联 NMOS pull-down stack;`va` 必须同时驱动 `MPA.G` 与 `MNA.G`;`vb` 必须同时驱动 `MPB.G` 与 `MNB.G`。`layoutHints.inputSide` 与 `layoutHints.outputSide` 只移动输入/输出端口和展示 routing,不改变 topology signature。
|
||||
|
||||
CMOS NOR template 则锁定互补的数字逻辑 invariant:`MPA` 与 `MPB` 必须是从 `VDD` 到 `vout` 的串联 PMOS pull-up stack;`MNA` 与 `MNB` 必须是从 `vout` 到 `GND` 的并联 NMOS pull-down network;`va` 必须同时驱动 `MPA.G` 与 `MNA.G`;`vb` 必须同时驱动 `MPB.G` 与 `MNB.G`。同样只允许通过 layout hints 移动端口展示位置,不能改变 topology signature。
|
||||
|
||||
## 为什么 `CircuitSpec` 独立存在
|
||||
|
||||
当前不会为了电路图过早扩展 `DiagramSpec`。电路图需要拓扑、terminal references、layout lanes 和 package conventions;这些字段并不属于普通 flowchart 或 data chart。
|
||||
|
|
@ -284,4 +288,4 @@ npm test -- --runInBand src/tests/circuitikzExporter.test.ts src/tests/circuitik
|
|||
|
||||
## 非目标
|
||||
|
||||
这个原型不捆绑 LaTeX、不把 TikZJax 作为 Obsidian runtime 依赖调用、不 OCR 识别 path-only glyph text、不做精确 pixel-level overlap detection、不覆盖尚未支持的 SVG path geometry,也不使用渲染图像反馈进行自动修复。这些是后续 gate。它也不接受任意自然语言电路请求。当前重要声明更窄:经过验证的 `CircuitSpec` 输入可以为两个高价值 golden families 生成稳定、可读的 circuitikz,已有 compile logs 可以转换为 actionable diagnostics,并且显式配置的本地 renderer 可以在不走 shell-specific command parsing 的情况下执行,同时可选证明具体输出 artifact 已经创建;如果输出是 SVG 或 PNG,还能证明它具备进入后续视觉检查的基本结构。SVG 输出现在包含常见 SVG transforms 的 transform-aware geometry、针对 A/a arc extrema 的 exact arc bounds、针对 C/S/Q/T curve extrema 的 exact Bezier curve bounds、positioned `tspan` label geometry、path-only label classification、path-only glyph overlap detection 与保守 label-vs-drawing overlap detection;PNG 输出则会暴露前景包围盒、前景密度与 format-specific unsupported-export guidance,用于在 topology-preserving repair loop 之前拒绝明显画布裁剪、第一层 pixel crowding、Adam7 interlaced PNG 和 unsupported indexed-color bit depths。
|
||||
这个原型不捆绑 LaTeX、不把 TikZJax 作为 Obsidian runtime 依赖调用、不 OCR 识别 path-only glyph text、不做精确 pixel-level overlap detection、不覆盖尚未支持的 SVG path geometry,也不使用渲染图像反馈进行自动修复。这些是后续 gate。它也不接受任意自然语言电路请求。当前重要声明更窄:经过验证的 `CircuitSpec` 输入可以为四个 golden families 生成稳定、可读的 circuitikz,已有 compile logs 可以转换为 actionable diagnostics,并且显式配置的本地 renderer 可以在不走 shell-specific command parsing 的情况下执行,同时可选证明具体输出 artifact 已经创建;如果输出是 SVG 或 PNG,还能证明它具备进入后续视觉检查的基本结构。SVG 输出现在包含常见 SVG transforms 的 transform-aware geometry、针对 A/a arc extrema 的 exact arc bounds、针对 C/S/Q/T curve extrema 的 exact Bezier curve bounds、positioned `tspan` label geometry、path-only label classification、path-only glyph overlap detection 与保守 label-vs-drawing overlap detection;PNG 输出则会暴露前景包围盒、前景密度与 format-specific unsupported-export guidance,用于在 topology-preserving repair loop 之前拒绝明显画布裁剪、第一层 pixel crowding、Adam7 interlaced PNG 和 unsupported indexed-color bit depths。
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ Screenshot feedback can be manual first. Automated screenshot checks should star
|
|||
Phase A is documented. Phase B/C now have a constrained repository prototype:
|
||||
|
||||
- `src/diagram/adapters/circuitikz/circuitSpec.ts` defines the separate circuit-only spec boundary.
|
||||
- `src/diagram/adapters/circuitikz/circuitikzExporter.ts` validates topology and emits deterministic `circuitikz` LaTeX for `common-source-amplifier`, `cmos-inverter`, and `cmos-nand2` / `cmos-nand2-v1`. It now projects `layoutHints.inputSide` and `layoutHints.outputSide` into constrained port placement for those golden templates, so layout-only repair candidates can move input/output ports without changing topology. The CMOS NAND template locks the parallel PMOS pull-up network, the series NMOS pull-down stack, and dual input nets `va` / `vb` before export.
|
||||
- `src/diagram/adapters/circuitikz/circuitikzExporter.ts` validates topology and emits deterministic `circuitikz` LaTeX for `common-source-amplifier`, `cmos-inverter`, `cmos-nand2` / `cmos-nand2-v1`, and `cmos-nor2` / `cmos-nor2-v1`. It now projects `layoutHints.inputSide` and `layoutHints.outputSide` into constrained port placement for those golden templates, so layout-only repair candidates can move input/output ports without changing topology. The CMOS NAND template locks the parallel PMOS pull-up network, the series NMOS pull-down stack, and dual input nets `va` / `vb` before export; the CMOS NOR template locks the complementary series PMOS pull-up stack and parallel NMOS pull-down network before export.
|
||||
- `src/diagram/adapters/circuitikz/circuitikzExporter.ts` now also exposes `createCircuitTopologySignature` and `assertCircuitTopologyUnchanged` so topology-preserving repair can reject electrical drift while allowing label and layout changes.
|
||||
- `scripts/export-circuitikz.js` and `npm run diagram:export-circuitikz` provide the offline export command, including `--topology-reference` for repair candidates.
|
||||
- `src/diagram/adapters/circuitikz/circuitikzRepairBrief.ts`, `--repair-brief-output`, and `--repair-brief` produce and consume schema `notemd.circuitikz.repair-brief.v1`, a topology-preserving repair handoff that carries the source `CircuitSpec`, topology signature, compile/render diagnostics, allowed changes, prohibited topology changes, next verification steps, and a structured `repairPrompt`. The prompt role is `topology-preserving-circuitikz-repair`; it includes `diagnosticFocus` items derived from diagnostics and `acceptanceCriteria` that require candidate validation plus fresh compile and render-smoke checks. Candidate specs can be checked against the brief signature before output is written, and `--repair-brief` now returns `repairAcceptance` evidence with schema `notemd.circuitikz.repair-acceptance.v1`, `readyForVisualAcceptance`, gate-level status, blocking diagnostics, and `remainingChecks`. `--repair-acceptance-output` can persist the same evidence as a JSON file for CI or release records.
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ screenshot feedback 可以先保持人工执行。自动 screenshot 检查应从
|
|||
Phase A 已经文档化。Phase B/C 现在有了受约束的仓库内原型:
|
||||
|
||||
- `src/diagram/adapters/circuitikz/circuitSpec.ts` 定义独立的 circuit-only spec 边界。
|
||||
- `src/diagram/adapters/circuitikz/circuitikzExporter.ts` 会验证拓扑,并为 `common-source-amplifier`、`cmos-inverter` 和 `cmos-nand2` / `cmos-nand2-v1` 输出确定性的 `circuitikz` LaTeX。它现在会把 `layoutHints.inputSide` 与 `layoutHints.outputSide` 投影为这些 golden templates 的受约束 port placement,因此 layout-only repair candidates 可以移动输入/输出端口,而不改变拓扑。CMOS NAND template 会在导出前锁定并联 PMOS pull-up network、串联 NMOS pull-down stack 和双输入 nets `va` / `vb`。
|
||||
- `src/diagram/adapters/circuitikz/circuitikzExporter.ts` 会验证拓扑,并为 `common-source-amplifier`、`cmos-inverter`、`cmos-nand2` / `cmos-nand2-v1` 和 `cmos-nor2` / `cmos-nor2-v1` 输出确定性的 `circuitikz` LaTeX。它现在会把 `layoutHints.inputSide` 与 `layoutHints.outputSide` 投影为这些 golden templates 的受约束 port placement,因此 layout-only repair candidates 可以移动输入/输出端口,而不改变拓扑。CMOS NAND template 会在导出前锁定并联 PMOS pull-up network、串联 NMOS pull-down stack 和双输入 nets `va` / `vb`;CMOS NOR template 会在导出前锁定互补的串联 PMOS pull-up stack 与并联 NMOS pull-down network。
|
||||
- `src/diagram/adapters/circuitikz/circuitikzExporter.ts` 现在还暴露 `createCircuitTopologySignature` 与 `assertCircuitTopologyUnchanged`,让 topology-preserving repair 可以拒绝电气拓扑漂移,同时允许 label 与 layout 变化。
|
||||
- `scripts/export-circuitikz.js` 与 `npm run diagram:export-circuitikz` 提供离线导出命令,并通过 `--topology-reference` 支持 repair candidate。
|
||||
- `src/diagram/adapters/circuitikz/circuitikzRepairBrief.ts`、`--repair-brief-output` 与 `--repair-brief` 会生成并消费 schema `notemd.circuitikz.repair-brief.v1`,作为 topology-preserving repair handoff,携带 source `CircuitSpec`、topology signature、compile/render diagnostics、允许修改项、禁止拓扑修改项、下一步验证步骤和结构化 `repairPrompt`。prompt role 是 `topology-preserving-circuitikz-repair`;它包含从 diagnostics 派生的 `diagnosticFocus` 项,以及要求 candidate validation、重新 compile 和 render-smoke checks 的 `acceptanceCriteria`。candidate spec 可以在写出 output 前按 brief signature 执行校验,`--repair-brief` 现在还会返回 schema `notemd.circuitikz.repair-acceptance.v1` 的 `repairAcceptance` 证据,包含 `readyForVisualAcceptance`、gate-level status、blocking diagnostics 与 `remainingChecks`。`--repair-acceptance-output` 可以把同一份 evidence 持久化为 JSON 文件,供 CI 或 release 记录使用。
|
||||
|
|
|
|||
34
docs/maintainer/fixtures/circuitikz/cmos-nor2-v1.json
Normal file
34
docs/maintainer/fixtures/circuitikz/cmos-nor2-v1.json
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"circuitKind": "cmos-nor2",
|
||||
"title": "Two Input CMOS NOR Gate",
|
||||
"goldenReferenceId": "cmos-nor2-v1",
|
||||
"style": {
|
||||
"package": "circuitikz",
|
||||
"voltageConvention": "american voltages"
|
||||
},
|
||||
"nets": ["VDD", "GND", "va", "vb", "vout", "pull_up_mid"],
|
||||
"components": [
|
||||
{ "id": "MPA", "type": "pmos", "label": "$M_{PA}$", "terminals": { "S": "VDD", "G": "va", "D": "pull_up_mid" } },
|
||||
{ "id": "MPB", "type": "pmos", "label": "$M_{PB}$", "terminals": { "S": "pull_up_mid", "G": "vb", "D": "vout" } },
|
||||
{ "id": "MNA", "type": "nmos", "label": "$M_{NA}$", "terminals": { "D": "vout", "G": "va", "S": "GND" } },
|
||||
{ "id": "MNB", "type": "nmos", "label": "$M_{NB}$", "terminals": { "D": "vout", "G": "vb", "S": "GND" } }
|
||||
],
|
||||
"connections": [
|
||||
{ "from": "VDD", "to": "MPA.S" },
|
||||
{ "from": "MPA.D", "to": "MPB.S" },
|
||||
{ "from": "MPB.D", "to": "vout" },
|
||||
{ "from": "MNA.D", "to": "vout" },
|
||||
{ "from": "MNB.D", "to": "vout" },
|
||||
{ "from": "MNA.S", "to": "GND" },
|
||||
{ "from": "MNB.S", "to": "GND" },
|
||||
{ "from": "va", "to": "MPA.G" },
|
||||
{ "from": "va", "to": "MNA.G" },
|
||||
{ "from": "vb", "to": "MPB.G" },
|
||||
{ "from": "vb", "to": "MNB.G" }
|
||||
],
|
||||
"layoutHints": {
|
||||
"inputSide": "left",
|
||||
"outputSide": "right",
|
||||
"routingStyle": "orthogonal"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
export const SUPPORTED_CIRCUIT_KINDS = [
|
||||
'common-source-amplifier',
|
||||
'cmos-inverter',
|
||||
'cmos-nand2'
|
||||
'cmos-nand2',
|
||||
'cmos-nor2'
|
||||
] as const;
|
||||
|
||||
export type CircuitKind = typeof SUPPORTED_CIRCUIT_KINDS[number];
|
||||
|
|
|
|||
|
|
@ -46,12 +46,12 @@ function extendedPortX(side: 'left' | 'right'): string {
|
|||
return side === 'left' ? '0.8' : '5.2';
|
||||
}
|
||||
|
||||
function nandInputPortX(side: 'left' | 'right'): string {
|
||||
function dualInputPortX(side: 'left' | 'right'): string {
|
||||
return side === 'left' ? '0.8' : '5.2';
|
||||
}
|
||||
|
||||
function requiredNetsForCircuitKind(circuitKind: string): string[] {
|
||||
if (circuitKind === 'cmos-nand2') {
|
||||
if (circuitKind === 'cmos-nand2' || circuitKind === 'cmos-nor2') {
|
||||
return ['VDD', 'GND', 'va', 'vb', 'vout'];
|
||||
}
|
||||
|
||||
|
|
@ -286,6 +286,43 @@ function validateCmosNand2Template(
|
|||
}
|
||||
}
|
||||
|
||||
function validateCmosNor2Template(
|
||||
spec: CircuitSpec,
|
||||
componentById: Map<string, CircuitComponent>,
|
||||
errors: string[]
|
||||
): void {
|
||||
if (spec.goldenReferenceId !== 'cmos-nor2-v1') {
|
||||
errors.push('CMOS NOR requires goldenReferenceId "cmos-nor2-v1".');
|
||||
}
|
||||
|
||||
requireComponent(componentById, 'MPA', 'pmos', errors);
|
||||
requireComponent(componentById, 'MPB', 'pmos', errors);
|
||||
requireComponent(componentById, 'MNA', 'nmos', errors);
|
||||
requireComponent(componentById, 'MNB', 'nmos', errors);
|
||||
|
||||
if (!hasConnection(spec.connections, 'VDD', 'MPA.S')) {
|
||||
errors.push('CMOS NOR requires MPA.S to connect to VDD at the top of the pull-up stack.');
|
||||
}
|
||||
if (!hasConnection(spec.connections, 'MPA.D', 'MPB.S')) {
|
||||
errors.push('CMOS NOR requires MPA.D to connect to MPB.S for the series pull-up stack.');
|
||||
}
|
||||
if (!hasConnection(spec.connections, 'MPB.D', 'vout')) {
|
||||
errors.push('CMOS NOR requires MPB.D to connect to vout at the bottom of the pull-up stack.');
|
||||
}
|
||||
if (!hasConnection(spec.connections, 'MNA.D', 'vout') || !hasConnection(spec.connections, 'MNB.D', 'vout')) {
|
||||
errors.push('CMOS NOR requires both NMOS drains to connect to vout for the parallel pull-down network.');
|
||||
}
|
||||
if (!hasConnection(spec.connections, 'MNA.S', 'GND') || !hasConnection(spec.connections, 'MNB.S', 'GND')) {
|
||||
errors.push('CMOS NOR requires both NMOS sources to connect to GND for the parallel pull-down network.');
|
||||
}
|
||||
if (!hasConnection(spec.connections, 'va', 'MPA.G') || !hasConnection(spec.connections, 'va', 'MNA.G')) {
|
||||
errors.push('CMOS NOR requires va to drive both MPA.G and MNA.G.');
|
||||
}
|
||||
if (!hasConnection(spec.connections, 'vb', 'MPB.G') || !hasConnection(spec.connections, 'vb', 'MNB.G')) {
|
||||
errors.push('CMOS NOR requires vb to drive both MPB.G and MNB.G.');
|
||||
}
|
||||
}
|
||||
|
||||
export function validateCircuitSpec(spec: CircuitSpec): CircuitSpecValidationResult {
|
||||
const errors: string[] = [];
|
||||
|
||||
|
|
@ -328,6 +365,8 @@ export function validateCircuitSpec(spec: CircuitSpec): CircuitSpecValidationRes
|
|||
validateCmosInverterTemplate(spec, componentById, errors);
|
||||
} else if (spec.circuitKind === 'cmos-nand2') {
|
||||
validateCmosNand2Template(spec, componentById, errors);
|
||||
} else if (spec.circuitKind === 'cmos-nor2') {
|
||||
validateCmosNor2Template(spec, componentById, errors);
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
@ -434,23 +473,23 @@ function renderCmosNand2Template(spec: CircuitSpec): string {
|
|||
? ` (MPA.G) to [short] (1.7,4.2)
|
||||
(MNA.G) to [short] (1.7,2.7)
|
||||
(1.7,4.2) to [short] (1.7,2.7)
|
||||
to [short, -o] (${nandInputPortX(inputSide)},3.45)
|
||||
to [short, -o] (${dualInputPortX(inputSide)},3.45)
|
||||
node[left]{$v_A$}
|
||||
(MPB.G) to [short] (1.3,4.2)
|
||||
(MNB.G) to [short] (1.3,1.8)
|
||||
(1.3,4.2) to [short] (1.3,1.8)
|
||||
to [short, -o] (${nandInputPortX(inputSide)},2.7)
|
||||
to [short, -o] (${dualInputPortX(inputSide)},2.7)
|
||||
node[left]{$v_B$};`
|
||||
: ` (MPA.G) to [short] (4.1,4.2)
|
||||
(MNA.G) to [short] (4.1,2.7)
|
||||
(4.1,4.2) to [short] (4.1,2.7)
|
||||
to [short] (4.5,2.7)
|
||||
to [short, -o] (${nandInputPortX(inputSide)},4.15)
|
||||
to [short, -o] (${dualInputPortX(inputSide)},4.15)
|
||||
node[right]{$v_A$}
|
||||
(MPB.G) to [short] (4.5,4.2)
|
||||
(MNB.G) to [short] (4.5,1.8)
|
||||
(4.5,4.2) to [short] (4.5,1.8)
|
||||
to [short, -o] (${nandInputPortX(inputSide)},1.85)
|
||||
to [short, -o] (${dualInputPortX(inputSide)},1.85)
|
||||
node[right]{$v_B$};`;
|
||||
|
||||
return `\\usepackage{circuitikz}
|
||||
|
|
@ -482,6 +521,68 @@ ${inputRoute}
|
|||
`;
|
||||
}
|
||||
|
||||
function renderCmosNor2Template(spec: CircuitSpec): string {
|
||||
const mpa = spec.components.find(component => component.id === 'MPA');
|
||||
const mpb = spec.components.find(component => component.id === 'MPB');
|
||||
const mna = spec.components.find(component => component.id === 'MNA');
|
||||
const mnb = spec.components.find(component => component.id === 'MNB');
|
||||
const mpaLabel = normalizeLabel(mpa?.label, '$M_{PA}$');
|
||||
const mpbLabel = normalizeLabel(mpb?.label, '$M_{PB}$');
|
||||
const mnaLabel = normalizeLabel(mna?.label, '$M_{NA}$');
|
||||
const mnbLabel = normalizeLabel(mnb?.label, '$M_{NB}$');
|
||||
const inputSide = layoutSide(spec.layoutHints?.inputSide, 'left');
|
||||
const outputSide = layoutSide(spec.layoutHints?.outputSide, 'right');
|
||||
const inputRoute = inputSide === 'left'
|
||||
? ` (MPA.G) to [short] (1.7,4.35)
|
||||
(MNA.G) to [short] (1.7,1.95)
|
||||
(1.7,4.35) to [short] (1.7,1.95)
|
||||
to [short, -o] (${dualInputPortX(inputSide)},4.35)
|
||||
node[left]{$v_A$}
|
||||
(MPB.G) to [short] (1.3,3.35)
|
||||
(MNB.G) to [short] (1.3,1.45)
|
||||
(1.3,3.35) to [short] (1.3,1.45)
|
||||
to [short, -o] (${dualInputPortX(inputSide)},1.95)
|
||||
node[left]{$v_B$};`
|
||||
: ` (MPA.G) to [short] (4.1,4.35)
|
||||
(MNA.G) to [short] (4.1,1.95)
|
||||
(4.1,4.35) to [short] (4.1,1.95)
|
||||
to [short] (4.5,1.95)
|
||||
to [short, -o] (${dualInputPortX(inputSide)},4.35)
|
||||
node[right]{$v_A$}
|
||||
(MPB.G) to [short] (4.5,3.35)
|
||||
(MNB.G) to [short] (4.5,1.45)
|
||||
(4.5,3.35) to [short] (4.5,1.45)
|
||||
to [short, -o] (${dualInputPortX(inputSide)},1.95)
|
||||
node[right]{$v_B$};`;
|
||||
|
||||
return `\\usepackage{circuitikz}
|
||||
\\begin{document}
|
||||
\\begin{circuitikz}[${spec.style.voltageConvention}]
|
||||
\\draw
|
||||
(3,5) node[vcc]{$V_{DD}$}
|
||||
to [short] (3,4.8)
|
||||
node[pmos, anchor=S] (MPA) {${mpaLabel}}
|
||||
(MPA.D) to [short] (3,3.75)
|
||||
node[pmos, anchor=S] (MPB) {${mpbLabel}}
|
||||
(MPB.D) to [short] (3,2.75);
|
||||
\\draw
|
||||
(3,2.75) to [short, *-o] (${commonPortX(outputSide)},2.75) node[${outputSide}]{$v_{out}$};
|
||||
\\draw
|
||||
(3,2.75) to [short] (2.5,2.2)
|
||||
node[nmos, anchor=D] (MNA) {${mnaLabel}}
|
||||
(MNA.S) to [short] (2.5,0.7)
|
||||
(3,2.75) to [short] (3.5,2.2)
|
||||
node[nmos, anchor=D] (MNB) {${mnbLabel}}
|
||||
(MNB.S) to [short] (3.5,0.7)
|
||||
(2.5,0.7) to [short] (3.5,0.7)
|
||||
(3,0.7) node[ground]{};
|
||||
\\draw
|
||||
${inputRoute}
|
||||
\\end{circuitikz}
|
||||
\\end{document}
|
||||
`;
|
||||
}
|
||||
|
||||
export function exportCircuitSpecToCircuitikz(spec: CircuitSpec): string {
|
||||
assertValidCircuitSpec(spec);
|
||||
|
||||
|
|
@ -492,6 +593,8 @@ export function exportCircuitSpecToCircuitikz(spec: CircuitSpec): string {
|
|||
return renderCmosInverterTemplate(spec);
|
||||
case 'cmos-nand2':
|
||||
return renderCmosNand2Template(spec);
|
||||
case 'cmos-nor2':
|
||||
return renderCmosNor2Template(spec);
|
||||
default:
|
||||
throw new ValidationError('Unsupported circuitKind.', 'INVALID_INPUT');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,12 +41,15 @@ describe('circuitikz export docs contract', () => {
|
|||
expect(doc).toContain('cmos-inverter-v1');
|
||||
expect(doc).toContain('cmos-nand2');
|
||||
expect(doc).toContain('cmos-nand2-v1');
|
||||
expect(doc).toContain('cmos-nor2');
|
||||
expect(doc).toContain('cmos-nor2-v1');
|
||||
expect(doc).toContain('layoutHints.inputSide');
|
||||
expect(doc).toContain('layoutHints.outputSide');
|
||||
expect(doc).toContain('port placement');
|
||||
expect(doc).toContain('docs/maintainer/fixtures/circuitikz/common-source-nmos-v1.json');
|
||||
expect(doc).toContain('docs/maintainer/fixtures/circuitikz/cmos-inverter-v1.json');
|
||||
expect(doc).toContain('docs/maintainer/fixtures/circuitikz/cmos-nand2-v1.json');
|
||||
expect(doc).toContain('docs/maintainer/fixtures/circuitikz/cmos-nor2-v1.json');
|
||||
expect(doc).toContain('UTF-8');
|
||||
expect(doc).toContain('BOM');
|
||||
expect(doc).toContain('--compile-log');
|
||||
|
|
@ -131,6 +134,8 @@ describe('circuitikz export docs contract', () => {
|
|||
expect(doc).toContain('CircuitSpec -> circuitikz');
|
||||
expect(doc).toContain('cmos-nand2');
|
||||
expect(doc).toContain('cmos-nand2-v1');
|
||||
expect(doc).toContain('cmos-nor2');
|
||||
expect(doc).toContain('cmos-nor2-v1');
|
||||
expect(doc).toContain('layoutHints.inputSide');
|
||||
expect(doc).toContain('layoutHints.outputSide');
|
||||
expect(doc).toContain('port placement');
|
||||
|
|
@ -217,6 +222,8 @@ describe('circuitikz export docs contract', () => {
|
|||
expect(doc).toContain('circuitikzRepairBrief.ts');
|
||||
expect(doc).toContain('cmos-nand2');
|
||||
expect(doc).toContain('cmos-nand2-v1');
|
||||
expect(doc).toContain('cmos-nor2');
|
||||
expect(doc).toContain('cmos-nor2-v1');
|
||||
expect(doc).toContain('layoutHints.inputSide');
|
||||
expect(doc).toContain('layoutHints.outputSide');
|
||||
expect(doc).toContain('port placement');
|
||||
|
|
|
|||
|
|
@ -104,6 +104,43 @@ function createCmosNand2Spec(): CircuitSpec {
|
|||
};
|
||||
}
|
||||
|
||||
function createCmosNor2Spec(): CircuitSpec {
|
||||
return {
|
||||
circuitKind: 'cmos-nor2' as CircuitSpec['circuitKind'],
|
||||
title: 'Two Input CMOS NOR Gate',
|
||||
goldenReferenceId: 'cmos-nor2-v1',
|
||||
style: {
|
||||
package: 'circuitikz',
|
||||
voltageConvention: 'american voltages'
|
||||
},
|
||||
nets: ['VDD', 'GND', 'va', 'vb', 'vout', 'pull_up_mid'],
|
||||
components: [
|
||||
{ id: 'MPA', type: 'pmos', label: '$M_{PA}$', terminals: { S: 'VDD', G: 'va', D: 'pull_up_mid' } },
|
||||
{ id: 'MPB', type: 'pmos', label: '$M_{PB}$', terminals: { S: 'pull_up_mid', G: 'vb', D: 'vout' } },
|
||||
{ id: 'MNA', type: 'nmos', label: '$M_{NA}$', terminals: { D: 'vout', G: 'va', S: 'GND' } },
|
||||
{ id: 'MNB', type: 'nmos', label: '$M_{NB}$', terminals: { D: 'vout', G: 'vb', S: 'GND' } }
|
||||
],
|
||||
connections: [
|
||||
{ from: 'VDD', to: 'MPA.S' },
|
||||
{ from: 'MPA.D', to: 'MPB.S' },
|
||||
{ from: 'MPB.D', to: 'vout' },
|
||||
{ from: 'MNA.D', to: 'vout' },
|
||||
{ from: 'MNB.D', to: 'vout' },
|
||||
{ from: 'MNA.S', to: 'GND' },
|
||||
{ from: 'MNB.S', to: 'GND' },
|
||||
{ from: 'va', to: 'MPA.G' },
|
||||
{ from: 'va', to: 'MNA.G' },
|
||||
{ from: 'vb', to: 'MPB.G' },
|
||||
{ from: 'vb', to: 'MNB.G' }
|
||||
],
|
||||
layoutHints: {
|
||||
inputSide: 'left',
|
||||
outputSide: 'right',
|
||||
routingStyle: 'orthogonal'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
describe('circuitikz exporter', () => {
|
||||
test('validates and exports the common-source golden reference template deterministically', () => {
|
||||
const spec = createCommonSourceSpec();
|
||||
|
|
@ -209,6 +246,52 @@ describe('circuitikz exporter', () => {
|
|||
expect(() => exportCircuitSpecToCircuitikz(spec)).toThrow(/series pull-down stack/);
|
||||
});
|
||||
|
||||
test('validates and exports a constrained two-input CMOS NOR template deterministically', () => {
|
||||
const spec = createCmosNor2Spec();
|
||||
const output = exportCircuitSpecToCircuitikz(spec);
|
||||
|
||||
expect(assertValidCircuitSpec(spec)).toBe(spec);
|
||||
expect(output).toContain('\\begin{circuitikz}[american voltages]');
|
||||
expect(output).toContain('node[pmos, anchor=S] (MPA) {$M_{PA}$}');
|
||||
expect(output).toContain('node[pmos, anchor=S] (MPB) {$M_{PB}$}');
|
||||
expect(output).toContain('node[nmos, anchor=D] (MNA) {$M_{NA}$}');
|
||||
expect(output).toContain('node[nmos, anchor=D] (MNB) {$M_{NB}$}');
|
||||
expect(output).toContain('(MPA.D) to [short] (3,3.75)');
|
||||
expect(output).toContain('(MPB.D) to [short] (3,2.75)');
|
||||
expect(output).toContain('(3,2.75) to [short, *-o] (5,2.75) node[right]{$v_{out}$};');
|
||||
expect(output).toContain('node[left]{$v_A$}');
|
||||
expect(output).toContain('node[left]{$v_B$};');
|
||||
});
|
||||
|
||||
test('projects CMOS NOR layout hints into deterministic input and output ports', () => {
|
||||
const reference = createCmosNor2Spec();
|
||||
const spec = createCmosNor2Spec();
|
||||
spec.layoutHints = {
|
||||
inputSide: 'right',
|
||||
outputSide: 'left',
|
||||
routingStyle: 'orthogonal'
|
||||
};
|
||||
|
||||
const output = exportCircuitSpecToCircuitikz(spec);
|
||||
|
||||
expect(createCircuitTopologySignature(spec)).toBe(createCircuitTopologySignature(reference));
|
||||
expect(output).toContain('(3,2.75) to [short, *-o] (0.8,2.75) node[left]{$v_{out}$};');
|
||||
expect(output).toContain('to [short, -o] (5.2,4.35)');
|
||||
expect(output).toContain('node[right]{$v_A$}');
|
||||
expect(output).toContain('to [short, -o] (5.2,1.95)');
|
||||
expect(output).toContain('node[right]{$v_B$};');
|
||||
});
|
||||
|
||||
test('rejects CMOS NOR specs that do not keep the PMOS pull-up stack in series', () => {
|
||||
const spec = createCmosNor2Spec();
|
||||
spec.connections = spec.connections.filter(connection => connection.from !== 'MPA.D' || connection.to !== 'MPB.S');
|
||||
|
||||
expect(validateCircuitSpec(spec).errors).toContain(
|
||||
'CMOS NOR requires MPA.D to connect to MPB.S for the series pull-up stack.'
|
||||
);
|
||||
expect(() => exportCircuitSpecToCircuitikz(spec)).toThrow(/series pull-up stack/);
|
||||
});
|
||||
|
||||
test('projects common-source layout hints into deterministic input and output ports', () => {
|
||||
const reference = createCommonSourceSpec();
|
||||
const spec = createCommonSourceSpec();
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ describe('circuitikz smoke fixtures CLI', () => {
|
|||
expect(fixtureNames).toEqual([
|
||||
'cmos-inverter-v1.json',
|
||||
'cmos-nand2-v1.json',
|
||||
'cmos-nor2-v1.json',
|
||||
'common-source-nmos-v1.json'
|
||||
]);
|
||||
|
||||
|
|
@ -58,7 +59,7 @@ describe('circuitikz smoke fixtures CLI', () => {
|
|||
const report = JSON.parse(result.stdout);
|
||||
expect(report).toEqual(expect.objectContaining({
|
||||
ok: false,
|
||||
fixtureCount: 3,
|
||||
fixtureCount: 4,
|
||||
reportOutputPath: reportPath,
|
||||
rendererAvailability: expect.objectContaining({
|
||||
ok: false,
|
||||
|
|
@ -137,12 +138,13 @@ fs.writeFileSync(
|
|||
const report = JSON.parse(stdout);
|
||||
expect(report).toEqual(expect.objectContaining({
|
||||
ok: true,
|
||||
fixtureCount: 3,
|
||||
fixtureCount: 4,
|
||||
outputDirectory
|
||||
}));
|
||||
expect(report.fixtures.map((fixture: any) => fixture.name).sort()).toEqual([
|
||||
'cmos-inverter-v1',
|
||||
'cmos-nand2-v1',
|
||||
'cmos-nor2-v1',
|
||||
'common-source-nmos-v1'
|
||||
]);
|
||||
|
||||
|
|
@ -162,7 +164,7 @@ fs.writeFileSync(
|
|||
.trim()
|
||||
.split(/\r?\n/)
|
||||
.map(line => JSON.parse(line));
|
||||
expect(invocations).toHaveLength(3);
|
||||
expect(invocations).toHaveLength(4);
|
||||
for (const invocation of invocations) {
|
||||
expect(invocation[0]).toBe(compilerPath);
|
||||
expect(invocation).toContain('--tex');
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@ describe('website documentation contract', () => {
|
|||
expect(content).toContain('cmos-inverter-v1');
|
||||
expect(content).toContain('cmos-nand2');
|
||||
expect(content).toContain('cmos-nand2-v1');
|
||||
expect(content).toContain('cmos-nor2');
|
||||
expect(content).toContain('cmos-nor2-v1');
|
||||
expect(content).toContain('layoutHints.inputSide');
|
||||
expect(content).toContain('layoutHints.outputSide');
|
||||
expect(content).toContain('port placement');
|
||||
|
|
@ -139,6 +141,7 @@ describe('website documentation contract', () => {
|
|||
expect(content).toContain('docs/maintainer/fixtures/circuitikz/common-source-nmos-v1.json');
|
||||
expect(content).toContain('docs/maintainer/fixtures/circuitikz/cmos-inverter-v1.json');
|
||||
expect(content).toContain('docs/maintainer/fixtures/circuitikz/cmos-nand2-v1.json');
|
||||
expect(content).toContain('docs/maintainer/fixtures/circuitikz/cmos-nor2-v1.json');
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -213,13 +213,14 @@ Notemd now includes the first constrained repository prototype for this directio
|
|||
npm run diagram:export-circuitikz -- --input cmos-inverter.json --output cmos-inverter.tex
|
||||
```
|
||||
|
||||
The prototype adds a separate `CircuitSpec` boundary and deterministic exporter for three golden-reference families:
|
||||
The prototype adds a separate `CircuitSpec` boundary and deterministic exporter for four golden-reference families:
|
||||
|
||||
| Circuit kind | Golden reference | Current guarantee |
|
||||
|--------------|------------------|-------------------|
|
||||
| `common-source-amplifier` | `common-source-nmos-v1` | validates `VDD -> R_D -> M1.D`, `vin -> M1.G`, `M1.S -> GND`, and `M1.D -> vout` before writing LaTeX |
|
||||
| `cmos-inverter` | `cmos-inverter-v1` | validates PMOS-over-NMOS topology, shared gate input, shared drain output, `VDD -> MP.S`, and `MN.S -> GND` before writing LaTeX |
|
||||
| `cmos-nand2` | `cmos-nand2-v1` | validates parallel PMOS pull-up, series NMOS pull-down, dual inputs `va` / `vb`, and `vout` before writing LaTeX |
|
||||
| `cmos-nor2` | `cmos-nor2-v1` | validates series PMOS pull-up, parallel NMOS pull-down, dual inputs `va` / `vb`, and `vout` before writing LaTeX |
|
||||
|
||||
This is not a general TikZ generator yet. It does not compile LaTeX, call TikZJax, inspect screenshots, or run automated image-feedback repair. Those remain later gates.
|
||||
|
||||
|
|
@ -295,7 +296,7 @@ For release or maintainer evidence, run every supported golden family through th
|
|||
npm run diagram:smoke-circuitikz -- --output-dir docs/export/circuitikz-smoke --compile-executable pdflatex --compile-arg -interaction=nonstopmode --compile-arg -halt-on-error --compile-arg -output-directory={outputDir} --compile-arg {tex} --expected-artifact {outputDir}/{jobName}.pdf
|
||||
```
|
||||
|
||||
The runner uses `docs/maintainer/fixtures/circuitikz/common-source-nmos-v1.json`, `docs/maintainer/fixtures/circuitikz/cmos-inverter-v1.json`, and `docs/maintainer/fixtures/circuitikz/cmos-nand2-v1.json`, calls the same shell-free exporter path for each fixture, and returns an aggregate JSON report with per-fixture `compileExecution` and `compileDiagnostics`. It is still a maintainer command, not a plugin runtime dependency.
|
||||
The runner uses `docs/maintainer/fixtures/circuitikz/common-source-nmos-v1.json`, `docs/maintainer/fixtures/circuitikz/cmos-inverter-v1.json`, `docs/maintainer/fixtures/circuitikz/cmos-nand2-v1.json`, and `docs/maintainer/fixtures/circuitikz/cmos-nor2-v1.json`, calls the same shell-free exporter path for each fixture, and returns an aggregate JSON report with per-fixture `compileExecution` and `compileDiagnostics`. It is still a maintainer command, not a plugin runtime dependency.
|
||||
|
||||
When a maintainer machine has no renderer configured yet, run the same fixture command without `--compile-executable` and persist the environment gate explicitly:
|
||||
|
||||
|
|
@ -344,7 +345,7 @@ For a CMOS inverter, the prompt should request an explicit topology plus layout
|
|||
| General diagrams | Spec-first pipeline implemented for Mermaid, JSON Canvas, Vega-Lite, HTML | Keep expanding semantic verification coverage |
|
||||
| Editable figures | `editable-html-svg`, Draw.io XML, and Drawnix JSON artifact boundaries implemented | Add richer primitives only after tests prove editability |
|
||||
| CLI support | `npm run diagram:export-artifact` exports editable HTML/SVG, Draw.io, and Drawnix from one `DiagramSpec` | Add target-specific smoke fixtures when new targets ship |
|
||||
| circuitikz | `CircuitSpec -> circuitikz` prototype exports common-source, CMOS inverter, and `cmos-nand2` / `cmos-nand2-v1` golden templates, projects `layoutHints.inputSide` and `layoutHints.outputSide` into deterministic input/output port placement without changing topology, rejects repair topology drift through `--topology-reference`, emits topology-preserving repair briefs through `--repair-brief-output` and schema `notemd.circuitikz.repair-brief.v1`, includes structured `repairPrompt` handoff content with `diagnosticFocus`, `acceptanceCriteria`, and role `topology-preserving-circuitikz-repair`, validates repair candidates through `--repair-brief`, returns `repairAcceptance` gate evidence through schema `notemd.circuitikz.repair-acceptance.v1` with `readyForVisualAcceptance` and `remainingChecks`, persists that evidence through `--repair-acceptance-output`, parses compile logs, can run explicit local renderers plus `--expected-artifact`, SVG `--expected-svg-text`, accessibility metadata checks through `aria-label`, `<title>`, and `<desc>`, hidden/transparent SVG element exclusion, `render-svg-text-path-only` / `pathOnlyGlyphUseCount` classification for path-only labels, path-only glyph placement checks for `<use href="#...">`, path-only glyph overlap diagnostics through `render-svg-path-glyph-overlap`, close-path current-point handling for `Z/z`, exact arc bounds for A/a arc extrema, exact Bezier curve bounds for C/S/Q/T curve extrema, stroke-width-aware SVG bounds and label overlap checks, `polyline` / `polygon` drawing geometry checks, positioned `tspan` label geometry, `text-anchor`-aware positioned text geometry, transform-aware geometry for SVG bounded-canvas/text-overlap and label-vs-drawing smoke through `render-svg-label-overlap`, and PNG nonblank / clipped / dense-foreground screenshot smoke checks, including indexed-color palette alpha, grayscale/RGB tRNS transparent samples, and format-specific `render-png-unsupported` guidance for Adam7 interlaced PNGs and indexed bit-depth failures, through `foregroundBounds`, `foregroundDensity`, `render-png-content-clipped`, and `render-png-foreground-dense` without shell parsing, includes aggregate maintainer smoke fixtures through `npm run diagram:smoke-circuitikz`, records missing renderer configuration through `rendererAvailability.status: "missing-configuration"` and `compile-executable-invalid`, and has generic preview diagnostics, diagnostic summary counts, diagnostics-aware history entries, and source-only fallback through `RenderArtifact.diagnostics` and the preview modal | Add OCR-level label recognition for path-only visual text, precise pixel-level overlap checks, broader SVG path coverage where needed, automatic renderer installation/discovery only if it can remain optional, and automated topology-preserving repair execution |
|
||||
| circuitikz | `CircuitSpec -> circuitikz` prototype exports common-source, CMOS inverter, `cmos-nand2` / `cmos-nand2-v1`, and `cmos-nor2` / `cmos-nor2-v1` golden templates, projects `layoutHints.inputSide` and `layoutHints.outputSide` into deterministic input/output port placement without changing topology, rejects repair topology drift through `--topology-reference`, emits topology-preserving repair briefs through `--repair-brief-output` and schema `notemd.circuitikz.repair-brief.v1`, includes structured `repairPrompt` handoff content with `diagnosticFocus`, `acceptanceCriteria`, and role `topology-preserving-circuitikz-repair`, validates repair candidates through `--repair-brief`, returns `repairAcceptance` gate evidence through schema `notemd.circuitikz.repair-acceptance.v1` with `readyForVisualAcceptance` and `remainingChecks`, persists that evidence through `--repair-acceptance-output`, parses compile logs, can run explicit local renderers plus `--expected-artifact`, SVG `--expected-svg-text`, accessibility metadata checks through `aria-label`, `<title>`, and `<desc>`, hidden/transparent SVG element exclusion, `render-svg-text-path-only` / `pathOnlyGlyphUseCount` classification for path-only labels, path-only glyph placement checks for `<use href="#...">`, path-only glyph overlap diagnostics through `render-svg-path-glyph-overlap`, close-path current-point handling for `Z/z`, exact arc bounds for A/a arc extrema, exact Bezier curve bounds for C/S/Q/T curve extrema, stroke-width-aware SVG bounds and label overlap checks, `polyline` / `polygon` drawing geometry checks, positioned `tspan` label geometry, `text-anchor`-aware positioned text geometry, transform-aware geometry for SVG bounded-canvas/text-overlap and label-vs-drawing smoke through `render-svg-label-overlap`, and PNG nonblank / clipped / dense-foreground screenshot smoke checks, including indexed-color palette alpha, grayscale/RGB tRNS transparent samples, and format-specific `render-png-unsupported` guidance for Adam7 interlaced PNGs and indexed bit-depth failures, through `foregroundBounds`, `foregroundDensity`, `render-png-content-clipped`, and `render-png-foreground-dense` without shell parsing, includes aggregate maintainer smoke fixtures through `npm run diagram:smoke-circuitikz`, records missing renderer configuration through `rendererAvailability.status: "missing-configuration"` and `compile-executable-invalid`, and has generic preview diagnostics, diagnostic summary counts, diagnostics-aware history entries, and source-only fallback through `RenderArtifact.diagnostics` and the preview modal | Add OCR-level label recognition for path-only visual text, precise pixel-level overlap checks, broader SVG path coverage where needed, automatic renderer installation/discovery only if it can remain optional, and automated topology-preserving repair execution |
|
||||
| TikZJax integration | Candidate render host for Obsidian-side display | Keep it optional; do not make TikZJax a hard plugin runtime dependency |
|
||||
|
||||
## Configuration
|
||||
|
|
|
|||
|
|
@ -213,13 +213,14 @@ Notemd 现在已经包含这个方向的第一个受约束仓库内原型。它
|
|||
npm run diagram:export-circuitikz -- --input cmos-inverter.json --output cmos-inverter.tex
|
||||
```
|
||||
|
||||
这个原型新增了独立的 `CircuitSpec` 边界,并为三个 golden-reference families 提供确定性 exporter:
|
||||
这个原型新增了独立的 `CircuitSpec` 边界,并为四个 golden-reference families 提供确定性 exporter:
|
||||
|
||||
| Circuit kind | Golden reference | Current guarantee |
|
||||
|--------------|------------------|-------------------|
|
||||
| `common-source-amplifier` | `common-source-nmos-v1` | 写出 LaTeX 前验证 `VDD -> R_D -> M1.D`、`vin -> M1.G`、`M1.S -> GND` 和 `M1.D -> vout` |
|
||||
| `cmos-inverter` | `cmos-inverter-v1` | 写出 LaTeX 前验证 PMOS-over-NMOS 拓扑、shared gate input、shared drain output、`VDD -> MP.S` 和 `MN.S -> GND` |
|
||||
| `cmos-nand2` | `cmos-nand2-v1` | 写出 LaTeX 前验证并联 PMOS pull-up、串联 NMOS pull-down、双输入 `va` / `vb` 和 `vout` |
|
||||
| `cmos-nor2` | `cmos-nor2-v1` | 写出 LaTeX 前验证串联 PMOS pull-up、并联 NMOS pull-down、双输入 `va` / `vb` 和 `vout` |
|
||||
|
||||
这还不是通用 TikZ 生成器。它不会编译 LaTeX、调用 TikZJax、检查截图或运行自动化图像反馈修复;这些仍是后续 gate。
|
||||
|
||||
|
|
@ -295,7 +296,7 @@ npm run diagram:export-circuitikz -- --input repaired-cmos-inverter.json --repai
|
|||
npm run diagram:smoke-circuitikz -- --output-dir docs/export/circuitikz-smoke --compile-executable pdflatex --compile-arg -interaction=nonstopmode --compile-arg -halt-on-error --compile-arg -output-directory={outputDir} --compile-arg {tex} --expected-artifact {outputDir}/{jobName}.pdf
|
||||
```
|
||||
|
||||
runner 使用 `docs/maintainer/fixtures/circuitikz/common-source-nmos-v1.json`、`docs/maintainer/fixtures/circuitikz/cmos-inverter-v1.json` 和 `docs/maintainer/fixtures/circuitikz/cmos-nand2-v1.json`,对每个 fixture 调用同一条 shell-free exporter path,并返回包含每个 fixture 的 `compileExecution` 与 `compileDiagnostics` 的聚合 JSON report。它仍然是 maintainer command,不是插件 runtime 依赖。
|
||||
runner 使用 `docs/maintainer/fixtures/circuitikz/common-source-nmos-v1.json`、`docs/maintainer/fixtures/circuitikz/cmos-inverter-v1.json`、`docs/maintainer/fixtures/circuitikz/cmos-nand2-v1.json` 和 `docs/maintainer/fixtures/circuitikz/cmos-nor2-v1.json`,对每个 fixture 调用同一条 shell-free exporter path,并返回包含每个 fixture 的 `compileExecution` 与 `compileDiagnostics` 的聚合 JSON report。它仍然是 maintainer command,不是插件 runtime 依赖。
|
||||
|
||||
如果维护者机器尚未配置 renderer,可以不传 `--compile-executable`,用同一个 fixture command 把环境 gate 明确持久化:
|
||||
|
||||
|
|
@ -344,7 +345,7 @@ npm run diagram:smoke-circuitikz -- --output-dir docs/export/circuitikz-smoke --
|
|||
| General diagrams | Mermaid、JSON Canvas、Vega-Lite、HTML 的 spec-first pipeline 已实现 | 继续扩展 semantic verification coverage |
|
||||
| Editable figures | `editable-html-svg`、Draw.io XML、Drawnix JSON artifact boundaries 已实现 | 只有在测试证明 editability 后再加入更丰富 primitives |
|
||||
| CLI support | `npm run diagram:export-artifact` 可从同一个 `DiagramSpec` 导出 editable HTML/SVG、Draw.io、Drawnix | 新 target 发布时增加 target-specific smoke fixtures |
|
||||
| circuitikz | `CircuitSpec -> circuitikz` 原型已经可通过 `npm run diagram:export-circuitikz` 导出 common-source、CMOS inverter 和 `cmos-nand2` / `cmos-nand2-v1` golden templates、把 `layoutHints.inputSide` 与 `layoutHints.outputSide` 投影为不改变拓扑的确定性 input/output port placement、通过 `--topology-reference` 拒绝 repair topology drift、通过 `--repair-brief-output` 和 schema `notemd.circuitikz.repair-brief.v1` 写出 topology-preserving repair briefs、内含带 `diagnosticFocus`、`acceptanceCriteria` 与 role `topology-preserving-circuitikz-repair` 的结构化 `repairPrompt` handoff、通过 `--repair-brief` 校验 repair candidates、通过 schema `notemd.circuitikz.repair-acceptance.v1` 返回带 `readyForVisualAcceptance` 与 `remainingChecks` 的 `repairAcceptance` gate evidence、通过 `--repair-acceptance-output` 持久化这份 evidence、解析 compile logs,并在不经过 shell parsing 的情况下运行显式本地 renderer、`--expected-artifact`、SVG `--expected-svg-text`、通过 `aria-label`、`<title>` 与 `<desc>` 执行 accessibility metadata checks、hidden/transparent SVG element exclusion、用于 path-only labels 的 `render-svg-text-path-only` / `pathOnlyGlyphUseCount` 分类、针对 `<use href="#...">` 的 path-only glyph placement checks、通过 `render-svg-path-glyph-overlap` 执行的 path-only glyph overlap diagnostics、针对 `Z/z` 的 close-path current-point handling、针对 A/a arc extrema 的 exact arc bounds、针对 C/S/Q/T curve extrema 的 exact Bezier curve bounds、stroke-width-aware SVG bounds 与 label overlap checks、`polyline` / `polygon` drawing geometry checks、positioned `tspan` label geometry、`text-anchor`-aware positioned text geometry、带 transform-aware geometry 的 SVG bounded-canvas / text-overlap 以及通过 `render-svg-label-overlap` 报告的 label-vs-drawing smoke,以及通过 `foregroundBounds`、`foregroundDensity`、`render-png-content-clipped` 和 `render-png-foreground-dense` 执行的 PNG nonblank / clipped / dense-foreground screenshot smoke checks,包含 indexed-color palette alpha、grayscale/RGB tRNS transparent samples,以及针对 Adam7 interlaced PNG 与 indexed bit-depth failures 的 format-specific `render-png-unsupported` guidance;现在还通过 `npm run diagram:smoke-circuitikz` 提供聚合 maintainer smoke fixtures,通过 `rendererAvailability.status: "missing-configuration"` 与 `compile-executable-invalid` 记录缺失 renderer 配置,并通过 `RenderArtifact.diagnostics` 和 preview modal 具备通用 preview diagnostics、diagnostic summary counts、diagnostics-aware history entries 与 source-only fallback | 增加 path-only visual text 的 OCR-level label recognition、精确 pixel-level overlap checks、按需补齐更完整 SVG path coverage;只有能保持可选时才考虑 automatic renderer installation/discovery;继续推进自动化 topology-preserving repair execution |
|
||||
| circuitikz | `CircuitSpec -> circuitikz` 原型已经可通过 `npm run diagram:export-circuitikz` 导出 common-source、CMOS inverter、`cmos-nand2` / `cmos-nand2-v1` 和 `cmos-nor2` / `cmos-nor2-v1` golden templates、把 `layoutHints.inputSide` 与 `layoutHints.outputSide` 投影为不改变拓扑的确定性 input/output port placement、通过 `--topology-reference` 拒绝 repair topology drift、通过 `--repair-brief-output` 和 schema `notemd.circuitikz.repair-brief.v1` 写出 topology-preserving repair briefs、内含带 `diagnosticFocus`、`acceptanceCriteria` 与 role `topology-preserving-circuitikz-repair` 的结构化 `repairPrompt` handoff、通过 `--repair-brief` 校验 repair candidates、通过 schema `notemd.circuitikz.repair-acceptance.v1` 返回带 `readyForVisualAcceptance` 与 `remainingChecks` 的 `repairAcceptance` gate evidence、通过 `--repair-acceptance-output` 持久化这份 evidence、解析 compile logs,并在不经过 shell parsing 的情况下运行显式本地 renderer、`--expected-artifact`、SVG `--expected-svg-text`、通过 `aria-label`、`<title>` 与 `<desc>` 执行 accessibility metadata checks、hidden/transparent SVG element exclusion、用于 path-only labels 的 `render-svg-text-path-only` / `pathOnlyGlyphUseCount` 分类、针对 `<use href="#...">` 的 path-only glyph placement checks、通过 `render-svg-path-glyph-overlap` 执行的 path-only glyph overlap diagnostics、针对 `Z/z` 的 close-path current-point handling、针对 A/a arc extrema 的 exact arc bounds、针对 C/S/Q/T curve extrema 的 exact Bezier curve bounds、stroke-width-aware SVG bounds 与 label overlap checks、`polyline` / `polygon` drawing geometry checks、positioned `tspan` label geometry、`text-anchor`-aware positioned text geometry、带 transform-aware geometry 的 SVG bounded-canvas / text-overlap 以及通过 `render-svg-label-overlap` 报告的 label-vs-drawing smoke,以及通过 `foregroundBounds`、`foregroundDensity`、`render-png-content-clipped` 和 `render-png-foreground-dense` 执行的 PNG nonblank / clipped / dense-foreground screenshot smoke checks,包含 indexed-color palette alpha、grayscale/RGB tRNS transparent samples,以及针对 Adam7 interlaced PNG 与 indexed bit-depth failures 的 format-specific `render-png-unsupported` guidance;现在还通过 `npm run diagram:smoke-circuitikz` 提供聚合 maintainer smoke fixtures,通过 `rendererAvailability.status: "missing-configuration"` 与 `compile-executable-invalid` 记录缺失 renderer 配置,并通过 `RenderArtifact.diagnostics` 和 preview modal 具备通用 preview diagnostics、diagnostic summary counts、diagnostics-aware history entries 与 source-only fallback | 增加 path-only visual text 的 OCR-level label recognition、精确 pixel-level overlap checks、按需补齐更完整 SVG path coverage;只有能保持可选时才考虑 automatic renderer installation/discovery;继续推进自动化 topology-preserving repair execution |
|
||||
| TikZJax integration | Obsidian-side display 的候选 render host | 保持可选,不把 TikZJax 变成硬 runtime 依赖 |
|
||||
|
||||
## Configuration
|
||||
|
|
|
|||
|
|
@ -359,6 +359,7 @@ function auditDiagramDocs() {
|
|||
assertContains(englishDiagrams, 'CircuitSpec', 'English diagrams doc');
|
||||
assertContains(englishDiagrams, 'cmos-inverter-v1', 'English diagrams doc');
|
||||
assertContains(englishDiagrams, 'cmos-nand2-v1', 'English diagrams doc');
|
||||
assertContains(englishDiagrams, 'cmos-nor2-v1', 'English diagrams doc');
|
||||
assertContains(englishDiagrams, '--compile-log', 'English diagrams doc');
|
||||
assertContains(englishDiagrams, '--diagnostics-output', 'English diagrams doc');
|
||||
assertContains(englishDiagrams, '--compile-executable', 'English diagrams doc');
|
||||
|
|
@ -382,6 +383,7 @@ function auditDiagramDocs() {
|
|||
assertContains(zhCnDiagrams, 'CircuitSpec', 'zh-CN diagrams doc');
|
||||
assertContains(zhCnDiagrams, 'cmos-inverter-v1', 'zh-CN diagrams doc');
|
||||
assertContains(zhCnDiagrams, 'cmos-nand2-v1', 'zh-CN diagrams doc');
|
||||
assertContains(zhCnDiagrams, 'cmos-nor2-v1', 'zh-CN diagrams doc');
|
||||
assertContains(zhCnDiagrams, '--compile-log', 'zh-CN diagrams doc');
|
||||
assertContains(zhCnDiagrams, '--diagnostics-output', 'zh-CN diagrams doc');
|
||||
assertContains(zhCnDiagrams, '--compile-executable', 'zh-CN diagrams doc');
|
||||
|
|
|
|||
Loading…
Reference in a new issue