From 9e4064758175c4b59a7380c1431c3ee9f9620dc7 Mon Sep 17 00:00:00 2001 From: jess Date: Wed, 22 Apr 2026 13:41:00 -0700 Subject: [PATCH] Added method for snapping items to grid. Also added a pour method. --- src/client/geometry.rs | 20 +++++ src/model/item.rs | 171 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 191 insertions(+) diff --git a/src/client/geometry.rs b/src/client/geometry.rs index 83fa56e..786f5d9 100644 --- a/src/client/geometry.rs +++ b/src/client/geometry.rs @@ -34,6 +34,26 @@ impl KiCadClient { let _ = response_payload_as_any(response, RES_PROTOBUF_EMPTY)?; Ok(()) } + + /// Rebuilds fill geometry for every zone on the current board. + pub async fn refill_all_zones(&self) -> Result<(), KiCadError> { + let zones = self.get_all_pcb_items_raw().await?; + let mut ids: Vec = Vec::new(); + for (_type_code, items) in zones { + for any in items { + if any.type_url.ends_with("kiapi.board.types.Zone") { + let wrapper = crate::model::item::Item::from_any(any); + if let Ok(Some(id)) = wrapper.kiid() { + ids.push(id); + } + } + } + } + if ids.is_empty() { + return Ok(()); + } + self.refill_zones(ids).await + } /// Returns pad polygon responses as raw protobuf payloads. pub async fn get_pad_shape_as_polygon_raw( &self, diff --git a/src/model/item.rs b/src/model/item.rs index 3c13f62..89106bb 100644 --- a/src/model/item.rs +++ b/src/model/item.rs @@ -244,6 +244,81 @@ impl Item { } } + /// Snaps all positional coordinates of this item to the nearest multiple of the grid. + /// + /// `grid_x_nm` / `grid_y_nm` are the grid step in nanometres. `grid_x_nm` / `grid_y_nm` of + /// `0` or below leaves that axis unchanged. + /// + /// Returns `Ok(true)` if the item's bytes were rewritten. Unsupported kinds (Zone, Dimension, + /// Field, Group, ReferenceImage, Unknown) return `Ok(false)`. + pub fn snap_position(&mut self, grid_x_nm: i64, grid_y_nm: i64) -> Result { + let gx = grid_x_nm; + let gy = grid_y_nm; + let value = self.raw.value.as_slice(); + let (new_bytes, mutated) = match self.kind() { + ItemKind::Track => { + let mut m = bt::Track::decode(value).map_err(decode_err)?; + snap_opt_v2(&mut m.start, gx, gy); + snap_opt_v2(&mut m.end, gx, gy); + (m.encode_to_vec(), true) + } + ItemKind::Arc => { + let mut m = bt::Arc::decode(value).map_err(decode_err)?; + snap_opt_v2(&mut m.start, gx, gy); + snap_opt_v2(&mut m.mid, gx, gy); + snap_opt_v2(&mut m.end, gx, gy); + (m.encode_to_vec(), true) + } + ItemKind::Via => { + let mut m = bt::Via::decode(value).map_err(decode_err)?; + snap_opt_v2(&mut m.position, gx, gy); + (m.encode_to_vec(), true) + } + ItemKind::FootprintInstance => { + let mut m = bt::FootprintInstance::decode(value).map_err(decode_err)?; + snap_opt_v2(&mut m.position, gx, gy); + (m.encode_to_vec(), true) + } + ItemKind::Pad => { + let mut m = bt::Pad::decode(value).map_err(decode_err)?; + snap_opt_v2(&mut m.position, gx, gy); + (m.encode_to_vec(), true) + } + ItemKind::BoardGraphicShape => { + let mut m = bt::BoardGraphicShape::decode(value).map_err(decode_err)?; + if let Some(shape) = m.shape.as_mut() { + snap_graphic_shape(shape, gx, gy); + } + (m.encode_to_vec(), true) + } + ItemKind::BoardText => { + let mut m = bt::BoardText::decode(value).map_err(decode_err)?; + if let Some(t) = m.text.as_mut() { + snap_opt_v2(&mut t.position, gx, gy); + } + (m.encode_to_vec(), true) + } + ItemKind::BoardTextBox => { + let mut m = bt::BoardTextBox::decode(value).map_err(decode_err)?; + if let Some(tb) = m.textbox.as_mut() { + snap_opt_v2(&mut tb.top_left, gx, gy); + snap_opt_v2(&mut tb.bottom_right, gx, gy); + } + (m.encode_to_vec(), true) + } + ItemKind::Zone + | ItemKind::Dimension + | ItemKind::Field + | ItemKind::Group + | ItemKind::ReferenceImage + | ItemKind::Unknown(_) => (Vec::new(), false), + }; + if mutated { + self.raw.value = new_bytes; + } + Ok(mutated) + } + /// Builds a new `Group` item wrapping the given member KIIDs. /// /// The returned `Item` has no `id` set, so `CreateItems` assigns a @@ -306,6 +381,102 @@ fn decode_err(e: prost::DecodeError) -> KiCadError { KiCadError::ProtobufDecode(e.to_string()) } +#[inline] +fn snap_coord(v: i64, grid: i64) -> i64 { + if grid <= 0 { return v; } + let q = v.div_euclid(grid); + let r = v.rem_euclid(grid); + if r * 2 >= grid { (q + 1) * grid } else { q * grid } +} + +fn snap_opt_v2( + v: &mut Option, + gx: i64, + gy: i64, +) { + if let Some(p) = v.as_mut() { + p.x_nm = snap_coord(p.x_nm, gx); + p.y_nm = snap_coord(p.y_nm, gy); + } +} + +fn snap_graphic_shape( + shape: &mut crate::proto::kiapi::common::types::GraphicShape, + gx: i64, + gy: i64, +) { + use crate::proto::kiapi::common::types::graphic_shape::Geometry; + use crate::proto::kiapi::common::types::poly_line_node::Geometry as NodeGeom; + if let Some(geo) = shape.geometry.as_mut() { + match geo { + Geometry::Segment(s) => { + snap_opt_v2(&mut s.start, gx, gy); + snap_opt_v2(&mut s.end, gx, gy); + } + Geometry::Rectangle(r) => { + snap_opt_v2(&mut r.top_left, gx, gy); + snap_opt_v2(&mut r.bottom_right, gx, gy); + } + Geometry::Arc(a) => { + snap_opt_v2(&mut a.start, gx, gy); + snap_opt_v2(&mut a.mid, gx, gy); + snap_opt_v2(&mut a.end, gx, gy); + } + Geometry::Circle(c) => { + snap_opt_v2(&mut c.center, gx, gy); + snap_opt_v2(&mut c.radius_point, gx, gy); + } + Geometry::Polygon(polyset) => { + for poly in polyset.polygons.iter_mut() { + if let Some(outline) = poly.outline.as_mut() { + snap_polyline_nodes(&mut outline.nodes, gx, gy); + } + for h in poly.holes.iter_mut() { + snap_polyline_nodes(&mut h.nodes, gx, gy); + } + } + } + Geometry::Bezier(b) => { + snap_opt_v2(&mut b.start, gx, gy); + snap_opt_v2(&mut b.control1, gx, gy); + snap_opt_v2(&mut b.control2, gx, gy); + snap_opt_v2(&mut b.end, gx, gy); + } + } + } + + fn snap_polyline_nodes( + nodes: &mut [crate::proto::kiapi::common::types::PolyLineNode], + gx: i64, + gy: i64, + ) { + for node in nodes.iter_mut() { + if let Some(g) = node.geometry.as_mut() { + match g { + NodeGeom::Point(p) => { + p.x_nm = snap_coord(p.x_nm, gx); + p.y_nm = snap_coord(p.y_nm, gy); + } + NodeGeom::Arc(a) => { + if let Some(p) = a.start.as_mut() { + p.x_nm = snap_coord(p.x_nm, gx); + p.y_nm = snap_coord(p.y_nm, gy); + } + if let Some(p) = a.mid.as_mut() { + p.x_nm = snap_coord(p.x_nm, gx); + p.y_nm = snap_coord(p.y_nm, gy); + } + if let Some(p) = a.end.as_mut() { + p.x_nm = snap_coord(p.x_nm, gx); + p.y_nm = snap_coord(p.y_nm, gy); + } + } + } + } + } + } +} + fn unsupported(op: &str, kind: ItemKind) -> KiCadError { KiCadError::InvalidResponse { reason: format!("{op} not supported for {}", kind.type_name()),