← Commit history

v0.3.6: fix issue #139 — board 3D render now (1) stages project-local models (${KIPRJMOD}/3d/, plus a 3d/ folder for custom refs) so vendor STEPs resolve, and (2) includes DNP footprints (KiCad omits them by default, so a mis-flagged part rendered blank)

barrett-land ·365bb59f58 ·3mo ago ·parent cd06b4c
1 file changed +35−5
src/main.rs+35−5
@@ -1494,7 +1494,8 @@ fn render_board_glb(slug: &str) -> Option<Vec<u8>> {     // machine-absolute Unix path (/home/…), a Windows path (C:\Users\…), OR a     // project-relative path (<Board>.3dshapes/…). Handling all three means the 3D     // renders regardless of how the source .kicad_pcb wrote its paths.-    let mut models: std::collections::HashSet<String> = std::collections::HashSet::new();+    let mut models: std::collections::HashSet<String> = std::collections::HashSet::new(); // custom refs → 3d/<basename>+    let mut proj_models: std::collections::HashSet<String> = std::collections::HashSet::new(); // ${KIPRJMOD}/<rel> → fetched from the page at <rel>     let mut out = String::with_capacity(pcb.len());     let mut rest = pcb.as_str();     while let Some(pos) = rest.find("(model \"") {@@ -1504,7 +1505,13 @@ fn render_board_glb(slug: &str) -> Option<Vec<u8>> {             Some(end) => {                 let path = &after[..end];                 if path.starts_with("${") {-                    // standard KiCad 3D model dir — the render service already has it+                    // ${KIPRJMOD}/<rel> is PROJECT-LOCAL (a vendor STEP in the page's+                    // 3d/ etc.), not a stock model — the file lives in the page repo, so+                    // stage it. Other ${KICAD*_3DMODEL_DIR} refs are stock and resolve on+                    // the render service.+                    if let Some(rel) = path.replace('\\', "/").strip_prefix("${KIPRJMOD}/") {+                        proj_models.insert(rel.to_string());+                    }                     out.push_str(path);                 } else {                     let base = path.rsplit(['/', '\\']).next().unwrap_or(path);@@ -1517,19 +1524,42 @@ fn render_board_glb(slug: &str) -> Option<Vec<u8>> {         }     }     out.push_str(rest);+    // Show DNP (Do-Not-Populate) parts in the 3D view. KiCad's 3D export omits DNP+    // footprints by default, so a part left/mis-flagged DNP (e.g. a main IC) renders+    // blank with no body — surprising in a design-browsing tool. Drop the `dnp` attr+    // token so every placed footprint gets its model.+    let out = out.replace(" dnp)", ")").replace(" dnp ", " ");     // assemble the bundle: board.kicad_pcb (rewritten) + 3d/<custom models>     let key = slug.replace('/', "_");     let bundle = format!("{SCRATCH}/glb-{key}");     let _ = std::fs::remove_dir_all(&bundle);     std::fs::create_dir_all(format!("{bundle}/3d")).ok()?;     std::fs::write(format!("{bundle}/board.kicad_pcb"), &out).ok()?;-    if let Some(sd) = &shapes_dir {-        for base in &models {-            if let Some(mb) = wiki_bytes(slug, &format!("{sd}/{base}")) {+    // custom refs (absolute/relative) were rewritten to ${KIPRJMOD}/3d/<base>; find the+    // model on the page — it may live in a `.3dshapes` folder, a `3d/` folder, or at the+    // referenced relative path — and stage it under 3d/<base>.+    for base in &models {+        let candidates: Vec<String> = shapes_dir.iter().map(|sd| format!("{sd}/{base}"))+            .chain([format!("3d/{base}"), base.clone()])+            .collect();+        for cand in candidates {+            if let Some(mb) = wiki_bytes(slug, &cand) {                 let _ = std::fs::write(format!("{bundle}/3d/{base}"), mb);+                break;             }         }     }+    // project-local ${KIPRJMOD}/<rel> models: fetch each from the page repo at <rel> and+    // stage it at the same relative path so ${KIPRJMOD} resolves inside the bundle.+    for rel in &proj_models {+        if let Some(mb) = wiki_bytes(slug, rel) {+            let dest = format!("{bundle}/{rel}");+            if let Some(parent) = std::path::Path::new(&dest).parent() {+                let _ = std::fs::create_dir_all(parent);+            }+            let _ = std::fs::write(&dest, mb);+        }+    }     let targz = format!("{SCRATCH}/glb-{key}.tar.gz");     if !Command::new("tar").args(["-czf", &targz, "-C", &bundle, "."]).status().map(|s| s.success()).unwrap_or(false) {         return None;