1mod dialog;
13mod gestures;
14#[cfg(test)]
15mod tests;
16mod visuals;
17
18use dialog::draw_dialog;
19use visuals::visuals;
20
21use geop_core_math::{
22 primitives::CoordinateSystem,
23 scalars::Scalar,
24 vector::{Vector2, Vector3},
25};
26use geop_core_sketch::{
27 Constraint, ConstraintId, CurveId, CurveKind, PointId, Sketch, SolveReport,
28 point::{P2, add, dist, sub},
29 profile::curve_polyline,
30};
31use geop_ops::{
32 Part,
33 operation::Role,
34 ui::{
35 Action, Button, CanvasEvent, Edit, Form, ListItem, Pointer, Shape, Style, Tone, Value,
36 Visual, hit::hit_visuals,
37 },
38};
39
40use crate::{
41 AddSketchArgs,
42 constraints::{self, Selection},
43 geometry::sweep_through,
44};
45
46const AUTO_HV_SLOPE: f64 = 0.034_920_769_491_747_23; #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
52pub enum Tool {
53 #[default]
54 Select,
55 Line,
56 Rectangle,
57 Arc,
58 Circle,
59 Spline,
60 Point,
61}
62
63impl Tool {
64 const ALL: [(Tool, &'static str, &'static str, &'static str); 7] = [
66 (Tool::Select, "select", "Select", "Escape"),
67 (Tool::Line, "line", "Line", "l"),
68 (Tool::Rectangle, "rectangle", "Rectangle", "r"),
69 (Tool::Arc, "arc", "Arc", "a"),
70 (Tool::Circle, "circle", "Circle", "c"),
71 (Tool::Spline, "spline", "Spline", "s"),
72 (Tool::Point, "point", "Point", "p"),
73 ];
74
75 fn by_name(name: &str) -> Option<Tool> {
76 Tool::ALL.iter().find(|t| t.1 == name).map(|t| t.0)
77 }
78
79 fn by_shortcut(shortcut: &str) -> Option<Tool> {
80 Tool::ALL
81 .iter()
82 .find(|t| t.3 != "Escape" && t.3 == shortcut.to_lowercase())
83 .map(|t| t.0)
84 }
85}
86
87#[derive(Clone, Debug, PartialEq)]
89enum Draft {
90 Line {
91 start: PointId,
92 },
93 Rectangle {
94 corner: PointId,
95 },
96 Arc {
97 start: PointId,
98 end: Option<PointId>,
99 },
100 Circle {
101 center: PointId,
102 },
103 Spline {
104 points: Vec<PointId>,
105 },
106}
107
108#[derive(Clone, Debug, PartialEq)]
110enum Drag {
111 Points {
113 points: Vec<PointId>,
114 origins: Vec<P2>,
115 grab: P2,
116 },
117 Circle { curve: CurveId },
119 Arc { curve: CurveId },
121}
122
123#[derive(Clone, Debug, PartialEq)]
125enum Solved {
126 Solved {
127 report: SolveReport,
128 },
129 Failed {
131 error: String,
132 },
133}
134
135#[derive(Clone, Debug, Default, PartialEq)]
139pub struct SketchSession {
140 tool: Tool,
141 draft: Option<Draft>,
142 cursor: Option<P2>,
144 drag: Option<Drag>,
145 solved: Option<Solved>,
147}
148
149fn key_id(key: &str, prefix: char) -> Option<u64> {
151 key.strip_prefix(prefix)?.parse().ok()
152}
153
154fn point_key(key: &str) -> Option<PointId> {
155 key_id(key, 'p').map(PointId)
156}
157
158fn curve_key(key: &str) -> Option<CurveId> {
159 key_id(key, 'c').map(CurveId)
160}
161
162fn glyph_key(key: &str) -> Option<ConstraintId> {
164 key_id(key, 'k').map(ConstraintId)
165}
166
167fn selected(sketch: &Sketch, keys: &[String]) -> (Selection, Vec<ConstraintId>) {
170 let mut selection = Selection::default();
171 let mut constraints = Vec::new();
172 for key in keys {
173 if let Some(p) = point_key(key).filter(|p| sketch.points.contains_key(p)) {
174 selection.points.push(p);
175 } else if let Some(c) = curve_key(key).filter(|c| sketch.curves.contains_key(c)) {
176 selection.curves.push(c);
177 } else if let Some(k) = glyph_key(key).filter(|k| sketch.constraints.contains_key(k)) {
178 constraints.push(k);
179 }
180 }
181 (selection, constraints)
182}
183
184fn hit_key<S: Scalar>(
188 visuals: &[Visual<S>],
189 pointer: &Pointer<S>,
190 stages: &[&dyn Fn(&str) -> bool],
191) -> Option<String> {
192 stages.iter().find_map(|accept| {
193 hit_visuals(visuals, pointer, |v| accept(&v.key)).map(|h| h.visual.key.clone())
194 })
195}
196
197fn is_point(key: &str) -> bool {
198 point_key(key).is_some()
199}
200
201fn is_curve(key: &str) -> bool {
202 curve_key(key).is_some()
203}
204
205fn is_origin(key: &str) -> bool {
206 key == "origin"
207}
208
209fn pt(sketch: &Sketch, id: PointId) -> P2 {
211 sketch.points[&id].xy()
212}
213
214struct Editing<'a, S: Scalar> {
217 args: &'a mut AddSketchArgs,
218 s: &'a mut SketchSession,
219 selection: &'a mut Vec<String>,
220 frame: CoordinateSystem<S>,
221}
222
223fn to_world<S: Scalar>(frame: &CoordinateSystem<S>, p: P2) -> Vector3<S> {
225 frame.uv_to_xyz(&Vector2::from_array(p.map(S::from_f64)))
226}
227
228fn solve(sketch: &mut Sketch, s: &mut SketchSession, drags: &[(PointId, P2)]) {
231 s.solved = Some(match sketch.solve_with_drag(drags) {
232 Ok(report) => Solved::Solved { report },
233 Err(e) => Solved::Failed {
234 error: e.root_message().to_string(),
235 },
236 });
237}
238
239impl<S: Scalar> Editing<'_, S> {
240 fn sketch(&self) -> &Sketch {
241 &self.args.sketch
242 }
243
244 fn in_plane(&self, pointer: &Pointer<S>) -> Option<(P2, S)> {
247 let (t, p) = pointer.ray.intersect_uv_plane(&self.frame)?;
248 Some(([p[0].to_f64(), p[1].to_f64()], t))
249 }
250
251 fn commit(&mut self, mut next: Sketch) {
253 solve(&mut next, self.s, &[]);
254 self.args.sketch = next;
255 }
256
257 fn to_sketch(&self, p: &Vector3<S>) -> P2 {
259 let uvw = self.frame.to_uvw(p);
260 [uvw[0].to_f64(), uvw[1].to_f64()]
261 }
262}
263
264pub(crate) fn form<'a, S: Scalar>(
267 before: &'a Part<S>,
268 args: &AddSketchArgs,
269 s: &SketchSession,
270 selection: &[String],
271) -> Form<'a, S, AddSketchArgs, SketchSession> {
272 let mut s = s.clone();
273 if s.solved.is_none() {
274 solve(&mut args.sketch.clone(), &mut s, &[]);
275 }
276 let mut f = Form::<S, AddSketchArgs, SketchSession>::new();
277 f.reference(
279 "plane",
280 "plane",
281 args.plane.iter().cloned().collect(),
282 &[Role::Plane],
283 None,
284 false,
285 move |edit, picked| {
286 if let [plane] = picked.as_slice()
287 && plane.resolve_plane(before).is_ok()
288 {
289 edit.args.plane = Some(plane.clone());
290 edit.session.draft = None;
291 }
292 },
293 );
294 let Some(plane) = &args.plane else {
295 return f;
296 };
297 let frame = match plane.resolve_plane(before) {
298 Ok(frame) => frame,
299 Err(e) => {
300 f.text("plane_error", e.root_message(), Tone::Error);
301 return f;
302 }
303 };
304 draw_dialog(&mut f, before, &args.sketch, &s, selection);
305 f.visuals = visuals(&args.sketch, &s, &frame);
306 f.focus = Some(frame);
307 f.tool = s.tool != Tool::Select;
308 f
309}
310
311pub(crate) fn event<S: Scalar>(
313 before: &Part<S>,
314 edit: Edit<'_, AddSketchArgs, SketchSession>,
315 event: &CanvasEvent<S>,
316) {
317 editing(before, edit, |e| e.event(event));
318}
319
320fn editing<S: Scalar>(
323 before: &Part<S>,
324 edit: Edit<'_, AddSketchArgs, SketchSession>,
325 f: impl FnOnce(&mut Editing<S>),
326) {
327 let Edit {
328 args,
329 session: s,
330 selection,
331 } = edit;
332 let Some(Ok(frame)) = args.plane.as_ref().map(|p| p.resolve_plane(before)) else {
333 return;
334 };
335 if s.solved.is_none() {
336 solve(&mut args.sketch, s, &[]);
337 }
338 f(&mut Editing {
339 args,
340 s,
341 selection,
342 frame,
343 });
344}