#constraint-solver #geometry #cad

ezpz

A constraint solver for KCL and Zoo Design Studio

13 releases

Uses new Rust 2024

new 0.2.29 Aug 5, 2026
0.2.28 Jul 15, 2026
0.2.27 Jun 23, 2026
0.2.26 May 26, 2026
0.0.1 Nov 15, 2024

#31 in Math

Download history 7243/week @ 2026-04-17 7479/week @ 2026-04-24 8535/week @ 2026-05-01 8077/week @ 2026-05-08 5903/week @ 2026-05-15 4575/week @ 2026-05-22 5168/week @ 2026-05-29 5579/week @ 2026-06-05 4526/week @ 2026-06-12 4570/week @ 2026-06-19 5205/week @ 2026-06-26 5877/week @ 2026-07-03 7820/week @ 2026-07-10 6925/week @ 2026-07-17 9517/week @ 2026-07-24 9199/week @ 2026-07-31

34,789 downloads per month
Used in 2 crates (via kcl-lib)

MIT license

410KB
9K SLoC

Ezpz

This is a 2D constraint solver, for use in CAD or graphics applications.

Usage

use ezpz::{Config, solve, Constraint, ConstraintRequest, datatypes::inputs::DatumPoint, IdGenerator};

// Define the geometry.
// These entities don't have known positions or dimensions yet, the solver
// will place them for us.
let mut ids = IdGenerator::default();
let p = DatumPoint::new(&mut ids);
let q = DatumPoint::new(&mut ids);

// Define constraints on the geometry.
// These could constraint the entities themselves
// (e.g. the position of a point or the radius of a circle),
// or their relationship to each other
// (e.g. these two lines must be parallel, or this point must lie on this arc).
let requests = [
    // Fix P to the origin
    ConstraintRequest::highest_priority(Constraint::Fixed(p.id_x(), 0.0)),
    ConstraintRequest::highest_priority(Constraint::Fixed(p.id_y(), 0.0)),
    // P and Q should be 4 units apart.
    ConstraintRequest::highest_priority(Constraint::Distance(p, q, 4.0)),
];

// Provide some initial guesses to the solver for their locations.
let initial_guesses = vec![
    (p.id_x(), 0.0),
    (p.id_y(), -0.02),
    (q.id_x(), 4.39),
    (q.id_y(), 4.38),
];

// Run the solver!
let outcome = solve(
    &requests,
    initial_guesses,
    // You can customize the config, but for this example, we'll just use the default.
    Config::default(),
);

// Check the outcome.
match outcome {
  Ok(solution) => {
    // If you give incompatible constraints, then your constraints cannot possibly
    // be satisfied. But in this example, there should be a solution.
    assert!(solution.is_satisfied());
    assert!(solution.unsatisfied().is_empty());
    let solved_p = solution.final_value_point(&p);
    let solved_q = solution.final_value_point(&q);
    println!("P = ({}, {})", solved_p.x, solved_p.y);
    println!("Q = ({}, {})", solved_q.x, solved_q.y);
  }
  Err(e) => {
    eprintln!("ezpz could not solve this constraint system: {}", e.error);
  }
}

Constraint problem files

ezpz defines a text format for writing out constraint problems. You don't have to use this format -- you can use the Rust library directly -- but it's a very convenient format. It looks like this:

# constraints
point p
point q
p.x = 0
p.y = 0
q.y = 0
vertical(p, q)

# guesses
p roughly (3, 4)
q roughly (5, 6)

There's two sections, Constraints and Guesses. You define each point (like p and q) and once defined, you can write constraints that use them. For example, you can fix a point's X or Y component (p.x = 0). Or you can relate two points, e.g. vertical(p, q).

For more examples, see the test_cases/ directory.

Dependencies

~10–14MB
~329K SLoC