Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
612ccc4
SYN-362 - add verified ADS124S08 and REF5025 registry parts
nyagamunene Oct 27, 2026
d17e651
SYN-364 - add verified BME280 and 32 MHz nRF52840 crystal, verify LP5…
nyagamunene Oct 27, 2026
81bb62a
SYN-363 - keep each split ground's fence vias inside its own pour
nyagamunene Oct 27, 2026
35bbbe9
SYN-363 - give each placed footprint body its own UUIDs
nyagamunene Oct 29, 2026
ec69001
SYN-363 - add the PT100 RTD front end board and its qualification record
nyagamunene Oct 29, 2026
eca9e04
SYN-363 - compact the RTD board and add angled and schematic views
nyagamunene Oct 29, 2026
ca49439
SYN-363 - tighten the RTD board to 63 x 51 mm
nyagamunene Oct 29, 2026
5ec0166
SYN-363 - re-place and compact the RTD board to 50 x 44 mm
nyagamunene Oct 30, 2026
ffbaa66
SYN-363 - level J2 with J3 at the board edge
nyagamunene Oct 30, 2026
4b5b115
SYN-363 - notch the board and centre J1 on the top edge
nyagamunene Oct 30, 2026
4eb13e7
SYN-363 - attribute the board review to the designated reviewer
nyagamunene Oct 30, 2026
689a6fc
SYN-363 - fix clippy cast_sign_loss in split-ground test
nyagamunene Oct 30, 2026
60fe000
SYN-363 - add the four-layer copper image
nyagamunene Oct 30, 2026
c08f766
SYN-363 - make the board outline a placement-sidecar construct
nyagamunene Nov 2, 2026
bcf7f9e
WIP: merge main and split-ground return-via fixes
nyagamunene Nov 2, 2026
b84fd51
WIP: advisory return-via gate, split-ground selection and outline-awa…
nyagamunene Nov 2, 2026
85e414e
WIP: single-ground board routes clean
nyagamunene Nov 2, 2026
59528a6
WIP: single-ground board clean at 0 DRC
nyagamunene Nov 2, 2026
e0ccfa8
SYNTH-363: single-ground record, BOM attributes and source revision
nyagamunene Nov 2, 2026
ccaa71f
SYNTH-363: stamp source revision and relativize run records
nyagamunene Nov 2, 2026
5b68de7
SYNTH-363: refresh schematic fingerprints after the merge
nyagamunene Nov 2, 2026
8eff718
SYNTH-363: regenerate single-ground qualification package
nyagamunene Nov 2, 2026
3378ce2
Revert schematic fingerprint refresh to the CI KiCad rendering
nyagamunene Nov 2, 2026
5ad8905
Restore the branch CI fingerprints for the SYN-66/67 schematic rendering
nyagamunene Nov 2, 2026
bcc89cd
SYNTH-363: remove the notch and use the plain rectangle
nyagamunene Nov 2, 2026
cc4b674
SYNTH-363: regenerate the plain-rectangle qualification package
nyagamunene Nov 2, 2026
32d3d03
SYNTH-363: 50.1 mm rectangle with filled-via GND escape
nyagamunene Nov 2, 2026
83cc6ff
SYNTH-363: regenerate the 50.1 mm filled-via package
nyagamunene Nov 2, 2026
403e633
SYNTH-363: move the reference and analog support into the empty top-left
Nov 2, 2026
e7372ca
SYNTH-363: keep the checkout path out of the netlist record
Nov 3, 2026
51104b2
SYNTH-363: regenerate the package for the top-left re-place
Nov 3, 2026
e37474d
NOISSUE - pin CI to ubuntu-24.04 for the KiCad 10 PPA
Nov 3, 2026
df54c77
SYNTH-363: compact the board and fit the outline to the placement
Nov 3, 2026
8a4bf21
SYNTH-363: regenerate the package for the compact board
Nov 3, 2026
77c261b
SYNTH-363: correct the QSPI and label notes for the compact board
Nov 3, 2026
945d222
SYNTH-363: centre the USB-C connector on the top edge
Nov 3, 2026
eb345ac
SYNTH-363: open the U2 escape channel for the centred connector
Nov 3, 2026
a499358
SYNTH-363: tie the GND pads beside U2 with source stitching vias
Nov 3, 2026
0bc9745
SYNTH-363: grow the board 1 mm to route the centred connector
Nov 3, 2026
28f7f1e
SYNTH-363: hide the two reference labels that the denser top row clips
Nov 3, 2026
f4b3a37
SYNTH-363: regenerate the package with the centred USB-C
Nov 3, 2026
5694bc8
SYN-363 - Reconcile via and SI warning counts in the RTD docs
nyagamunene Nov 3, 2026
75a00a5
SYN-363 - Refresh doc hashes
nyagamunene Nov 3, 2026
4630043
Merge remote-tracking branch 'upstream/main' into redo/rtd-375
nyagamunene Nov 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion crates/synth-cli/src/release.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@ pub struct ReleaseManifest {
pub schema_version: String,
pub tool_version: String,
pub input: String,
/// Commit named by `SYNTH_SOURCE_REVISION` when the package was built.
/// `None` means the caller did not name one.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_revision: Option<String>,
pub fab_requested: bool,
pub production_status: ProductionStatus,
pub release_ready: bool,
Expand Down Expand Up @@ -270,6 +274,25 @@ fn gate_scope() -> String {
.to_string()
}

/// The input path as a checkout-relative path where possible, so a package
/// does not record the absolute directory of the machine that built it.
fn display_input(input: &Path) -> String {
std::env::current_dir()
.ok()
.and_then(|cwd| input.strip_prefix(cwd).ok())
.unwrap_or(input)
.display()
.to_string()
}

/// The source revision a caller names in `SYNTH_SOURCE_REVISION`. An empty
/// value names none, so an unset or blank variable is not stamped as a commit.
fn revision_from(value: Option<String>) -> Option<String> {
value
.map(|v| v.trim().to_string())
.filter(|v| !v.is_empty())
}

impl ReleaseManifest {
pub fn new(
tool_version: &str,
Expand All @@ -283,7 +306,8 @@ impl ReleaseManifest {
Self {
schema_version: SCHEMA_VERSION.to_string(),
tool_version: tool_version.to_string(),
input: input.display().to_string(),
input: display_input(input),
source_revision: revision_from(std::env::var("SYNTH_SOURCE_REVISION").ok()),
fab_requested,
production_status: if clean {
ProductionStatus::Production
Expand Down Expand Up @@ -522,6 +546,27 @@ mod tests {
)
}

#[test]
fn an_input_under_the_current_directory_is_recorded_relative() {
let cwd = std::env::current_dir().expect("cwd");
let absolute = cwd.join("qualification/class-b/rtd-afe/rtd_afe.synth");
assert_eq!(
display_input(&absolute),
"qualification/class-b/rtd-afe/rtd_afe.synth"
);
assert_eq!(display_input(Path::new("board.synth")), "board.synth");
}

#[test]
fn a_named_source_revision_is_kept_and_a_blank_one_is_not() {
assert_eq!(
revision_from(Some(" abc123\n".into())).as_deref(),
Some("abc123")
);
assert_eq!(revision_from(Some(" ".into())), None);
assert_eq!(revision_from(None), None);
}

fn unverified() -> OverrideRecord {
OverrideRecord::new(
OverrideKind::UnverifiedParts,
Expand Down
99 changes: 95 additions & 4 deletions crates/synth-kicad/src/bom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

//! Bill of materials CSV writer.
//!
//! Columns: `refdes,value,kind,description`. Fields containing
//! commas, quotes, or newlines are RFC-4180 quoted. Output is
//! Columns: `refdes,value,kind,description`, the KiCad symbol and
//! footprint, supplier identities, and the source's structured
//! `dielectric`, `voltage`, `tolerance` and `power` attributes. Fields
//! containing commas, quotes, or newlines are RFC-4180 quoted. Output is
//! sorted by refdes for diff stability.
//!
//! Do-not-populate parts are left out: a DNP line in the BOM would
Expand Down Expand Up @@ -39,6 +41,10 @@ fn build_bom_csv_filtered(board: &Board, extra_dnp: &BTreeSet<&str>) -> String {
String,
String,
String,
String,
String,
String,
String,
)> = board
.components
.iter()
Expand Down Expand Up @@ -78,6 +84,7 @@ fn build_bom_csv_filtered(board: &Board, extra_dnp: &BTreeSet<&str>) -> String {
.as_ref()
.and_then(|p| p.mpn.clone())
.unwrap_or_default();
let attribute = |key: &str| c.properties.get(key).cloned().unwrap_or_default();
(
c.refdes.clone(),
value,
Expand All @@ -87,14 +94,35 @@ fn build_bom_csv_filtered(board: &Board, extra_dnp: &BTreeSet<&str>) -> String {
kicad_footprint,
lcsc_pn,
mpn,
attribute("Dielectric"),
attribute("Voltage"),
attribute("Tolerance"),
attribute("Power"),
)
})
.collect();
rows.sort();

let mut out = String::new();
out.push_str("refdes,value,kind,description,kicad_symbol,kicad_footprint,lcsc_pn,mpn\n");
for (refdes, value, kind, description, kicad_symbol, kicad_footprint, lcsc_pn, mpn) in rows {
out.push_str(
"refdes,value,kind,description,kicad_symbol,kicad_footprint,lcsc_pn,mpn,\
dielectric,voltage,tolerance,power\n",
);
for (
refdes,
value,
kind,
description,
kicad_symbol,
kicad_footprint,
lcsc_pn,
mpn,
dielectric,
voltage,
tolerance,
power,
) in rows
{
write_field(&mut out, &refdes);
out.push(',');
write_field(&mut out, &value);
Expand All @@ -110,6 +138,14 @@ fn build_bom_csv_filtered(board: &Board, extra_dnp: &BTreeSet<&str>) -> String {
write_field(&mut out, &lcsc_pn);
out.push(',');
write_field(&mut out, &mpn);
out.push(',');
write_field(&mut out, &dielectric);
out.push(',');
write_field(&mut out, &voltage);
out.push(',');
write_field(&mut out, &tolerance);
out.push(',');
write_field(&mut out, &power);
out.push('\n');
}
out
Expand Down Expand Up @@ -192,6 +228,61 @@ mod tests {
assert!(lite.contains("R1") && !lite.contains("R2") && !lite.contains("R3"));
}

#[test]
fn structured_attributes_get_their_own_columns() {
use std::collections::BTreeMap;
use synth_ir::{Component, ComponentId};
let mut properties = BTreeMap::new();
properties.insert("Dielectric".to_string(), "C0G".to_string());
properties.insert("Voltage".to_string(), "50V".to_string());
properties.insert("Tolerance".to_string(), "0.1%".to_string());
let board = Board {
schematic_overflow: None,
schematic_paper: None,
groups: Vec::new(),
legends: false,
name: "b".to_string(),
layers: 2,
manufacturer: None,
revision: None,
company: None,
components: vec![Component {
id: ComponentId(0),
refdes: "C1".to_string(),
kind: "capacitor".to_string(),
part: None,
value: Some("100nF".to_string()),
dnp: false,
properties,
placement_hint: None,
escape: None,
group: None,
sheet: None,
source_span: synth_diagnostics::Span::new(0, 0),
}],
nets: vec![],
diff_pairs: vec![],
notes: vec![],
keepouts: vec![],
stackup: None,
netclasses: vec![],
buses: vec![],
modules: vec![],
variants: vec![],
required_processes: std::collections::BTreeSet::new(),
source_span: synth_diagnostics::Span::new(0, 0),
};
let csv = build_bom_csv(&board);
assert!(
csv.starts_with(
"refdes,value,kind,description,kicad_symbol,kicad_footprint,lcsc_pn,mpn,\
dielectric,voltage,tolerance,power\n"
),
"{csv}"
);
assert!(csv.contains(",C0G,50V,0.1%,"), "{csv}");
}

#[test]
fn quoting_doubles_internal_quotes() {
let mut s = String::new();
Expand Down
12 changes: 9 additions & 3 deletions crates/synth-kicad/src/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,17 +465,23 @@ fn export_inner(
// external router, which reads this file and writes its own candidate
// back; Synth does not synthesize traces here, so nothing downstream
// can mistake them for router output.
let (label_overrides, fixed_vias) = sidecars
let placement_sidecar = sidecars
.placement_path()
.and_then(synth_layout::sidecar::SidecarLayout::load_from_file)
.map(|sidecar| (sidecar.labels, sidecar.vias))
.and_then(synth_layout::sidecar::SidecarLayout::load_from_file);
let (label_overrides, fixed_vias) = placement_sidecar
.as_ref()
.map(|sidecar| (sidecar.labels.clone(), sidecar.vias.clone()))
.unwrap_or_default();
let outline: &[synth_layout::sidecar::SidecarPoint] = placement_sidecar
.as_ref()
.map_or(&[], |sidecar| sidecar.outline.as_slice());
let pcb_text = pcb::build_pcb(
board,
&placement,
&synth_pcb::Routing::default(),
&project_namespace,
&label_overrides,
outline,
&fixed_vias,
)
.map_err(|error| ExportError::PlaneFanout {
Expand Down
Loading