1use indexmap::IndexMap;
2use kcl_error::SourceRange;
3use parse_display::Display;
4use parse_display::FromStr;
5use schemars::JsonSchema;
6use serde::Deserialize;
7use serde::Serialize;
8use serde::ser::SerializeSeq;
9use uuid::Uuid;
10
11use crate::ArtifactId;
12use crate::NodePath;
13use crate::ObjectId;
14use crate::UnitLength;
15
16pub type DummyPathToNode = Vec<()>;
17
18fn serialize_dummy_path_to_node<S>(_path_to_node: &DummyPathToNode, serializer: S) -> Result<S::Ok, S::Error>
19where
20 S: serde::Serializer,
21{
22 let seq = serializer.serialize_seq(Some(0))?;
23 seq.end()
24}
25
26#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
27#[ts(export_to = "Artifact.ts")]
28#[serde(rename_all = "camelCase")]
29pub struct CodeRef {
30 pub range: SourceRange,
31 pub node_path: NodePath,
32 #[serde(default, serialize_with = "serialize_dummy_path_to_node")]
34 #[ts(type = "Array<[string | number, string]>")]
35 pub path_to_node: DummyPathToNode,
36}
37
38impl CodeRef {
39 pub fn placeholder(range: SourceRange) -> Self {
40 Self {
41 range,
42 node_path: Default::default(),
43 path_to_node: Vec::new(),
44 }
45 }
46}
47
48#[derive(Debug, Hash, Eq, Copy, Clone, Deserialize, Serialize, JsonSchema, PartialEq, ts_rs::TS, Display, FromStr)]
49#[ts(export)]
50#[serde(rename_all = "camelCase")]
51pub enum PlaneName {
52 #[display("XY")]
54 Xy,
55 #[display("-XY")]
57 NegXy,
58 #[display("XZ")]
60 Xz,
61 #[display("-XZ")]
63 NegXz,
64 #[display("YZ")]
66 Yz,
67 #[display("-YZ")]
69 NegYz,
70}
71
72#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Default, ts_rs::TS)]
73#[ts(export_to = "Artifact.ts")]
74pub struct ArtifactPoint3d {
77 pub x: f64,
78 pub y: f64,
79 pub z: f64,
80 pub units: Option<UnitLength>,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
84#[ts(export_to = "Artifact.ts")]
85#[serde(rename_all = "camelCase")]
86pub struct ArtifactPlaneInfo {
89 pub origin: ArtifactPoint3d,
90 pub x_axis: ArtifactPoint3d,
91 pub y_axis: ArtifactPoint3d,
92 pub z_axis: ArtifactPoint3d,
93}
94
95#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
96#[ts(export_to = "Artifact.ts")]
97#[serde(rename_all = "snake_case")]
98pub enum ArtifactSweepMethod {
101 New,
102 Merge,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
106#[ts(export_to = "Artifact.ts")]
107#[serde(rename_all = "camelCase")]
108pub struct CompositeSolid {
109 pub id: ArtifactId,
110 pub consumed: bool,
112 pub sub_type: CompositeSolidSubType,
113 #[serde(default, skip_serializing_if = "Option::is_none")]
116 pub output_index: Option<usize>,
117 pub solid_ids: Vec<ArtifactId>,
119 pub tool_ids: Vec<ArtifactId>,
121 pub code_ref: CodeRef,
122 #[serde(default, skip_serializing_if = "Option::is_none")]
125 pub composite_solid_id: Option<ArtifactId>,
126 #[serde(default, skip_serializing_if = "Vec::is_empty")]
128 pub pattern_ids: Vec<ArtifactId>,
129}
130
131#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
132#[ts(export_to = "Artifact.ts")]
133#[serde(rename_all = "camelCase")]
134pub enum CompositeSolidSubType {
135 Intersect,
136 Subtract,
137 Split,
138 Union,
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
142#[ts(export_to = "Artifact.ts")]
143#[serde(rename_all = "camelCase")]
144pub struct Plane {
145 pub id: ArtifactId,
146 pub path_ids: Vec<ArtifactId>,
147 pub code_ref: CodeRef,
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
151#[ts(export_to = "Artifact.ts")]
152#[serde(rename_all = "camelCase")]
153pub struct Path {
154 pub id: ArtifactId,
155 pub sub_type: PathSubType,
156 pub plane_id: ArtifactId,
157 pub seg_ids: Vec<ArtifactId>,
158 pub consumed: bool,
160 #[serde(default, skip_serializing_if = "Option::is_none")]
161 pub sweep_id: Option<ArtifactId>,
164 pub trajectory_sweep_id: Option<ArtifactId>,
166 #[serde(default, skip_serializing_if = "Option::is_none")]
167 pub solid2d_id: Option<ArtifactId>,
168 pub code_ref: CodeRef,
169 #[serde(default, skip_serializing_if = "Option::is_none")]
172 pub composite_solid_id: Option<ArtifactId>,
173 #[serde(default, skip_serializing_if = "Option::is_none")]
176 pub sketch_block_id: Option<ArtifactId>,
177 #[serde(default, skip_serializing_if = "Option::is_none")]
180 pub origin_path_id: Option<ArtifactId>,
181 #[serde(default, skip_serializing_if = "Option::is_none")]
183 pub inner_path_id: Option<ArtifactId>,
184 #[serde(default, skip_serializing_if = "Option::is_none")]
187 pub outer_path_id: Option<ArtifactId>,
188 #[serde(default, skip_serializing_if = "Vec::is_empty")]
190 pub pattern_ids: Vec<ArtifactId>,
191}
192
193#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
194#[ts(export_to = "Artifact.ts")]
195#[serde(rename_all = "camelCase")]
196pub enum PathSubType {
197 Sketch,
198 Region,
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
202#[ts(export_to = "Artifact.ts")]
203#[serde(rename_all = "camelCase")]
204pub struct Segment {
205 pub id: ArtifactId,
206 pub path_id: ArtifactId,
207 #[serde(default, skip_serializing_if = "Option::is_none")]
210 pub source_segment_id: Option<ArtifactId>,
211 #[serde(default, skip_serializing_if = "Option::is_none")]
214 pub original_seg_id: Option<ArtifactId>,
215 #[serde(default, skip_serializing_if = "Option::is_none")]
216 pub surface_id: Option<ArtifactId>,
217 pub edge_ids: Vec<ArtifactId>,
218 #[serde(default, skip_serializing_if = "Option::is_none")]
219 pub edge_cut_id: Option<ArtifactId>,
220 pub code_ref: CodeRef,
221 pub common_surface_ids: Vec<ArtifactId>,
222}
223
224#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
226#[ts(export_to = "Artifact.ts")]
227#[serde(rename_all = "camelCase")]
228pub struct Sweep {
229 pub id: ArtifactId,
230 pub sub_type: SweepSubType,
231 pub path_id: ArtifactId,
232 pub surface_ids: Vec<ArtifactId>,
233 pub edge_ids: Vec<ArtifactId>,
234 pub code_ref: CodeRef,
235 #[serde(default, skip_serializing_if = "Option::is_none")]
238 pub source_sweep_id: Option<ArtifactId>,
239 pub trajectory_id: Option<ArtifactId>,
243 pub method: ArtifactSweepMethod,
244 pub consumed: bool,
246 #[serde(default, skip_serializing_if = "Vec::is_empty")]
248 pub pattern_ids: Vec<ArtifactId>,
249}
250
251#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
252#[ts(export_to = "Artifact.ts")]
253#[serde(rename_all = "camelCase")]
254pub enum SweepSubType {
255 Extrusion,
256 ExtrusionTwist,
257 Revolve,
258 RevolveAboutEdge,
259 Loft,
260 Blend,
261 Sweep,
262}
263
264#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
265#[ts(export_to = "Artifact.ts")]
266#[serde(rename_all = "camelCase")]
267pub struct Solid2d {
268 pub id: ArtifactId,
269 pub path_id: ArtifactId,
270}
271
272#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
273#[ts(export_to = "Artifact.ts")]
274#[serde(rename_all = "camelCase")]
275pub struct PrimitiveFace {
276 pub id: ArtifactId,
277 pub solid_id: ArtifactId,
278 pub code_ref: CodeRef,
279}
280
281#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
282#[ts(export_to = "Artifact.ts")]
283#[serde(rename_all = "camelCase")]
284pub struct PrimitiveEdge {
285 pub id: ArtifactId,
286 pub solid_id: ArtifactId,
287 pub code_ref: CodeRef,
288}
289
290#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
291#[ts(export_to = "Artifact.ts")]
292#[serde(rename_all = "camelCase")]
293pub struct PlaneOfFace {
294 pub id: ArtifactId,
295 pub face_id: ArtifactId,
296 pub code_ref: CodeRef,
297}
298
299#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
300#[ts(export_to = "Artifact.ts")]
301#[serde(rename_all = "camelCase")]
302pub struct StartSketchOnFace {
303 pub id: ArtifactId,
304 pub face_id: ArtifactId,
305 pub code_ref: CodeRef,
306}
307
308#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
309#[ts(export_to = "Artifact.ts")]
310#[serde(rename_all = "camelCase")]
311pub struct StartSketchOnPlane {
312 pub id: ArtifactId,
313 pub plane_id: ArtifactId,
314 pub code_ref: CodeRef,
315}
316
317#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
318#[ts(export_to = "Artifact.ts")]
319#[serde(rename_all = "camelCase")]
320pub struct SketchBlock {
321 pub id: ArtifactId,
322 #[serde(default, skip_serializing_if = "Option::is_none")]
324 pub standard_plane: Option<PlaneName>,
325 #[serde(default, skip_serializing_if = "Option::is_none")]
327 pub plane_id: Option<ArtifactId>,
328 #[serde(default, skip_serializing_if = "Option::is_none")]
330 pub plane_info: Option<ArtifactPlaneInfo>,
331 #[serde(default, skip_serializing_if = "Option::is_none")]
335 pub path_id: Option<ArtifactId>,
336 pub code_ref: CodeRef,
337 pub sketch_id: ObjectId,
339}
340
341#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
342#[ts(export_to = "Artifact.ts")]
343#[serde(rename_all = "camelCase")]
344pub enum SketchBlockConstraintType {
345 Angle,
346 Coincident,
347 Distance,
348 Diameter,
349 EqualRadius,
350 Fixed,
351 HorizontalDistance,
352 VerticalDistance,
353 Horizontal,
354 LinesEqualLength,
355 Midpoint,
356 Parallel,
357 Perpendicular,
358 Radius,
359 Symmetric,
360 Tangent,
361 Vertical,
362}
363
364#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
365#[ts(export_to = "Artifact.ts")]
366#[serde(rename_all = "camelCase")]
367pub struct SketchBlockConstraint {
368 pub id: ArtifactId,
369 pub sketch_id: ObjectId,
371 pub constraint_id: ObjectId,
373 pub constraint_type: SketchBlockConstraintType,
374 pub code_ref: CodeRef,
375}
376
377#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
378#[ts(export_to = "Artifact.ts")]
379#[serde(rename_all = "camelCase")]
380pub struct Wall {
381 pub id: ArtifactId,
382 pub seg_id: ArtifactId,
383 pub edge_cut_edge_ids: Vec<ArtifactId>,
384 pub sweep_id: ArtifactId,
385 pub path_ids: Vec<ArtifactId>,
386 pub face_code_ref: CodeRef,
389 pub cmd_id: Uuid,
391}
392
393#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
394#[ts(export_to = "Artifact.ts")]
395#[serde(rename_all = "camelCase")]
396pub struct Cap {
397 pub id: ArtifactId,
398 pub sub_type: CapSubType,
399 pub edge_cut_edge_ids: Vec<ArtifactId>,
400 pub sweep_id: ArtifactId,
401 pub path_ids: Vec<ArtifactId>,
402 pub face_code_ref: CodeRef,
405 pub cmd_id: Uuid,
407}
408
409#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
410#[ts(export_to = "Artifact.ts")]
411#[serde(rename_all = "camelCase")]
412pub enum CapSubType {
413 Start,
414 End,
415}
416
417#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
418#[ts(export_to = "Artifact.ts")]
419#[serde(rename_all = "camelCase")]
420pub struct SweepEdge {
421 pub id: ArtifactId,
422 pub sub_type: SweepEdgeSubType,
423 pub seg_id: ArtifactId,
424 pub cmd_id: Uuid,
425 #[serde(skip)]
427 pub index: usize,
428 pub sweep_id: ArtifactId,
429 pub common_surface_ids: Vec<ArtifactId>,
430}
431
432#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
433#[ts(export_to = "Artifact.ts")]
434#[serde(rename_all = "camelCase")]
435pub enum SweepEdgeSubType {
436 Opposite,
437 Adjacent,
438 PreviousAdjacent,
439}
440
441#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
442#[ts(export_to = "Artifact.ts")]
443#[serde(rename_all = "camelCase")]
444pub struct EdgeCut {
445 pub id: ArtifactId,
446 pub sub_type: EdgeCutSubType,
447 pub consumed_edge_id: ArtifactId,
448 pub edge_ids: Vec<ArtifactId>,
449 #[serde(default, skip_serializing_if = "Option::is_none")]
450 pub surface_id: Option<ArtifactId>,
451 pub code_ref: CodeRef,
452}
453
454#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
455#[ts(export_to = "Artifact.ts")]
456#[serde(rename_all = "camelCase")]
457pub enum EdgeCutSubType {
458 Fillet,
459 Chamfer,
460 Custom,
461}
462
463#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
464#[ts(export_to = "Artifact.ts")]
465#[serde(rename_all = "camelCase")]
466pub struct EdgeCutEdge {
467 pub id: ArtifactId,
468 pub edge_cut_id: ArtifactId,
469 pub surface_id: ArtifactId,
470}
471
472#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
473#[ts(export_to = "Artifact.ts")]
474#[serde(rename_all = "camelCase")]
475pub struct Helix {
476 pub id: ArtifactId,
477 pub axis_id: Option<ArtifactId>,
480 pub code_ref: CodeRef,
481 pub trajectory_sweep_id: Option<ArtifactId>,
483 pub consumed: bool,
485}
486
487#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
488#[ts(export_to = "Artifact.ts")]
489#[serde(rename_all = "camelCase")]
490pub struct GdtAnnotationArtifact {
491 pub id: ArtifactId,
492 pub code_ref: CodeRef,
493}
494
495#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
496#[ts(export_to = "Artifact.ts")]
497#[serde(rename_all = "camelCase")]
498pub struct Pattern {
499 pub id: ArtifactId,
500 pub sub_type: PatternSubType,
501 pub source_id: ArtifactId,
503 pub copy_ids: Vec<ArtifactId>,
505 pub copy_face_ids: Vec<ArtifactId>,
507 pub copy_edge_ids: Vec<ArtifactId>,
509 pub code_ref: CodeRef,
510}
511
512#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
513#[ts(export_to = "Artifact.ts")]
514#[serde(rename_all = "camelCase")]
515pub enum PatternSubType {
516 Circular,
517 Linear,
518 Transform,
519}
520
521#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
522#[ts(export_to = "Artifact.ts")]
523#[serde(tag = "type", rename_all = "camelCase")]
524pub enum Artifact {
525 CompositeSolid(CompositeSolid),
526 Plane(Plane),
527 Path(Path),
528 Segment(Segment),
529 Solid2d(Solid2d),
530 PrimitiveFace(PrimitiveFace),
531 PrimitiveEdge(PrimitiveEdge),
532 PlaneOfFace(PlaneOfFace),
533 StartSketchOnFace(StartSketchOnFace),
534 StartSketchOnPlane(StartSketchOnPlane),
535 SketchBlock(SketchBlock),
536 SketchBlockConstraint(SketchBlockConstraint),
537 Sweep(Sweep),
538 Wall(Wall),
539 Cap(Cap),
540 SweepEdge(SweepEdge),
541 EdgeCut(EdgeCut),
542 EdgeCutEdge(EdgeCutEdge),
543 Helix(Helix),
544 GdtAnnotation(GdtAnnotationArtifact),
545 Pattern(Pattern),
546}
547
548impl Artifact {
549 pub fn id(&self) -> ArtifactId {
550 match self {
551 Self::CompositeSolid(a) => a.id,
552 Self::Plane(a) => a.id,
553 Self::Path(a) => a.id,
554 Self::Segment(a) => a.id,
555 Self::Solid2d(a) => a.id,
556 Self::PrimitiveFace(a) => a.id,
557 Self::PrimitiveEdge(a) => a.id,
558 Self::PlaneOfFace(a) => a.id,
559 Self::StartSketchOnFace(a) => a.id,
560 Self::StartSketchOnPlane(a) => a.id,
561 Self::SketchBlock(a) => a.id,
562 Self::SketchBlockConstraint(a) => a.id,
563 Self::Sweep(a) => a.id,
564 Self::Wall(a) => a.id,
565 Self::Cap(a) => a.id,
566 Self::SweepEdge(a) => a.id,
567 Self::EdgeCut(a) => a.id,
568 Self::EdgeCutEdge(a) => a.id,
569 Self::Helix(a) => a.id,
570 Self::GdtAnnotation(a) => a.id,
571 Self::Pattern(a) => a.id,
572 }
573 }
574
575 pub fn code_ref(&self) -> Option<&CodeRef> {
578 match self {
579 Self::CompositeSolid(a) => Some(&a.code_ref),
580 Self::Plane(a) => Some(&a.code_ref),
581 Self::Path(a) => Some(&a.code_ref),
582 Self::Segment(a) => Some(&a.code_ref),
583 Self::Solid2d(_) => None,
584 Self::PrimitiveFace(a) => Some(&a.code_ref),
585 Self::PrimitiveEdge(a) => Some(&a.code_ref),
586 Self::PlaneOfFace(a) => Some(&a.code_ref),
587 Self::StartSketchOnFace(a) => Some(&a.code_ref),
588 Self::StartSketchOnPlane(a) => Some(&a.code_ref),
589 Self::SketchBlock(a) => Some(&a.code_ref),
590 Self::SketchBlockConstraint(a) => Some(&a.code_ref),
591 Self::Sweep(a) => Some(&a.code_ref),
592 Self::Wall(_) | Self::Cap(_) | Self::SweepEdge(_) => None,
593 Self::EdgeCut(a) => Some(&a.code_ref),
594 Self::EdgeCutEdge(_) => None,
595 Self::Helix(a) => Some(&a.code_ref),
596 Self::GdtAnnotation(a) => Some(&a.code_ref),
597 Self::Pattern(a) => Some(&a.code_ref),
598 }
599 }
600
601 pub fn face_code_ref(&self) -> Option<&CodeRef> {
604 match self {
605 Self::PrimitiveFace(a) => Some(&a.code_ref),
606 Self::Wall(a) => Some(&a.face_code_ref),
607 Self::Cap(a) => Some(&a.face_code_ref),
608 _ => None,
609 }
610 }
611}
612
613#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema, ts_rs::TS)]
614#[ts(export_to = "Artifact.ts")]
615#[serde(rename_all = "camelCase")]
616pub struct ArtifactGraph {
617 map: IndexMap<ArtifactId, Artifact>,
618 item_count: usize,
619}
620
621impl ArtifactGraph {
622 pub fn from_parts(map: IndexMap<ArtifactId, Artifact>, item_count: usize) -> Self {
623 Self { map, item_count }
624 }
625
626 pub fn into_parts(self) -> (IndexMap<ArtifactId, Artifact>, usize) {
627 (self.map, self.item_count)
628 }
629
630 pub fn item_count(&self) -> usize {
631 self.item_count
632 }
633
634 pub fn get(&self, id: &ArtifactId) -> Option<&Artifact> {
635 self.map.get(id)
636 }
637
638 pub fn len(&self) -> usize {
639 self.map.len()
640 }
641
642 pub fn is_empty(&self) -> bool {
643 self.map.is_empty()
644 }
645
646 pub fn iter(&self) -> impl Iterator<Item = (&ArtifactId, &Artifact)> {
647 self.map.iter()
648 }
649
650 pub fn values(&self) -> impl Iterator<Item = &Artifact> {
651 self.map.values()
652 }
653
654 pub fn clear(&mut self) {
655 self.map.clear();
656 self.item_count = 0;
657 }
658}
659
660#[cfg(test)]
661mod tests {
662 use super::*;
663
664 #[test]
665 fn default_artifact_graph_json_round_trip() {
666 let graph = ArtifactGraph::default();
667 let json = serde_json::to_string(&graph).unwrap();
668
669 assert_eq!(json, r#"{"map":{},"itemCount":0}"#);
670 assert_eq!(serde_json::from_str::<ArtifactGraph>(&json).unwrap(), graph);
671 }
672
673 #[test]
674 fn artifact_sweep_method_uses_engine_json_shape() {
675 assert_eq!(serde_json::to_string(&ArtifactSweepMethod::New).unwrap(), r#""new""#);
676 assert_eq!(
677 serde_json::to_string(&ArtifactSweepMethod::Merge).unwrap(),
678 r#""merge""#
679 );
680 }
681}