MCAD-driven placement: apply IDF fixed positions to layout#876
Conversation
| fn resolve_footprint_pose(claim: &IdfPlacementClaim, local: LocalDatumPose) -> BoardPose { | ||
| let footprint_rotation = normalize_degrees(claim.pose.rotation_deg - local.rotation_deg); | ||
| let theta = footprint_rotation.to_radians(); | ||
| let cos_t = theta.cos(); | ||
| let sin_t = theta.sin(); | ||
| let dx = local.x_mm * cos_t - local.y_mm * sin_t; | ||
| let dy = local.x_mm * sin_t + local.y_mm * cos_t; | ||
|
|
||
| BoardPose { | ||
| x_nm: mm_to_nm(claim.pose.x_mm - dx), | ||
| y_nm: mm_to_nm(claim.pose.y_mm - dy), | ||
| rotation_deg: footprint_rotation, | ||
| side: claim.pose.side, | ||
| } | ||
| } |
There was a problem hiding this comment.
🚩 IDF coordinate system assumed to match KiCad frame
The resolve_footprint_pose function at crates/pcb-mechanical/src/placement.rs:128-142 passes IDF X/Y coordinates directly through to KiCad nanometer positions without any Y-axis inversion. Standard IDF 3.0 uses a right-hand coordinate system (Y-up), while KiCad uses Y-down internally. The README explicitly states "IDF coordinates map 1:1 onto the KiCad sheet frame", making this an intentional design choice — the MCAD export is expected to already be in KiCad's coordinate frame. This works for the described workflow but would silently produce wrong results if a user feeds in a standard Y-up IDF export from SolidWorks/Fusion 360 without pre-transforming coordinates. Consider documenting this assumption prominently (e.g., in the datum TOML or idf_for_board docstring).
Was this helpful? React with 👍 or 👎 to provide feedback.
ceb841c to
83c66e7
Compare
| .get(&instance_ref) | ||
| .and_then(|instance| instance.attributes.get(ATTR_FOOTPRINT)) | ||
| .and_then(AttributeValue::string) | ||
| .map(str::to_owned); | ||
| let Some(footprint) = footprint else { | ||
| errors.push(format!( |
There was a problem hiding this comment.
🚩 ATTR_FOOTPRINT constant assumes evaluator stores footprint under "footprint" key in attributes
The new constant ATTR_FOOTPRINT = "footprint" at crates/pcb-sch/src/lib.rs:51 is used by resolve_mcad_positions (crates/pcb-mechanical/src/placement.rs:49-54) to look up the footprint from instance.attributes. However, the Python JsonNetlistParser reads the footprint from instance.get("footprint_fpid") — a top-level JSON field, not an attribute. The test at crates/pcb-mechanical/src/placement.rs:188 manually adds "footprint" to attributes, so it passes regardless of what the real evaluator does. If the Zener evaluator stores the footprint under a different attribute key (e.g. "footprint_fpid"), datum-based hash validation would always fail with "has no footprint" for every MCAD claim. Verifying the evaluator's attribute key convention would confirm correctness.
Was this helpful? React with 👍 or 👎 to provide feedback.
Adds a pcb-mechanical crate that consumes IDF (.emn) exports from mechanical CAD and pins MCAD-owned components at their exact mechanical pose. Claims are matched to components by reference designator, translated through a footprint-datum catalog (mechanical/footprint-datums.toml) that maps each mechanical datum into footprint-local coordinates, and applied to the schematic as fixed, locked placements that the layout sync enforces on every run while HierPlace packs ECAD-owned parts around them. The .emn is discovered by convention (mechanical/<board>.emn next to the board's .zen or at the workspace root) or declared explicitly via [board.mechanical.idf] in pcb.toml. Includes an end-to-end example (examples/usbc_mcad) with a USB-C receptacle and mounting holes fixed by the enclosure. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
83c66e7 to
17001ce
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 17001ce. Configure here.
| return Ok(()); | ||
| } | ||
|
|
||
| pcb_mechanical::apply_mcad_positions(&mut schematic, zen_path, &resolution_result)?; |
There was a problem hiding this comment.
Release skips MCAD placement
Medium Severity
pcb layout applies IDF poses via apply_mcad_positions before process_layout, but release validate_build serializes the staged schematic to netlist.json without that step. Release DRC and layout sync then run against instances missing placement, so MCAD-owned parts are not fixed or updated during release the way pcb layout enforces.
Reviewed by Cursor Bugbot for commit 17001ce. Configure here.
| }; | ||
|
|
||
| let datums = FootprintDatums::load_for_board(board_zen_path, &resolution.workspace_info.root)?; | ||
| let claims = idf::load_placement_claims(&emn)?; |
There was a problem hiding this comment.
Auto IDF parse fails layout
Medium Severity
When mechanical/<board>.emn is found by convention, apply_mcad_positions always parses it. An export without a .PLACEMENT section makes load_placement_claims fail and aborts layout, even if the file was only meant as outline data or MCAD placement is not in use yet.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 17001ce. Configure here.
| pub(crate) fn lookup(&self, idf_package: &str, footprint: &str) -> Option<&FootprintDatum> { | ||
| self.entries.iter().find(|entry| { | ||
| entry.idf_package.eq_ignore_ascii_case(idf_package) && entry.footprint == footprint | ||
| }) |
There was a problem hiding this comment.
🚩 Datum lookup uses exact footprint string match, tying it to package URI format
The FootprintDatums::lookup at crates/pcb-mechanical/src/datum.rs:61-64 does case-insensitive match on idf_package but exact (case-sensitive) match on footprint. The footprint value comes from the instance's ATTR_FOOTPRINT attribute, which is a package:// URI like package://gitlab.com/kicad/libraries/kicad-footprints@10.0.3/.... The datum catalog must use the identical URI string. This is fragile if the version tag in the URI changes (e.g., @10.0.3 → @10.0.4), requiring datum catalog updates. The README mentions footprint_hash as an optional integrity check, which partially mitigates stale datums but doesn't address the matching itself.
Was this helpful? React with 👍 or 👎 to provide feedback.


Adds a pcb-mechanical crate that consumes IDF (.emn) exports from mechanical CAD and pins MCAD-owned components at their exact mechanical pose. Claims are matched to components by reference designator, translated through a footprint-datum catalog
(mechanical/footprint-datums.toml) that maps each mechanical datum into footprint-local coordinates, and applied to the schematic as fixed, locked placements that the layout sync enforces on every run while HierPlace packs ECAD-owned parts around them.
The .emn is discovered by convention (mechanical/.emn next to the board's .zen or at the workspace root) or declared explicitly via [board.mechanical.idf] in pcb.toml. Includes an end-to-end example (examples/usbc_mcad) with a USB-C receptacle and mounting holes fixed by the enclosure.
Note
Medium Risk
Touches layout sync placement logic and introduces new resolution failures (missing datums, ambiguous IDF paths, hash mismatches); incorrect pose math could misplace MCAD-owned parts on every sync.
Overview
Adds MCAD-driven placement so
pcb layoutcan honor mechanical CAD exports before KiCad sync.A new
pcb-mechanicalcrate discovers IDF board files (.emn) viamechanical/<board>.emnor[board.mechanical.idf]inpcb.toml, parses.PLACEMENTforMCAD-owned refdes, and resolves footprint origins throughmechanical/footprint-datums.toml(optionalfootprint_hashvalidation). Resolved poses are written onto schematic instances asBoardPose/Instance.placement.pcbc layoutcallsapply_mcad_positionsafter the schematic build. The layout lens treats compilerfixed_placementas authoritative: it overrides saved KiCad placement, creates new footprints at that pose (locked), re-applies pose on updates, and excludes those parts from HierPlace auto-packing (fixed footprints count as existing content). JSON netlist parsing forwards per-instanceplacement.Includes
examples/usbc_mcaddemonstrating USB-C and mounting holes fixed by IDF while ECAD parts stay auto-placed.Reviewed by Cursor Bugbot for commit 17001ce. Bugbot is set up for automated code reviews on this repo. Configure here.