Skip to main content

pedalkernel_layout/
kicad.rs

1//! KiCad `.kicad_sch` schematic export from layout data.
2//!
3//! Generates a KiCad 7+ schematic file with components placed at the layout
4//! positions and wires routed along the layout paths. This gives users a
5//! readable schematic — not the tangled mess that KiCad's default import
6//! produces.
7
8use crate::types::*;
9use pedalkernel::compiler::Component;
10use pedalkernel::dsl::PedalDef;
11use std::fmt::Write;
12
13/// Scale factor from layout units to KiCad mils (1 layout unit ≈ 5 mils).
14const SCALE: f32 = 5.0;
15
16/// Export a layout + pedal definition to KiCad `.kicad_sch` format.
17pub fn export_kicad_schematic(layout: &Layout, pedal: &PedalDef) -> String {
18    let mut out = String::with_capacity(8192);
19
20    write_header(&mut out, layout);
21    write_components(&mut out, layout, pedal);
22    write_wires(&mut out, layout);
23    write_power_symbols(&mut out, layout);
24    write_footer(&mut out);
25
26    out
27}
28
29fn write_header(out: &mut String, layout: &Layout) {
30    let w = layout.bounds.width * SCALE;
31    let h = layout.bounds.height * SCALE;
32
33    writeln!(
34        out,
35        "(kicad_sch (version 20230121) (generator pedalkernel-layout)"
36    )
37    .unwrap();
38    writeln!(out).unwrap();
39    writeln!(out, "  (uuid \"00000000-0000-0000-0000-000000000001\")").unwrap();
40    writeln!(out, "  (paper \"A3\")").unwrap();
41    writeln!(out).unwrap();
42    writeln!(
43        out,
44        "  (title_block\n    (title \"{name}\")\n    (comment 1 \"Generated by PedalKernel Layout Engine\")\n    (comment 2 \"Canvas: {w:.0} x {h:.0} mils\")\n  )",
45        name = "PedalKernel Circuit",
46    )
47    .unwrap();
48    writeln!(out).unwrap();
49}
50
51fn write_components(out: &mut String, layout: &Layout, pedal: &PedalDef) {
52    // Build a lookup from component name to ComponentDef
53    let comp_map: std::collections::BTreeMap<&str, &pedalkernel::dsl::ComponentDef> = pedal
54        .components
55        .iter()
56        .map(|c| (c.id.as_str(), c))
57        .collect();
58
59    for (idx, placed) in layout.components.iter().enumerate() {
60        let comp_def = comp_map.get(placed.name.as_str());
61        let (lib_symbol, ref_prefix) = comp_def
62            .map(|c| kicad_symbol_ref(c.kind.as_ref()))
63            .unwrap_or(("Device:R", "X"));
64
65        let x = placed.x * SCALE;
66        let y = placed.y * SCALE;
67        let angle = placed.orientation as f32;
68
69        writeln!(
70            out,
71            "  (symbol (lib_id \"{lib_symbol}\") (at {x:.1} {y:.1} {angle:.0})"
72        )
73        .unwrap();
74        writeln!(
75            out,
76            "    (uuid \"{:08x}-0000-0000-0000-{:012x}\")",
77            idx + 1,
78            idx + 1
79        )
80        .unwrap();
81        writeln!(
82            out,
83            "    (property \"Reference\" \"{ref_prefix}{name}\" (at {x:.1} {y_ref:.1} 0)",
84            name = placed.name,
85            y_ref = y - 15.0
86        )
87        .unwrap();
88        writeln!(out, "      (effects (font (size 1.27 1.27)))").unwrap();
89        writeln!(out, "    )").unwrap();
90
91        if let Some(label) = &placed.label {
92            writeln!(
93                out,
94                "    (property \"Value\" \"{label}\" (at {x:.1} {y_val:.1} 0)",
95                y_val = y + 15.0
96            )
97            .unwrap();
98            writeln!(out, "      (effects (font (size 1.27 1.27)))").unwrap();
99            writeln!(out, "    )").unwrap();
100        }
101
102        writeln!(out, "  )").unwrap();
103        writeln!(out).unwrap();
104    }
105}
106
107fn write_wires(out: &mut String, layout: &Layout) {
108    for wire in &layout.wires {
109        for seg in wire.points.windows(2) {
110            let x1 = seg[0][0] * SCALE;
111            let y1 = seg[0][1] * SCALE;
112            let x2 = seg[1][0] * SCALE;
113            let y2 = seg[1][1] * SCALE;
114
115            writeln!(
116                out,
117                "  (wire (pts (xy {x1:.1} {y1:.1}) (xy {x2:.1} {y2:.1})))"
118            )
119            .unwrap();
120        }
121    }
122    writeln!(out).unwrap();
123}
124
125fn write_power_symbols(out: &mut String, layout: &Layout) {
126    for rail in &layout.supply_rails {
127        let y = rail.y * SCALE;
128        let symbol = if rail.name == "vcc" {
129            "power:VCC"
130        } else {
131            "power:GND"
132        };
133        let label = rail.name.to_uppercase();
134
135        writeln!(out, "  (symbol (lib_id \"{symbol}\") (at 50.0 {y:.1} 0)").unwrap();
136        writeln!(out, "    (uuid \"rail-{name}\")", name = rail.name).unwrap();
137        writeln!(
138            out,
139            "    (property \"Reference\" \"#{label}\" (at 50.0 {y_ref:.1} 0)",
140            y_ref = y - 10.0
141        )
142        .unwrap();
143        writeln!(out, "      (effects (font (size 1.27 1.27)) hide)").unwrap();
144        writeln!(out, "    )").unwrap();
145        writeln!(out, "  )").unwrap();
146        writeln!(out).unwrap();
147    }
148}
149
150fn write_footer(out: &mut String) {
151    writeln!(out, ")").unwrap();
152}
153
154/// Map a Component to a KiCad library symbol reference and reference prefix.
155fn kicad_symbol_ref(kind: &dyn Component) -> (&'static str, &'static str) {
156    kind.footprint_ref()
157}