A pure MoonBit nonlinear constraint solver for small 2D CAD sketches
moon add mik1e80/cad_sketch///|
import {
"mik1e80/cad_sketch" @sketch,
}///|
test "dimensioned segment and repeated solve" {
let s = @sketch.Sketch::new()
let a = s.add_point(0.0, 0.0)
let b = s.add_point(3.0, 0.5)
s.constrain(Fix(a, 0.0, 0.0)) |> ignore
s.constrain(Horizontal(s.add_line(a, b))) |> ignore
let dimension = s.constrain(Distance(a, b, 5.0))
let first = s.solve_and_apply().unwrap()
assert_true(first.report.is_satisfied())
assert_eq(first.report.local_dof, 0)
assert_true((s.point(b).unwrap().x - 5.0).abs() < 1.0e-6)
assert_true(s.set_constraint(dimension, Distance(a, b, 7.0)))
let second = s.solve_and_apply().unwrap()
assert_true(second.report.is_satisfied())
assert_true((second.point(b).unwrap().x - 7.0).abs() < 1.0e-6)
}///|
test "handle typed sketch errors through the public API" {
let s = @sketch.Sketch::new()
let a = s.add_point(0.0, 0.0)
let line = s.add_line(a, a)
match s.solve_and_apply() {
Err(DegenerateLine(id)) => assert_eq(id.index, line.index)
Err(error) => fail(error.message())
Ok(_) => fail("Expected a degenerate line error")
}
}///|
test "disable a conflicting edit and inspect its state" {
let s = @sketch.Sketch::new()
let p = s.add_point(0.0, 0.0)
s.constrain(Fix(p, 0.0, 0.0)) |> ignore
let conflict = s.constrain(Fix(p, 10.0, 0.0))
assert_false(s.solve().unwrap().report.is_satisfied())
assert_true(s.set_constraint_enabled(conflict, false))
let result = s.solve_and_apply().unwrap()
assert_true(result.report.is_satisfied())
assert_false(result.report.residual(conflict).unwrap().active)
assert_true(s.constraint(conflict) is Some(Fix(_, _, _)))
}///|
test "inspect likely conflicts by descending tolerance ratio" {
let s = @sketch.Sketch::new()
let p = s.add_point(0.0, 0.0)
s.constrain(Fix(p, 0.0, 0.0)) |> ignore
s.constrain(Fix(p, 6.0, 0.0)) |> ignore
s.constrain(Fix(p, 30.0, 0.0)) |> ignore
let report = s.solve_and_apply().unwrap().report
assert_false(report.is_satisfied())
let findings = report.likely_conflicts()
assert_eq(findings.length(), 3)
assert_true(findings[0].tolerance_ratio >= findings[1].tolerance_ratio)
assert_true(findings[1].tolerance_ratio >= findings[2].tolerance_ratio)
println("Likely conflicts (heuristic inspection order):")
for finding in findings {
let kind = match s.constraint(finding.id) {
Some(Fix(_, _, _)) => "Fix"
_ => "Other"
}
println(
"#\{finding.id.index()} \{kind}; error ratio: \{finding.tolerance_ratio}",
)
}
assert_eq(s.point(p).unwrap().x, 0.0)
assert_eq(report.residuals[0].id.index(), 0)
}///|
test "edit disable remove and readd without reviving the old handle" {
let s = @sketch.Sketch::new()
let p = s.add_point(0.0, 0.0)
s.constrain(Fix(p, 0.0, 0.0)) |> ignore
let temporary = s.constrain(Fix(p, 10.0, 0.0))
assert_true(s.set_constraint(temporary, Fix(p, 20.0, 0.0)))
assert_true(s.set_constraint_enabled(temporary, false))
let before = s.solve().unwrap().report
assert_true(before.is_satisfied())
assert_true(s.remove_constraint(temporary))
assert_true(s.constraint(temporary) is None)
assert_false(s.set_constraint_enabled(temporary, true))
let replacement = s.constrain(Fix(p, 0.0, 0.0))
assert_true(replacement.index() > temporary.index())
let after = s.solve_and_apply().unwrap().report
assert_true(after.is_satisfied())
assert_true(after.residual(temporary) is None)
assert_false(before.residual(temporary).unwrap().active)
assert_true(before.residual(replacement) is None)
}///|
test "mirror a hole center about a vertical construction line" {
let s = @sketch.Sketch::new()
let bottom = s.add_point(10.0, 0.0)
let top = s.add_point(10.0, 10.0)
let left = s.add_point(6.0, 3.0)
let right = s.add_point(13.0, 2.5)
s.constrain(Fix(bottom, 10.0, 0.0)) |> ignore
s.constrain(Fix(top, 10.0, 10.0)) |> ignore
s.constrain(Fix(left, 6.0, 3.0)) |> ignore
s.constrain(Mirror(left, right, s.add_line(bottom, top))) |> ignore
let r = s.solve_and_apply().unwrap()
assert_true(r.report.is_satisfied())
assert_eq(r.report.local_dof, 0)
assert_true((r.point(right).unwrap().x - 14.0).abs() < 1.0e-6)
assert_true((r.point(right).unwrap().y - 3.0).abs() < 1.0e-6)
}///|
test "export stored sketch geometry to SVG" {
let s = @sketch.Sketch::new()
let a = s.add_point(0.0, 0.0)
let b = s.add_point(5.0, 2.0)
s.add_line(a, b) |> ignore
s.add_circle(a, 1.0) |> ignore
let svg = s.to_svg().unwrap()
assert_true(svg.contains("data-line=\"0\""))
assert_true(svg.contains("data-circle=\"0\""))
assert_eq(s.point(b).unwrap().x, 5.0)
}pub(all) enum Constraint {
Fix(PointId, Double, Double)
Coincident(PointId, PointId)
Symmetric(PointId, PointId, PointId)
Mirror(PointId, PointId, LineId)
Horizontal(LineId)
Vertical(LineId)
Distance(PointId, PointId, Double)
DistanceX(PointId, PointId, Double)
DistanceY(PointId, PointId, Double)
Parallel(LineId, LineId)
Perpendicular(LineId, LineId)
Angle(LineId, LineId, Double)
EqualLength(LineId, LineId)
LengthRatio(LineId, LineId, Double)
Midpoint(PointId, LineId)
PointOnLine(PointId, LineId)
DistanceToLine(PointId, LineId, Double)
Radius(CircleId, Double)
EqualRadius(CircleId, CircleId)
Concentric(CircleId, CircleId)
PointOnCircle(PointId, CircleId)
Tangent(LineId, CircleId)
ExternalTangent(CircleId, CircleId)
InternalTangent(CircleId, CircleId)
} derive(Debug)pub struct ConstraintResidual {
id : ConstraintId
active : Bool
length_error : Double
angular_error : Double
tolerance_ratio : Double
} derive(Debug)pub struct Sketch {
// private fields
}pub(all) enum SketchError {
InvalidGeometry(String)
InvalidConstraint(ConstraintId)
ForeignHandle
DegenerateLine(LineId)
InvalidRadius(CircleId)
ResourceLimit
InvalidOptions(String)
NumericalFailure(String)
} derive(Debug)pub(all) struct SolveOptions {
length_tolerance : Double
angular_tolerance : Double
rank_tolerance : Double
max_iterations : Int
}pub struct SolveReport {
status : SolveStatus
iterations : Int
variables : Int
equations : Int
rank : Int
local_dof : Int
dependent_equations : Int
max_tolerance_ratio : Double
residuals : Array[ConstraintResidual]
// private fields
} derive(Debug)pub(all) struct SvgBounds {
min_x : Double
min_y : Double
max_x : Double
max_y : Double
} derive(Debug)Install
Download zipA pure MoonBit nonlinear constraint solver for small 2D CAD sketches