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