← Commit history

1.1.6: remove KiCad drawing-sheet by STRIPPING elements outside the symbol box (not clipping) — fixes the app's 3D-chip outline disappearing + keeps the worksheet off on zoom-out; flat SVG so pan/zoom + 3D layering are intact

John Lauer ·e7f51c12f4 ·3mo ago ·parent d93c4e8
5 files changed +69−23
Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "adom-symbol"-version = "1.1.5"+version = "1.1.6" edition = "2021" description = "Create and preview KiCad/Fusion 360 schematic symbols with interactive viewer" 
VERSION+1−1
@@ -1 +1 @@-1.1.5+1.1.6
bin/adom-symbol
⋯ 1 unchanged line ⋯
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "adom-symbol",-  "version": "1.1.5",+  "version": "1.1.6",   "type": "app",   "description": "KiCad symbol creator with interactive viewer and delivery to KiCad/Fusion 360",   "dependencies": {
src/render.rs+66−20
@@ -37,34 +37,76 @@ pub fn render_symbol_svg(kicad_sym: &str, symbol_name: &str) -> Result<String> {      // Drop KiCad 10's warm-white page background so the dark canvas shows.     let svg = svg.replace("fill:#F5F4EF", "fill:none").replace("stroke:#F5F4EF", "stroke:none");+    // crop_to_symbol also STRIPS the KiCad drawing-sheet (A4 border + Sheet/File/+    // Title/Size title block) — every top-level element that lies entirely outside+    // the symbol's box is deleted, so it can't appear on zoom-out. Removing it+    // (rather than clipping) keeps the SVG flat, so the app's SVG-space pan/zoom+    // and its 3D-chip layering are untouched.     let cropped = crop_to_symbol(&svg, &block);     let themed = crate::theme::apply_dark_theme_to_symbol(&cropped);-    // Hard-clip everything to the symbol's viewBox so KiCad's A4 drawing sheet-    // (border + "Sheet/File/Title/Size" title block, drawn out at the page edges)-    // can NEVER appear when the viewer zooms out. We want a symbol, not a sheet.-    Ok(clip_to_viewbox(&themed))+    Ok(strip_drawing_sheet(&themed)) } -/// Wrap the SVG body in a userSpace clip = its own viewBox, so nothing painted-/// outside the symbol's cropped box (i.e. the KiCad worksheet frame + title-/// block) is ever visible — even under the viewer's CSS zoom-out transform.-fn clip_to_viewbox(svg: &str) -> String {+/// Delete the KiCad drawing-sheet (A4 border + "Sheet/File/Title/Size" title+/// block) from the SVG: every `<path>`/`<polyline>`/`<text>` whose coordinates+/// lie ENTIRELY outside the symbol's viewBox (the sheet is drawn out at the page+/// edges, axis-aligned) is dropped. Removing it — rather than clipping — keeps+/// the SVG flat, so the app's SVG-space pan/zoom transform and its 3D-chip+/// layering are untouched, and nothing can appear on zoom-out.+fn strip_drawing_sheet(svg: &str) -> String {     let vb = Regex::new(r#"viewBox="(-?[\d.]+) (-?[\d.]+) (-?[\d.]+) (-?[\d.]+)""#).unwrap();-    let (x, y, w, h) = match vb.captures(svg) {-        Some(c) => (c[1].to_string(), c[2].to_string(), c[3].to_string(), c[4].to_string()),+    let (vx, vy, vw, vh) = match vb.captures(svg) {+        Some(c) => (+            c[1].parse::<f64>().unwrap_or(0.0),+            c[2].parse::<f64>().unwrap_or(0.0),+            c[3].parse::<f64>().unwrap_or(0.0),+            c[4].parse::<f64>().unwrap_or(0.0),+        ),         None => return svg.to_string(),     };-    let open = format!(-        "<defs><clipPath id=\"asym-crop\" clipPathUnits=\"userSpaceOnUse\"><rect x=\"{x}\" y=\"{y}\" width=\"{w}\" height=\"{h}\"/></clipPath></defs><g clip-path=\"url(#asym-crop)\">"-    );-    match svg.find('>') {-        Some(pos) => {-            let (head, tail) = svg.split_at(pos + 1);-            let body = tail.strip_suffix("</svg>").unwrap_or(tail);-            format!("{head}{open}{body}</g></svg>")+    let m = 1.0;+    let (x0, x1, y0, y1) = (vx - m, vx + vw + m, vy - m, vy + vh + m);+    let num = Regex::new(r"-?\d+\.?\d*").unwrap();+    // A shape is drawing-sheet iff ALL its X are outside the box OR ALL its Y are+    // (true for the axis-aligned page border + the bottom-right title block, never+    // for the symbol, which lives inside the box).+    let outside = |coords: &str, attr_pair: Option<(f64, f64)>| -> bool {+        let nums: Vec<f64> = match attr_pair {+            Some((x, y)) => vec![x, y],+            None => num.find_iter(coords).filter_map(|m| m.as_str().parse().ok()).collect(),+        };+        if nums.len() < 2 {+            return false;         }-        None => svg.to_string(),+        let all_x_out = nums.iter().step_by(2).all(|&x| x < x0 || x > x1);+        let all_y_out = nums.iter().skip(1).step_by(2).all(|&y| y < y0 || y > y1);+        all_x_out || all_y_out+    };+    let mut s = svg.to_string();+    for attr in ["d", "points"] {+        let re = Regex::new(&format!(r#"<(?:path|polyline)\b[^>]*\b{attr}="([^"]*)"[^>]*/?>"#)).unwrap();+        s = re+            .replace_all(&s, |c: &regex::Captures| {+                if outside(&c[1], None) { String::new() } else { c[0].to_string() }+            })+            .into_owned();     }+    // Invisible title-block strings (<text x=".." y="..">Sheet: /…</text>); their+    // paired stroked-text glyph <path>s are already gone from the pass above.+    let tre = Regex::new(r#"<text\b([^>]*)>[^<]*</text>"#).unwrap();+    let xre = Regex::new(r#"\bx="(-?[\d.]+)""#).unwrap();+    let yre = Regex::new(r#"\by="(-?[\d.]+)""#).unwrap();+    tre.replace_all(&s, |c: &regex::Captures| {+        let a = &c[1];+        match (+            xre.captures(a).and_then(|m| m[1].parse::<f64>().ok()),+            yre.captures(a).and_then(|m| m[1].parse::<f64>().ok()),+        ) {+            (Some(x), Some(y)) if outside("", Some((x, y))) => String::new(),+            _ => c[0].to_string(),+        }+    })+    .into_owned() }  fn extract_symbol_block(content: &str, name: &str) -> Option<String> {@@ -97,6 +139,10 @@ fn build_schematic(name: &str, block: &str) -> String {     let (rx, ry) = prop_at(block, "Reference").unwrap_or((0.0, 12.7));     let (vx, vy) = prop_at(block, "Value").unwrap_or((0.0, -12.7));     let rpre = prop_val(block, "Reference").unwrap_or_else(|| "U".to_string());+    // The on-canvas Value is the symbol's OWN Value property (a short human value+    // like "33Ω" for a clean discrete) — NOT the symbol name (the MPN). Falling+    // back to the name only when there is no Value keeps IC behavior unchanged.+    let vval = prop_val(block, "Value").filter(|v| !v.is_empty()).unwrap_or_else(|| name.to_string());     let rjust = prop_justify(block, "Reference");     let vjust = prop_justify(block, "Value");     // If the library symbol hides the Value (chip name laser-marked on the 3D@@ -114,7 +160,7 @@ fn build_schematic(name: &str, block: &str) -> String {          (in_bom yes) (on_board yes)\n    \          (uuid \"00000000-0000-0000-0000-000000000002\")\n    \          (property \"Reference\" \"{rpre}?\" (at {srx:.3} {sry:.3} 0) (effects (font (size 1.27 1.27)){rjust}))\n    \-         (property \"Value\" \"{name}\" (at {svx:.3} {svy:.3} 0) (effects (font (size 1.27 1.27)){vjust}{vhide}))\n  )\n  \+         (property \"Value\" \"{vval}\" (at {svx:.3} {svy:.3} 0) (effects (font (size 1.27 1.27)){vjust}{vhide}))\n  )\n  \          (sheet_instances (path \"/\" (page \"1\")))\n)\n"     ) }