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 let overhang = tool_diameter;
41
42 let density = tool_diameter * stepover;
44
45 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 let interval = (part_width / density).ceil() as i32 + 1;
56
57 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 i in 0..interval {
69 let x_row = (i as f32 * density * direction.x) + start_point.x;
71 let next_x_row = (((i + 1) as f32 * density) * direction.x) + start_point.x;
73 let diff_of_x_rows = (next_x_row - x_row) * direction.x / 2.0;
75
76 if i % 2 == 0 {
78 let x = x_row;
80 let _y = start_point.y;
81 let end_y = ((part_height + overhang + overhang) * direction.y) + start_point.y;
82 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 let x = x_row;
111
112 let y = start_point.y;
114 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 let solid: Solid = args.get_unlabeled_kw_arg("solid", &RuntimeType::Primitive(PrimitiveType::Solid), exec_state)?;
144
145 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 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 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 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 Ok(KclValue::Number {
294 value: 4.0,
295 ty: kcl_api::NumericType::count(),
296 meta: vec![],
297 })
298}