Skip to content
This repository was archived by the owner on Nov 11, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions tools/kicad/kicad_functions/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ edition = "2018"

[dependencies]
evalexpr = "6.1"
regex = "1.5"
resistor-calc = { git = "https://github.com/racklet/resistor-calc.git", branch = "fix-no-expr_builder-compile", default-features = false }
79 changes: 59 additions & 20 deletions tools/kicad/kicad_functions/src/vdiv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ use crate::util::err;
use evalexpr::{
ContextWithMutableVariables, EvalexprError, EvalexprResult, HashMapContext, Node, Value,
};
use regex::Regex;
use resistor_calc::{RCalc, RRes, RSeries};
use std::cell::RefCell;
use std::collections::HashSet;
use std::ops::Deref;
use std::panic::panic_any;

fn parse_series(str: &str) -> EvalexprResult<&'static RSeries> {
Expand All @@ -18,35 +21,57 @@ fn parse_series(str: &str) -> EvalexprResult<&'static RSeries> {
}
}

// evalexpr is not smart enough to distinguish identifiers on its own
fn unique_identifiers(e: &Node) -> usize {
fn resistor_identifiers(e: &Node) -> usize {
let re = Regex::new(r"^R[1-9][0-9]*$").unwrap();
let mut set = HashSet::new();
e.iter_variable_identifiers().for_each(|i| {
set.insert(i);
});
e.iter_variable_identifiers()
.filter(|i| re.is_match(i)) // Match only R? identifiers
.for_each(|i| {
set.insert(i);
});
set.len()
}

// Helper for parsing of potentially single-element tuples
fn parse_tuple(v: &Value) -> Vec<Value> {
if let Ok(t) = v.as_tuple() {
return t;
}

vec![v.clone()]
}

struct VoltageDividerConfig {
target: f64,
expression: Node,
count: usize,
series: &'static RSeries,
resistance_min: Option<f64>,
resistance_max: Option<f64>,
extra_parameters: Option<Vec<Value>>,
}

impl VoltageDividerConfig {
fn parse(v: &Value) -> EvalexprResult<Self> {
let tuple = v.as_tuple()?;

let resistance_min = tuple.get(3).map(|v| v.as_number()).transpose()?;
let resistance_max = tuple.get(4).map(|v| v.as_number()).transpose()?;
let resistance = tuple.get(3).map(|v| v.as_tuple()).transpose()?;
let resistance_min = resistance
.as_ref()
.map(|r| r.get(0).map(|v| v.as_number()))
.flatten()
.transpose()?;
let resistance_max = resistance
.as_ref()
.map(|r| r.get(1).map(|v| v.as_number()))
.flatten()
.transpose()?;

let extra_parameters = tuple.get(4).map(|v| parse_tuple(v));

// TODO: Support external constants
if let [target, expression, series] = &tuple[..3] {
let expression = evalexpr::build_operator_tree(&expression.as_string()?)?;
let count = unique_identifiers(&expression);
let count = resistor_identifiers(&expression);

Ok(Self {
target: target.as_number()?,
Expand All @@ -55,6 +80,7 @@ impl VoltageDividerConfig {
series: parse_series(&series.as_string()?)?,
resistance_min,
resistance_max,
extra_parameters,
})
} else {
err(&format!("unsupported argument count: {}", tuple.len()))
Expand All @@ -65,6 +91,16 @@ impl VoltageDividerConfig {
fn calculate(config: &VoltageDividerConfig) -> Option<RRes> {
let calc = RCalc::new(vec![config.series; config.count]);

let mut context = HashMapContext::new();
if let Some(v) = &config.extra_parameters {
for (i, p) in v.iter().enumerate() {
context
.set_value(format!("E{}", i + 1).into(), p.clone())
.unwrap();
}
}

let context_rc = RefCell::new(context);
calc.calc(|set| {
if let Some(true) = config.resistance_min.map(|r| set.sum() < r) {
return None; // Sum of resistance less than minimum
Expand All @@ -74,16 +110,17 @@ fn calculate(config: &VoltageDividerConfig) -> Option<RRes> {
return None; // Sum of resistance larger than maximum
}

// TODO: Storing this externally and using interior mutability
// could be helpful to avoid reallocating on every invocation.
let mut context = HashMapContext::new();
for i in 1..=config.count {
context
context_rc
.borrow_mut()
.set_value(format!("R{}", i).into(), Value::Float(set.r(i)))
.unwrap();
}

match config.expression.eval_with_context(&context) {
match config
.expression
.eval_with_context(context_rc.borrow().deref())
{
Ok(v) => Some((config.target - v.as_number().unwrap()).abs()),
Err(e) => match &e {
EvalexprError::DivisionError { divisor: d, .. } => {
Expand All @@ -96,22 +133,24 @@ fn calculate(config: &VoltageDividerConfig) -> Option<RRes> {
return None;
}
}
panic_any(e) // No graceful way to handle this from the closure
panic_any(e.to_string()) // No graceful way to handle this from the closure
}
_ => panic_any(e),
_ => panic_any(e.to_string()),
},
}
})
}

/// `voltage_divider` computes values for resistor-based voltage dividers.
/// - Usage: vdiv(<target voltage>, <divider expression>, <resistor series>, (min resistance), (max resistance))
/// - Example: vdiv(5.1, "(R1+R2)/R2*0.8", "E96", 500e3, 700e3)
/// - Usage: vdiv(<target voltage>, <divider expression>, <resistor series>,
/// {(<min resistance>, <max resistance>)}, ({extra 1}, {extra 2}, ...))
/// - Example: vdiv(5.1, "(R1+R2)/R2*E1", "E96", (500e3, 700e3), (0.8))
/// - Output: (<closest voltage>, <R1 value>, <R2 value>, ...)
/// There can be arbitrary many resistors in the divider, but they must be named "R1", "R2", etc.
/// The computed optimal resistance values are also presented in this order. The minimal and maximal
/// resistance limits are optional parameters, and only consider the sum of the resistances of all
/// resistors defined in the expression.
/// resistance pair is an optional parameter, and the limits only consider the sum of resistance of
/// all resistors defined in the expression. The "extra" parameters are optional external inputs for
/// the divider expression, and will be made available as "E1", "E2", etc. in order.
pub(crate) fn voltage_divider(argument: &Value) -> EvalexprResult<Value> {
let config = VoltageDividerConfig::parse(argument)?;
if let Some(res) = calculate(&config) {
Expand Down
15 changes: 9 additions & 6 deletions tools/kicad/kicad_rs/src/bin/evaluator.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
use kicad_rs::error::DynamicResult;
use kicad_rs::eval;
use kicad_rs::parser::{parse_schematic, SchematicFile};
use kicad_rs::parser::SchematicTree;
use std::env;

// Main function, can return different kinds of errors
fn main() -> DynamicResult<()> {
let args: Vec<String> = env::args().collect();
let path = std::path::Path::new(args.get(1).ok_or("expected file as first argument")?);

// Load the schematic file and parse it
let file = SchematicFile::load(path)?;
let mut schematic = parse_schematic(&file, String::new())?;
// Load the hierarchical schematic tree and parse it
let mut tree = SchematicTree::load(path)?;
let mut schematic = tree.parse()?;

// Index the parsed schematic and use the index to evaluate it. The
// index links to the schematic using mutable references, so that's
// why the schematic itself needs to be passed in as mutable here.
let mut index = eval::index_schematic(&mut schematic)?;
eval::evaluate_schematic(&mut index)?;

// TODO: Apply the internal schematic back to kicad_parse_gen::schematic::Schematic and print
println!("{:#?}", index);
// Update the fields of the components in the schematic tree based
// on the newly computed values and write the updated schematics
// back into the respective files
tree.update(&schematic)?;
tree.write()?;

Ok(())
}
Loading