1use serde::Deserialize;
2use serde::Serialize;
3
4use crate::Expr;
5use crate::Number;
6use crate::ObjectId;
7use crate::front::Plane;
8
9#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
10#[ts(export, export_to = "FrontendApi.ts", rename = "ApiSketch")]
11pub struct Sketch {
12 pub args: SketchCtor,
13 pub plane: ObjectId,
14 pub segments: Vec<ObjectId>,
15 pub constraints: Vec<ObjectId>,
16}
17
18#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
22#[ts(export, export_to = "FrontendApi.ts")]
23pub struct SketchCtor {
24 pub on: Plane,
26}
27
28#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
29#[ts(export, export_to = "FrontendApi.ts", rename = "ApiSegment")]
30#[serde(tag = "type")]
31pub enum Segment {
32 Point(Point),
33 Line(Line),
34 Arc(Arc),
35 Circle(Circle),
36 ControlPointSpline(ControlPointSpline),
37}
38
39#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
40#[ts(export, export_to = "FrontendApi.ts", rename = "ApiPoint")]
41pub struct Point {
42 pub position: Point2d<Number>,
43 pub ctor: Option<PointCtor>,
44 pub owner: Option<ObjectId>,
45 pub freedom: Freedom,
46 pub constraints: Vec<ObjectId>,
47}
48
49#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
50#[ts(export, export_to = "FrontendApi.ts", rename = "ApiLine")]
51pub struct Line {
52 pub start: ObjectId,
53 pub end: ObjectId,
54 #[serde(skip_serializing_if = "Option::is_none")]
55 #[ts(optional)]
56 pub owner: Option<ObjectId>,
57 pub ctor: SegmentCtor,
59 pub ctor_applicable: bool,
65 pub construction: bool,
66}
67
68#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
69#[ts(export, export_to = "FrontendApi.ts", rename = "ApiArc")]
70pub struct Arc {
71 pub start: ObjectId,
72 pub end: ObjectId,
73 pub center: ObjectId,
74 pub ctor: SegmentCtor,
76 pub ctor_applicable: bool,
77 pub construction: bool,
78 #[serde(default, skip_serializing_if = "ArcDirection::is_ccw")]
81 #[ts(as = "Option<ArcDirection>")]
82 #[ts(optional)]
83 pub direction: ArcDirection,
84}
85
86#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
87#[ts(export, export_to = "FrontendApi.ts", rename = "ApiCircle")]
88pub struct Circle {
89 pub start: ObjectId,
90 pub center: ObjectId,
91 pub ctor: SegmentCtor,
93 pub ctor_applicable: bool,
94 pub construction: bool,
95}
96
97#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
98#[ts(export, export_to = "FrontendApi.ts")]
99#[serde(tag = "type")]
100pub enum SegmentCtor {
101 Point(PointCtor),
102 Line(LineCtor),
103 Arc(ArcCtor),
104 Circle(CircleCtor),
105 ControlPointSpline(ControlPointSplineCtor),
106}
107
108impl SegmentCtor {
109 pub fn human_friendly_kind_with_article(&self) -> &'static str {
112 match self {
113 Self::Point(_) => "a Point constructor",
114 Self::Line(_) => "a Line constructor",
115 Self::Arc(_) => "an Arc constructor",
116 Self::Circle(_) => "a Circle constructor",
117 Self::ControlPointSpline(_) => "a Control Point Spline constructor",
118 }
119 }
120}
121
122#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, ts_rs::TS)]
124#[ts(export, export_to = "FrontendApi.ts")]
125#[serde(rename_all = "lowercase")]
126pub enum ArcDirection {
127 #[default]
128 Ccw,
129 Cw,
130}
131
132impl ArcDirection {
133 pub fn is_ccw(&self) -> bool {
134 matches!(self, ArcDirection::Ccw)
135 }
136
137 pub fn is_clockwise(self) -> bool {
138 matches!(self, ArcDirection::Cw)
139 }
140
141 pub fn ccw_order<T>(self, start: T, end: T) -> (T, T) {
146 match self {
147 ArcDirection::Ccw => (start, end),
148 ArcDirection::Cw => (end, start),
149 }
150 }
151}
152
153#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
154#[ts(export, export_to = "FrontendApi.ts", rename = "ApiControlPointSpline")]
155pub struct ControlPointSpline {
156 pub controls: Vec<ObjectId>,
157 pub degree: u32,
158 pub ctor: SegmentCtor,
159 pub ctor_applicable: bool,
160 pub construction: bool,
161}
162
163#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
164#[ts(export, export_to = "FrontendApi.ts", rename = "ApiPoint2d")]
165pub struct Point2d<U: std::fmt::Debug + Clone + ts_rs::TS> {
166 pub x: U,
167 pub y: U,
168}
169
170#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
171#[ts(export, export_to = "FrontendApi.ts")]
172pub struct PointCtor {
173 pub position: Point2d<Expr>,
174}
175
176#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
177#[ts(export, export_to = "FrontendApi.ts")]
178pub struct LineCtor {
179 pub start: Point2d<Expr>,
180 pub end: Point2d<Expr>,
181 #[serde(skip_serializing_if = "Option::is_none")]
182 #[ts(optional)]
183 pub construction: Option<bool>,
184}
185
186#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize, ts_rs::TS)]
187#[ts(export, export_to = "FrontendApi.ts")]
188pub enum Freedom {
189 Free,
190 Fixed,
191 Conflict,
192}
193
194impl Freedom {
195 pub fn merge(self, other: Self) -> Self {
201 match (self, other) {
202 (Self::Conflict, _) | (_, Self::Conflict) => Self::Conflict,
203 (Self::Free, _) | (_, Self::Free) => Self::Free,
204 (Self::Fixed, Self::Fixed) => Self::Fixed,
205 }
206 }
207}
208
209#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
210#[ts(export, export_to = "FrontendApi.ts")]
211pub struct ArcCtor {
212 pub start: Point2d<Expr>,
213 pub end: Point2d<Expr>,
214 pub center: Point2d<Expr>,
215 #[serde(default, skip_serializing_if = "Option::is_none")]
218 #[ts(optional)]
219 pub direction: Option<ArcDirection>,
220 #[serde(skip_serializing_if = "Option::is_none")]
221 #[ts(optional)]
222 pub construction: Option<bool>,
223}
224
225#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
226#[ts(export, export_to = "FrontendApi.ts")]
227pub struct CircleCtor {
228 pub start: Point2d<Expr>,
229 pub center: Point2d<Expr>,
230 #[serde(skip_serializing_if = "Option::is_none")]
231 #[ts(optional)]
232 pub construction: Option<bool>,
233}
234
235#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
236#[ts(export, export_to = "FrontendApi.ts")]
237pub struct ControlPointSplineCtor {
238 pub points: Vec<Point2d<Expr>>,
239 #[serde(skip_serializing_if = "Option::is_none")]
240 #[ts(optional)]
241 pub construction: Option<bool>,
242}
243
244#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
245#[ts(export, export_to = "FrontendApi.ts", rename = "ApiConstraint")]
246#[serde(tag = "type")]
247pub enum Constraint {
253 Coincident(Coincident),
254 Distance(Distance),
255 Angle(Angle),
256 Diameter(Diameter),
257 EqualRadius(EqualRadius),
258 Fixed(Fixed),
259 HorizontalDistance(Distance),
260 VerticalDistance(Distance),
261 Horizontal(Horizontal),
262 LinesEqualLength(LinesEqualLength),
263 Midpoint(Midpoint),
264 Parallel(Parallel),
265 Perpendicular(Perpendicular),
266 Radius(Radius),
267 Symmetric(Symmetric),
268 Tangent(Tangent),
269 Vertical(Vertical),
270}
271
272#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
273#[ts(export, export_to = "FrontendApi.ts")]
274pub struct Diameter {
275 pub arc: ObjectId,
276 pub diameter: Number,
277 #[serde(rename = "labelPosition")]
278 #[serde(default, skip_serializing_if = "Option::is_none")]
279 #[ts(rename = "labelPosition")]
280 #[ts(optional)]
281 pub label_position: Option<Point2d<Number>>,
282 #[serde(default)]
283 pub source: ConstraintSource,
284}
285
286#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
287#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
288pub struct EqualRadius {
289 pub input: Vec<ObjectId>,
290}
291
292#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
295#[ts(export, export_to = "FrontendApi.ts")]
296pub struct Fixed {
297 pub points: Vec<FixedPoint>,
298}
299
300#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
302#[ts(export, export_to = "FrontendApi.ts")]
303pub struct FixedPoint {
304 pub point: ObjectId,
305 pub position: Point2d<Number>,
306}
307
308#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
309#[ts(export, export_to = "FrontendApi.ts")]
310#[serde(untagged)]
311pub enum Horizontal {
312 Line { line: ObjectId },
313 Points { points: Vec<ConstraintSegment> },
314}
315
316#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
317#[ts(export, export_to = "FrontendApi.ts")]
318pub struct LinesEqualLength {
319 pub lines: Vec<ObjectId>,
320}
321
322#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
323#[ts(export, export_to = "FrontendApi.ts")]
324pub struct Midpoint {
325 pub point: ConstraintSegment,
326 #[serde(alias = "line")]
327 pub segment: ObjectId,
328}
329
330#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
331#[ts(export, export_to = "FrontendApi.ts")]
332#[serde(untagged)]
333pub enum Vertical {
334 Line { line: ObjectId },
335 Points { points: Vec<ConstraintSegment> },
336}
337
338#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
339#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
340pub struct Parallel {
341 pub lines: Vec<ObjectId>,
342}
343
344#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
345#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
346pub struct Perpendicular {
347 pub lines: Vec<ObjectId>,
348}
349
350#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
351#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
352pub struct Symmetric {
353 pub input: Vec<ObjectId>,
354 pub axis: ObjectId,
355}
356
357#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
358#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
359pub struct Tangent {
360 pub input: Vec<ObjectId>,
361}
362
363#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
364#[ts(export, export_to = "FrontendApi.ts")]
365pub struct Coincident {
366 pub segments: Vec<ConstraintSegment>,
367}
368#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize, ts_rs::TS)]
369#[ts(export, export_to = "FrontendApi.ts")]
370#[serde(untagged)]
371pub enum ConstraintSegment {
372 Segment(ObjectId),
373 Origin(OriginLiteral),
374}
375
376impl ConstraintSegment {
377 pub const ORIGIN: Self = Self::Origin(OriginLiteral::Origin);
378}
379
380#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize, ts_rs::TS)]
381#[ts(export, export_to = "FrontendApi.ts")]
382#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
383pub enum OriginLiteral {
384 Origin,
385}
386
387#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
388#[ts(export, export_to = "FrontendApi.ts")]
389pub struct Distance {
390 pub segments: Vec<ConstraintSegment>,
391 pub distance: Number,
392 #[serde(rename = "labelPosition")]
393 #[serde(default, skip_serializing_if = "Option::is_none")]
394 #[ts(rename = "labelPosition")]
395 #[ts(optional)]
396 pub label_position: Option<Point2d<Number>>,
397 pub source: ConstraintSource,
398}
399
400#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
401#[ts(export, export_to = "FrontendApi.ts")]
402pub struct Angle {
403 pub lines: Vec<ObjectId>,
404 pub angle: Number,
405 #[serde(default, skip_serializing_if = "Option::is_none")]
406 #[ts(optional)]
407 pub sector: Option<u8>,
408 #[serde(default, skip_serializing_if = "Option::is_none")]
409 #[ts(optional)]
410 pub inverse: Option<bool>,
411 #[serde(rename = "labelPosition")]
412 #[serde(default, skip_serializing_if = "Option::is_none")]
413 #[ts(rename = "labelPosition")]
414 #[ts(optional)]
415 pub label_position: Option<Point2d<Number>>,
416 pub source: ConstraintSource,
417}
418
419#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
420#[ts(export, export_to = "FrontendApi.ts")]
421pub struct Radius {
422 pub arc: ObjectId,
423 pub radius: Number,
424 #[serde(rename = "labelPosition")]
425 #[serde(default, skip_serializing_if = "Option::is_none")]
426 #[ts(rename = "labelPosition")]
427 #[ts(optional)]
428 pub label_position: Option<Point2d<Number>>,
429 #[serde(default)]
430 pub source: ConstraintSource,
431}
432
433#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize, ts_rs::TS)]
434#[ts(export, export_to = "FrontendApi.ts")]
435pub struct ConstraintSource {
436 pub expr: String,
437 pub is_literal: bool,
438}
439
440impl From<ObjectId> for ConstraintSegment {
441 fn from(value: ObjectId) -> Self {
442 Self::Segment(value)
443 }
444}