Skip to content

Commit ecc845d

Browse files
authored
Fix dielectric stackup type serialization to avoid layout.sync drift (#576)
1 parent 8e4f1fe commit ecc845d

File tree

2 files changed

+52
-6
lines changed

2 files changed

+52
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ and this project adheres to Semantic Versioning (https://semver.org/spec/v2.0.0.
1616
### Fixed
1717

1818
- Standalone `.zen` files with inline manifests now map `Board(..., layout_path=...)` to `package://workspace/...`, avoiding absolute-path leakage.
19+
- Stackup sync now emits dielectric `(type ...)` once per grouped layer (not per `addsublayer`), preventing spurious `layout.sync` drift.
1920

2021
## [0.3.44] - 2026-02-20
2122

crates/pcb-zen-core/src/lang/stackup.rs

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -732,12 +732,14 @@ impl Stackup {
732732
layer_entries.push(Sexpr::symbol("addsublayer"));
733733
}
734734

735-
// Build properties for this layer
736-
let mut props = vec![
737-
kv("type", Sexpr::string(form.to_string())),
738-
kv("thickness", *thickness),
739-
kv("material", Sexpr::string(material)),
740-
];
735+
// KiCad serializes dielectric type once per grouped dielectric layer,
736+
// not once per addsublayer segment.
737+
let mut props = Vec::new();
738+
if idx == 0 {
739+
props.push(kv("type", Sexpr::string(form.to_string())));
740+
}
741+
props.push(kv("thickness", *thickness));
742+
props.push(kv("material", Sexpr::string(material)));
741743

742744
// Add material properties if available
743745
if let Some(mat) = materials
@@ -1400,6 +1402,49 @@ mod tests {
14001402
assert!(sexpr_str.contains("3.48")); // epsilon_r for RO4350B
14011403
}
14021404

1405+
#[test]
1406+
fn test_generate_stackup_uses_single_type_for_dielectric_sublayers() {
1407+
let stackup = Stackup {
1408+
materials: Some(vec![Material {
1409+
name: Some("1080".to_string()),
1410+
vendor: None,
1411+
relative_permittivity: Some(3.91),
1412+
loss_tangent: Some(0.025),
1413+
reference_frequency: None,
1414+
}]),
1415+
copper_finish: None,
1416+
solder_mask_color: None,
1417+
silk_screen_color: None,
1418+
layers: Some(vec![
1419+
Layer::Copper {
1420+
thickness: 0.035,
1421+
role: CopperRole::Signal,
1422+
},
1423+
Layer::Dielectric {
1424+
thickness: 0.0764,
1425+
material: "1080".to_string(),
1426+
form: DielectricForm::Prepreg,
1427+
},
1428+
Layer::Dielectric {
1429+
thickness: 0.0764,
1430+
material: "1080".to_string(),
1431+
form: DielectricForm::Prepreg,
1432+
},
1433+
Layer::Copper {
1434+
thickness: 0.035,
1435+
role: CopperRole::Signal,
1436+
},
1437+
]),
1438+
};
1439+
1440+
let sexpr_str = format_tree(&stackup.generate_stackup_expr(), FormatMode::Normal)
1441+
.trim_end_matches('\n')
1442+
.to_string();
1443+
1444+
assert!(sexpr_str.contains("addsublayer"));
1445+
assert_eq!(sexpr_str.matches("(type \"prepreg\")").count(), 1);
1446+
}
1447+
14031448
#[test]
14041449
fn test_ground_role_maps_to_power_in_kicad() {
14051450
// Test that Ground role is mapped to "power" when generating KiCad output

0 commit comments

Comments
 (0)