Skip to main content

kcl_lib/std/
operation.rs

1use kcmc::each_cmd as mcmd;
2use kittycad_modeling_cmds::ModelingCmd;
3use kittycad_modeling_cmds::ok_response::OkModelingCmdResponse;
4use kittycad_modeling_cmds::shared::PathSegment;
5use kittycad_modeling_cmds::shared::Point3d;
6use kittycad_modeling_cmds::units::UnitLength;
7use kittycad_modeling_cmds::websocket::ModelingCmdReq;
8use kittycad_modeling_cmds::websocket::OkWebSocketResponseData;
9use kittycad_modeling_cmds::{self as kcmc};
10
11use crate::ExecState;
12use crate::errors::KclError;
13use crate::exec::KclValue;
14use crate::execution::ModelingCmdMeta;
15use crate::execution::Solid;
16use crate::execution::types::NumericTypeExt;
17use crate::execution::types::PrimitiveType;
18use crate::execution::types::RuntimeType;
19use crate::std::Args;
20use crate::std::args::TyF64;
21use crate::std::sketch::PlaneData;
22use crate::std::sketch::make_sketch_plane_from_orientation;
23
24#[derive(Debug)]
25pub struct Move {
26    end: Point3d<f32>,
27    point: Option<Point3d<f32>>,
28}
29
30pub fn s_curve(
31    part_width: f32,
32    part_height: f32,
33    tool_diameter: f32,
34    origin: Point3d<f32>,
35    direction: Point3d<f32>,
36    stepover: f32,
37) -> Vec<Move> {
38    // The distance before or past the part itself. This gives the operation clearance to go past the part
39    // and back into the part.
40    let overhang = tool_diameter;
41
42    // Scalar between the S Curve rows based on the tool diameter and the percent stepover.
43    let density = tool_diameter * stepover;
44
45    // Start point in world coordinate for the S Curve
46    let start_point = Point3d {
47        x: direction.x * origin.x,
48        y: (-overhang) * direction.y + origin.y,
49        z: origin.z,
50    };
51
52    let mut moves: Vec<Move> = vec![];
53
54    // The number of S curve rows
55    let interval = (part_width / density).ceil() as i32 + 1;
56
57    // Push origin
58    moves.push(Move {
59        end: Point3d {
60            x: start_point.x,
61            y: start_point.y,
62            z: start_point.z,
63        },
64        point: None,
65    });
66
67    // For the number of rows in the s curve push lines and arcs
68    for i in 0..interval {
69        // X position of the row
70        let x_row = (i as f32 * density * direction.x) + start_point.x;
71        // X position of the next row in the loop
72        let next_x_row = (((i + 1) as f32 * density) * direction.x) + start_point.x;
73        // Difference of values between the x row and next x row. Not a world coordinate value.
74        let diff_of_x_rows = (next_x_row - x_row) * direction.x / 2.0;
75
76        // First draw the line going upwards in +Y from the origin point in world coordinate
77        if i % 2 == 0 {
78            // The x position of the line and the start of the arc.
79            let x = x_row;
80            let _y = start_point.y;
81            let end_y = ((part_height + overhang + overhang) * direction.y) + start_point.y;
82            // diff_of_x_rows makes it a circular arc
83            let arc_middle_y = start_point.y + (direction.y * (part_height + overhang + overhang + diff_of_x_rows));
84
85            moves.push(Move {
86                end: Point3d {
87                    x,
88                    y: end_y,
89                    z: start_point.z,
90                },
91                point: None,
92            });
93
94            if i < interval - 1 {
95                moves.push(Move {
96                    point: Some(Point3d {
97                        x: x_row + diff_of_x_rows,
98                        y: arc_middle_y,
99                        z: start_point.z,
100                    }),
101                    end: Point3d {
102                        x: next_x_row,
103                        y: end_y,
104                        z: start_point.z,
105                    },
106                });
107            }
108        } else {
109            // -Y
110            let x = x_row;
111
112            // The move end needs to be in the direction going down.
113            let y = start_point.y;
114            // diff_of_x_rows makes it a circular arc
115            let arc_middle_y = -diff_of_x_rows * direction.y + start_point.y;
116
117            moves.push(Move {
118                end: Point3d { x, y, z: start_point.z },
119                point: None,
120            });
121            if i < interval - 1 {
122                moves.push(Move {
123                    point: Some(Point3d {
124                        x: x + diff_of_x_rows,
125                        y: arc_middle_y,
126                        z: start_point.z,
127                    }),
128                    end: Point3d {
129                        x: next_x_row,
130                        y,
131                        z: start_point.z,
132                    },
133                });
134            }
135        }
136    }
137
138    moves
139}
140
141pub async fn facing(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
142    // Get the 3D Solid
143    let solid: Solid = args.get_unlabeled_kw_arg("solid", &RuntimeType::Primitive(PrimitiveType::Solid), exec_state)?;
144
145    // Get the tool diameter
146    let tool_diameter: TyF64 = args.get_kw_arg("toolDiameter", &RuntimeType::length(), exec_state)?;
147
148    let step_over: TyF64 = args.get_kw_arg("stepOver", &RuntimeType::num_any(), exec_state)?;
149
150    let bounding_box_cmd_id = exec_state.next_uuid();
151    let cmd = ModelingCmd::from(
152        mcmd::BoundingBox::builder()
153            .entity_ids(vec![solid.id])
154            .output_unit(UnitLength::Millimeters)
155            .build(),
156    );
157    // Await the response of the AABB
158    let response = exec_state
159        .send_modeling_cmd(
160            ModelingCmdMeta::from_args_id(exec_state, &args, bounding_box_cmd_id),
161            cmd,
162        )
163        .await?;
164
165    let bounding_box = if let OkWebSocketResponseData::Modeling {
166        modeling_response: OkModelingCmdResponse::BoundingBox(data),
167    } = response
168    {
169        Some(data)
170    } else {
171        None
172    };
173
174    if let Some(bounding_box) = bounding_box {
175        let aabb = bounding_box.dimensions;
176        let center = bounding_box.center;
177        let origin = Point3d {
178            x: (center.x - (aabb.x / 2.0)) as f32,
179            y: (center.y - (aabb.y / 2.0)) as f32,
180            z: (center.z + (aabb.z / 2.0)) as f32,
181        };
182        let direction = Point3d { x: 1.0, y: 1.0, z: 1.0 };
183        let part_width = aabb.x;
184        let part_height = aabb.y;
185
186        let moves = s_curve(
187            part_width as f32,
188            part_height as f32,
189            tool_diameter.to_mm() as f32,
190            origin,
191            direction,
192            step_over.n as f32,
193        );
194
195        let plane = make_sketch_plane_from_orientation(PlaneData::XY, exec_state, &args).await?;
196        let sketch_surface_id = plane.id;
197        let enable_sketch_id = exec_state.next_uuid();
198        let path_id = exec_state.next_uuid();
199        let disable_sketch_id = exec_state.next_uuid();
200
201        let mut cmds = vec![
202            // Enter sketch mode on the surface.
203            // We call this here so you can reuse the sketch surface for multiple sketches.
204            ModelingCmdReq {
205                cmd: ModelingCmd::from(
206                    mcmd::EnableSketchMode::builder()
207                        .animated(false)
208                        .ortho(false)
209                        .entity_id(sketch_surface_id)
210                        .adjust_camera(false)
211                        .planar_normal(plane.info.x_axis.axes_cross_product(&plane.info.y_axis).into())
212                        .build(),
213                ),
214                cmd_id: enable_sketch_id.into(),
215            },
216            ModelingCmdReq {
217                cmd: ModelingCmd::from(mcmd::StartPath::default()),
218                cmd_id: path_id.into(),
219            },
220            ModelingCmdReq {
221                cmd: ModelingCmd::from(
222                    mcmd::MovePathPen::builder()
223                        .path(path_id.into())
224                        .to(Point3d {
225                            x: kittycad_modeling_cmds::length_unit::LengthUnit(moves[0].end.x as f64),
226                            y: kittycad_modeling_cmds::length_unit::LengthUnit(moves[0].end.y as f64),
227                            z: kittycad_modeling_cmds::length_unit::LengthUnit(moves[0].end.z as f64),
228                        })
229                        .build(),
230                ),
231                cmd_id: exec_state.next_uuid().into(),
232            },
233        ];
234
235        for move_op in moves {
236            match move_op.point {
237                Some(p) => {
238                    cmds.push(ModelingCmdReq {
239                        cmd_id: exec_state.next_uuid().into(),
240                        cmd: ModelingCmd::from(
241                            mcmd::ExtendPath::builder()
242                                .path(path_id.into())
243                                .segment(PathSegment::ArcTo {
244                                    interior: Point3d {
245                                        x: kittycad_modeling_cmds::length_unit::LengthUnit(p.x as f64),
246                                        y: kittycad_modeling_cmds::length_unit::LengthUnit(p.y as f64),
247                                        z: kittycad_modeling_cmds::length_unit::LengthUnit(p.z as f64),
248                                    },
249                                    end: Point3d {
250                                        x: kittycad_modeling_cmds::length_unit::LengthUnit(move_op.end.x as f64),
251                                        y: kittycad_modeling_cmds::length_unit::LengthUnit(move_op.end.y as f64),
252                                        z: kittycad_modeling_cmds::length_unit::LengthUnit(move_op.end.z as f64),
253                                    },
254                                    relative: false,
255                                })
256                                .build(),
257                        ),
258                    });
259                }
260                None => {
261                    cmds.push(ModelingCmdReq {
262                        cmd_id: exec_state.next_uuid().into(),
263                        cmd: ModelingCmd::from(
264                            mcmd::ExtendPath::builder()
265                                .path(path_id.into())
266                                .segment(PathSegment::Line {
267                                    end: Point3d {
268                                        x: kittycad_modeling_cmds::length_unit::LengthUnit(move_op.end.x as f64),
269                                        y: kittycad_modeling_cmds::length_unit::LengthUnit(move_op.end.y as f64),
270                                        z: kittycad_modeling_cmds::length_unit::LengthUnit(move_op.end.z as f64),
271                                    },
272                                    relative: false,
273                                })
274                                .build(),
275                        ),
276                    });
277                }
278            }
279        }
280
281        // disable
282        cmds.push(ModelingCmdReq {
283            cmd: ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable::default()),
284            cmd_id: disable_sketch_id.into(),
285        });
286
287        exec_state
288            .batch_modeling_cmds(ModelingCmdMeta::new(exec_state, &args.ctx, args.source_range), &cmds)
289            .await?;
290    }
291
292    // Pass this to the engine in an engine endpoint
293    Ok(KclValue::Number {
294        value: 4.0,
295        ty: kcl_api::NumericType::count(),
296        meta: vec![],
297    })
298}