geop_lib_stdlib/
drawing.rs1use geop_core_program::{
5 numeric::{NumericInput, evaluate},
6 program::Program,
7};
8use geop_ops_model::parameters::parameter_numbers;
9use std::collections::BTreeMap;
10
11use geop_core_geometry::nurb_curve::bezier_knots;
12use geop_core_math::{
13 geop_error::{GeopError, GeopResult},
14 scalars::Scalar,
15};
16use geop_core_sketch::{ConstraintId, CurveId, CurveKind, PointId, SplineShape};
17use geop_ops_model::sketch::{Constraint, Sketch, SketchPlane};
18use geop_ops_sketch::AddSketchArgs;
19
20pub struct Drawing {
28 sketch: Sketch,
29 origin: PointId,
30 formulas: BTreeMap<ConstraintId, NumericInput>,
31 numbers: BTreeMap<String, f64>,
33}
34
35impl Drawing {
36 pub fn new(program: &Program) -> GeopResult<Self> {
39 let numbers = parameter_numbers(program)?;
40 let mut sketch = Sketch::new();
41 let origin = sketch.add_fixed_point(Scalar::ZERO, Scalar::ZERO);
42 Ok(Self {
43 sketch,
44 origin,
45 formulas: BTreeMap::new(),
46 numbers,
47 })
48 }
49
50 fn value(&self, formula: &str) -> GeopResult<f64> {
52 evaluate(formula, |name| self.numbers.get(name).copied())
53 }
54
55 fn dimension(&mut self, constraint: Constraint, formula: &str) {
58 let id = self.sketch.constrain(constraint);
59 let input = NumericInput::from(formula);
60 if input.plain().is_none() {
61 self.formulas.insert(id, input);
62 }
63 }
64
65 pub fn point(&mut self, x: impl AsRef<str>, y: impl AsRef<str>) -> GeopResult<PointId> {
67 let (x, y) = (x.as_ref(), y.as_ref());
68 let at = [self.value(x)?, self.value(y)?].map(Scalar::from_f64);
69 let p = self.sketch.add_point(at[0], at[1]);
70 let origin = self.origin;
71 self.dimension(
72 Constraint::DistanceX {
73 a: origin,
74 b: p,
75 value: at[0],
76 },
77 x,
78 );
79 self.dimension(
80 Constraint::DistanceY {
81 a: origin,
82 b: p,
83 value: at[1],
84 },
85 y,
86 );
87 Ok(p)
88 }
89
90 pub fn line(&mut self, a: PointId, b: PointId) -> CurveId {
91 self.sketch.add_line(a, b)
92 }
93
94 pub fn bezier(&mut self, control: Vec<PointId>) -> CurveId {
98 let n = control.len();
99 let knots = bezier_knots(n - 1);
100 self.sketch.add_curve(CurveKind::Spline {
101 control_points: control,
102 shape: Some(SplineShape {
103 degree: n - 1,
104 knots,
105 weights: vec![Scalar::ONE; n],
106 }),
107 })
108 }
109
110 pub fn polygon(&mut self, corners: &[[String; 2]]) -> GeopResult<Vec<CurveId>> {
113 let points = corners
114 .iter()
115 .map(|[x, y]| self.point(x, y))
116 .collect::<GeopResult<Vec<_>>>()?;
117 Ok((0..points.len())
118 .map(|i| self.line(points[i], points[(i + 1) % points.len()]))
119 .collect())
120 }
121
122 pub fn arc(
125 &mut self,
126 start: PointId,
127 end: PointId,
128 radius: &str,
129 ccw: bool,
130 ) -> GeopResult<CurveId> {
131 let r = self.value(radius)?;
132 let at = |p: PointId| {
133 let point = &self.sketch.points[&p];
134 [point.x.to_f64(), point.y.to_f64()]
135 };
136 let ([x0, y0], [x1, y1]) = (at(start), at(end));
137 let chord = (x1 - x0).hypot(y1 - y0);
138 if chord > 2.0 * r {
139 return Err(GeopError::new(format!(
140 "no arc of radius {radius:?} = {r} joins points {chord} apart"
141 )));
142 }
143 let sweep = 2.0 * (chord / (2.0 * r)).asin();
144 let arc = self.sketch.add_arc(
145 start,
146 end,
147 Scalar::from_f64(if ccw { sweep } else { -sweep }),
148 );
149 self.dimension(
150 Constraint::Radius {
151 curve: arc,
152 value: Scalar::from_f64(r),
153 },
154 radius,
155 );
156 Ok(arc)
157 }
158
159 pub fn circle(&mut self, center: PointId, diameter: &str) -> GeopResult<CurveId> {
161 let d = self.value(diameter)?;
162 let circle = self.sketch.add_circle(center, Scalar::from_f64(d / 2.0));
163 self.dimension(
164 Constraint::Diameter {
165 curve: circle,
166 value: Scalar::from_f64(d),
167 },
168 diameter,
169 );
170 Ok(circle)
171 }
172
173 pub fn origin(&self) -> PointId {
175 self.origin
176 }
177
178 pub fn on(self, plane: SketchPlane) -> AddSketchArgs {
183 AddSketchArgs {
184 formulas: self.formulas,
185 ..AddSketchArgs::on(plane, self.sketch)
186 }
187 }
188}