app
Altium - the Altium Bridge
Public Made by Adomby adom
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.
← 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.
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",