← Commit history

0.2.50 source (not yet released, needs signing): build_schematic and export_manufacturing find Altium's sheet and OutJob templates under the running install's Public Documents folder (ADDevelop, AD26, ...) instead of AD26 only; symbol_mode no longer references an undeclared NOld, which halted the script on Altium Designer Develop; build_symbol_modes reports replaced. Full verb battery green on BOTH builds: Altium Designer Develop on Colby-ROG2 and Altium Designer 26.11.1 on DESKTOP-BAR6S0F, each PASS 145, FAIL 0, SKIP 1 (a365_logout), uncovered 0.

Colby Knox ·ee90f3859d ·5d ago ·parent cd2249e
9 files changed +39−12
bridge-rs/src/main.rs+1−1
@@ -31,7 +31,7 @@ use tiny_http::{Header, Method, Response, Server}; /// Attached to every failed reply (see the /command handler). const REPORT_ISSUES: &str = "If this looks like an altium-bridge BUG (not a refusal you expected), report it: check for a duplicate with `adom-wiki issue list adom/altium-bridge`, then `adom-wiki issue create adom/altium-bridge --title \"<verb>: <one-line symptom>\" --category bug-report --body \"<what you ran, what you expected, what happened, the VERBATIM response JSON (and errorShot if present), bridgeVersion, the Altium version from altium_status, the OS>\"`. A relay, routing, toast or lifecycle problem belongs on adom/adom-bridge instead. Reports from real machines are how this bridge gets correct: three different Altium builds behaved three different ways in one weekend."; -pub const BRIDGE_VERSION: &str = "0.2.49";+pub const BRIDGE_VERSION: &str = "0.2.50";  fn json_response(status: u16, body: &serde_json::Value) -> Response<std::io::Cursor<Vec<u8>>> {     let data = serde_json::to_vec(body).unwrap_or_else(|_| b"{}".to_vec());
bridge-rs/src/manufacturing.rs+4−3
@@ -6,7 +6,6 @@ use serde_json::{json, Value};  use crate::outputs; -const TEMPLATES: &str = r"C:\Users\Public\Documents\Altium\AD26\OutputJobs";  /// INI as Altium writes it: [sections] of key=value lines, in order. fn parse_ini(text: &str) -> Vec<(String, Vec<(String, String)>)> {@@ -70,10 +69,12 @@ pub fn outjob_path(prj: &str) -> String { /// disk makes Altium ask to reload it). Answers its path. pub fn write_outjob(prj: &str, pcb: &str, bom_columns: Option<&Vec<Value>>) -> Result<String, Value> {     let (prj, pcb) = (prj.to_string(), pcb.to_string());-    let read = |f: &str| std::fs::read_to_string(format!(r"{TEMPLATES}\{f}")).map(|t| parse_ini(&t));+    // Altium's own OutJob templates, from the running install's Public Documents folder+    let read = |f: &str| crate::support::altium_public_file(&format!(r"OutputJobs\{f}"))+        .and_then(|p| std::fs::read_to_string(&p).map_err(|e| format!("{p}: {e}"))).map(|t| parse_ini(&t));     let (fab, asm) = match (read("Fabrication.OutJob"), read("Assembly.OutJob")) {         (Ok(f), Ok(a)) => (f, a),-        (f, a) => return Err(json!({"success": false, "errorCode": "no_templates", "error": format!("Altium's OutJob templates are not at {TEMPLATES}: {:?} {:?}", f.err(), a.err())})),+        (f, a) => return Err(json!({"success": false, "errorCode": "no_templates", "error": format!("Altium's OutJob templates were not found: {:?} {:?}", f.err(), a.err())})),     };     let pcb_name = pcb.rsplit(['\\', '/']).next().unwrap_or(&pcb).to_string();     let pdir = prj.rsplit_once(['\\', '/']).map(|(d, _)| d.to_string()).unwrap_or_default();
bridge-rs/src/routing.rs+5−2
@@ -1171,8 +1171,11 @@ pub fn build_schematic(args: &Value) -> Value {     agent::call("close_document", &[("path", sch.clone())], None, Duration::from_secs(60));     // the sheet starts as one of Altium's own landscape templates (title block included), sized for the parts     let (tpl, tw, th) = match parts.len() { 0..=12 => ("ANSI B Landscape", 17000, 11000), 13..=40 => ("ANSI C Landscape", 22000, 17000), _ => ("ANSI D Landscape", 34000, 22000) };-    let tpl_path = format!(r"C:\Users\Public\Documents\Altium\AD26\Templates\{tpl}.SchDot");-    if let Err(e) = std::fs::copy(&tpl_path, &sch) { return err("template_missing", format!("could not start the sheet from {tpl_path}: {e}"), "Altium's sheet templates live under Public Documents\\Altium\\AD26\\Templates."); }+    let tpl_path = match crate::support::altium_public_file(&format!(r"Templates\{tpl}.SchDot")) {+        Ok(p) => p,+        Err(e) => return err("template_missing", e, "Altium's sheet templates live under Public Documents\\Altium\\<install>\\Templates (AD26, ADDevelop, ...)."),+    };+    if let Err(e) = std::fs::copy(&tpl_path, &sch) { return err("template_missing", format!("could not start the sheet from {tpl_path}: {e}"), ""); }     // the project: the sheet, the board, the libraries and the manufacturing OutJob, as Altium lists them     let dir = sch.rsplit_once(['\\', '/']).map(|(d, _)| d.to_string()).unwrap_or_default();     let stem = sch.rsplit_once(['\\', '/']).map(|(_, f)| f).unwrap_or(&sch).rsplit_once('.').map(|(s, _)| s.to_string()).unwrap_or_default();
bridge-rs/src/support.rs+23
@@ -273,3 +273,26 @@ mod tests {         assert!(out.get("_seatHint").is_none());     } }++/// A file under Altium's Public Documents tree (`<root>\<install key>\<rel>`, e.g. Templates\ANSI C+/// Landscape.SchDot or OutputJobs\Fabrication.OutJob). Each install keeps its own folder (AD26,+/// ADDevelop, ...): the running instance's first, then any install that has the file. The battery+/// found AD26 hard-coded when it ran on Altium Develop (2026-10-16).+pub fn altium_public_file(rel: &str) -> Result<String, String> {+    const ROOT: &str = r"C:\Users\Public\Documents\Altium";+    let mut keys: Vec<String> = Vec::new();+    if let Some(k) = crate::instance::selected().and_then(|i| i.get("key").and_then(|k| k.as_str()).map(str::to_string)) {+        keys.push(k);+    }+    if let Ok(rd) = std::fs::read_dir(ROOT) {+        let mut others: Vec<String> = rd.flatten().filter(|e| e.path().is_dir()).map(|e| e.file_name().to_string_lossy().to_string()).collect();+        others.sort();+        others.reverse(); // the newest AD<nn> first+        keys.extend(others);+    }+    for k in &keys {+        let p = format!(r"{ROOT}\{k}\{rel}");+        if std::path::Path::new(&p).is_file() { return Ok(p); }+    }+    Err(format!(r"{rel} is not under {ROOT}\<install> (looked in: {})", keys.join(", ")))+}
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "altium-bridge",-  "version": "0.2.49",+  "version": "0.2.50",   "type": "app",   "description": "Skills for your container so your AI knows how to drive the Altium Designer bridge (altium_* verbs). The bridge runtime itself is the release zip; Adom Bridge loads that.",   "tags": [
page.json+1−1
@@ -4,7 +4,7 @@   "type": "app",   "title": "Altium - the Altium Bridge",   "brief": "Adom Bridge for Altium Designer: a resident DelphiScript agent gives the AI eval, the 1,100-command process launcher, all 8 EDA surfaces with screenshots, compile/ERC, DRC, OutJob outputs, native SchLib/PcbLib authoring with STEP 3D bodies, and dialog handling by PID.",-  "version": "0.2.49",+  "version": "0.2.50",   "hero": {     "type": "image",     "path": "screenshots/hero.png"
src/BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.2.49+0.2.50
src/agent/AdomAgent.pas.tmpl+2−2
@@ -2951,7 +2951,7 @@ Begin     SchServer.RobotManager.SendMessage(SLib.I_ObjectAddress, c_BroadCast, SCHM_PrimitiveRegistration, Comp.I_ObjectAddress);     SLib.CurrentSchComponent := Comp;     SLib.GraphicallyInvalidate;-    Result := '{' + JRaw('ok', 'true') + ',' + JStr('libref', LibRef) + ',' + JStr('library', LibPath) + ',' ++    Result := '{' + JRaw('ok', 'true') + ',' + JStr('libref', LibRef) + ',' + JStr('library', LibPath) + ',' + JRaw('replaced', IntToStr(NOld)) + ',' +         JStr('libraryState', LibState) + ',' + JRaw('modes', IntToStr(NModes)) + ',' +         JRaw('pins', IntToStr(NPins)) + ',' + JRaw('lines', IntToStr(NLines)) + ',' +         JRaw('displayModeCount', IntToStr(Comp.DisplayModeCount)) + '}';@@ -2979,7 +2979,7 @@ Begin     Md := StrToIntDef(Param(Lines, 'mode'), 0);     Comp.DisplayMode := Md;     SLib.GraphicallyInvalidate;-    Result := '{' + JRaw('ok','true') + ',' + JRaw('mode', IntToStr(Comp.DisplayMode)) + ',' + JRaw('replaced', IntToStr(NOld)) + ',' ++    Result := '{' + JRaw('ok','true') + ',' + JRaw('mode', IntToStr(Comp.DisplayMode)) + ',' +         JRaw('displayModeCount', IntToStr(Comp.DisplayModeCount)) + ',' + JStr('libref', Comp.LibReference) + '}'; End; 
src/bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "altium",   "displayName": "Altium Designer",-  "version": "0.2.49",+  "version": "0.2.50",   "description": "Drive Altium Designer from Adom: a resident DelphiScript agent (eval + the 1,100-command process launcher), the X2 -R command line, UIA over the VCL shell, dialogs by PID, screenshots of every Altium window, and Altium 365.",   "homepage": "https://wiki.adom.inc/adom/altium-bridge",   "docs": "https://wiki.adom.inc/adom/altium-bridge",