1use std::collections::HashMap;
4
5use anyhow::Result;
6use kcmc::shared::Point3d as KPoint3d; use kcmc::{
8 ModelingCmd, each_cmd as mcmd,
9 length_unit::LengthUnit,
10 ok_response::OkModelingCmdResponse,
11 output::ExtrusionFaceInfo,
12 shared::{ExtrudeReference, ExtrusionFaceCapType, Opposite},
13 websocket::{ModelingCmdReq, OkWebSocketResponseData},
14};
15use kittycad_modeling_cmds::{
16 self as kcmc,
17 shared::{Angle, BodyType, ExtrudeMethod, Point2d},
18};
19use uuid::Uuid;
20
21use super::{DEFAULT_TOLERANCE_MM, args::TyF64, utils::point_to_mm};
22use crate::{
23 errors::{KclError, KclErrorDetails},
24 execution::{
25 ArtifactId, CreatorFace, ExecState, Extrudable, ExtrudeSurface, GeoMeta, KclValue, ModelingCmdMeta, Path,
26 ProfileClosed, Sketch, SketchSurface, Solid, SolidCreator, annotations,
27 types::{ArrayLen, PrimitiveType, RuntimeType},
28 },
29 parsing::ast::types::TagNode,
30 std::{Args, axis_or_reference::Point3dAxis3dOrGeometryReference},
31};
32
33pub async fn extrude(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
35 let sketches: Vec<Extrudable> = args.get_unlabeled_kw_arg(
36 "sketches",
37 &RuntimeType::Array(
38 Box::new(RuntimeType::Union(vec![
39 RuntimeType::sketch(),
40 RuntimeType::face(),
41 RuntimeType::tagged_face(),
42 ])),
43 ArrayLen::Minimum(1),
44 ),
45 exec_state,
46 )?;
47
48 let length: Option<TyF64> = args.get_kw_arg_opt("length", &RuntimeType::length(), exec_state)?;
49 let to = args.get_kw_arg_opt(
50 "to",
51 &RuntimeType::Union(vec![
52 RuntimeType::point3d(),
53 RuntimeType::Primitive(PrimitiveType::Axis3d),
54 RuntimeType::Primitive(PrimitiveType::Edge),
55 RuntimeType::plane(),
56 RuntimeType::Primitive(PrimitiveType::Face),
57 RuntimeType::sketch(),
58 RuntimeType::Primitive(PrimitiveType::Solid),
59 RuntimeType::tagged_edge(),
60 RuntimeType::tagged_face(),
61 ]),
62 exec_state,
63 )?;
64 let symmetric = args.get_kw_arg_opt("symmetric", &RuntimeType::bool(), exec_state)?;
65 let bidirectional_length: Option<TyF64> =
66 args.get_kw_arg_opt("bidirectionalLength", &RuntimeType::length(), exec_state)?;
67 let tag_start = args.get_kw_arg_opt("tagStart", &RuntimeType::tag_decl(), exec_state)?;
68 let tag_end = args.get_kw_arg_opt("tagEnd", &RuntimeType::tag_decl(), exec_state)?;
69 let twist_angle: Option<TyF64> = args.get_kw_arg_opt("twistAngle", &RuntimeType::degrees(), exec_state)?;
70 let twist_angle_step: Option<TyF64> = args.get_kw_arg_opt("twistAngleStep", &RuntimeType::degrees(), exec_state)?;
71 let twist_center: Option<[TyF64; 2]> = args.get_kw_arg_opt("twistCenter", &RuntimeType::point2d(), exec_state)?;
72 let tolerance: Option<TyF64> = args.get_kw_arg_opt("tolerance", &RuntimeType::length(), exec_state)?;
73 let method: Option<String> = args.get_kw_arg_opt("method", &RuntimeType::string(), exec_state)?;
74 let hide_seams: Option<bool> = args.get_kw_arg_opt("hideSeams", &RuntimeType::bool(), exec_state)?;
75 let body_type: Option<BodyType> = args.get_kw_arg_opt("bodyType", &RuntimeType::string(), exec_state)?;
76
77 let result = inner_extrude(
78 sketches,
79 length,
80 to,
81 symmetric,
82 bidirectional_length,
83 tag_start,
84 tag_end,
85 twist_angle,
86 twist_angle_step,
87 twist_center,
88 tolerance,
89 method,
90 hide_seams,
91 body_type,
92 exec_state,
93 args,
94 )
95 .await?;
96
97 Ok(result.into())
98}
99
100#[allow(clippy::too_many_arguments)]
101async fn inner_extrude(
102 extrudables: Vec<Extrudable>,
103 length: Option<TyF64>,
104 to: Option<Point3dAxis3dOrGeometryReference>,
105 symmetric: Option<bool>,
106 bidirectional_length: Option<TyF64>,
107 tag_start: Option<TagNode>,
108 tag_end: Option<TagNode>,
109 twist_angle: Option<TyF64>,
110 twist_angle_step: Option<TyF64>,
111 twist_center: Option<[TyF64; 2]>,
112 tolerance: Option<TyF64>,
113 method: Option<String>,
114 hide_seams: Option<bool>,
115 body_type: Option<BodyType>,
116 exec_state: &mut ExecState,
117 args: Args,
118) -> Result<Vec<Solid>, KclError> {
119 let body_type = body_type.unwrap_or_default();
120
121 if matches!(body_type, BodyType::Solid) && extrudables.iter().any(|sk| matches!(sk.is_closed(), ProfileClosed::No))
122 {
123 return Err(KclError::new_semantic(KclErrorDetails::new(
124 "Cannot solid extrude an open profile. Either close the profile, or use a surface extrude.".to_owned(),
125 vec![args.source_range],
126 )));
127 }
128
129 let mut solids = Vec::new();
131 let tolerance = LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE_MM));
132
133 let extrude_method = match method.as_deref() {
134 Some("new" | "NEW") => ExtrudeMethod::New,
135 Some("merge" | "MERGE") => ExtrudeMethod::Merge,
136 None => ExtrudeMethod::default(),
137 Some(other) => {
138 return Err(KclError::new_semantic(KclErrorDetails::new(
139 format!("Unknown merge method {other}, try using `MERGE` or `NEW`"),
140 vec![args.source_range],
141 )));
142 }
143 };
144
145 if symmetric.unwrap_or(false) && bidirectional_length.is_some() {
146 return Err(KclError::new_semantic(KclErrorDetails::new(
147 "You cannot give both `symmetric` and `bidirectional` params, you have to choose one or the other"
148 .to_owned(),
149 vec![args.source_range],
150 )));
151 }
152
153 if (length.is_some() || twist_angle.is_some()) && to.is_some() {
154 return Err(KclError::new_semantic(KclErrorDetails::new(
155 "You cannot give `length` or `twist` params with the `to` param, you have to choose one or the other"
156 .to_owned(),
157 vec![args.source_range],
158 )));
159 }
160
161 let bidirection = bidirectional_length.map(|l| LengthUnit(l.to_mm()));
162
163 let opposite = match (symmetric, bidirection) {
164 (Some(true), _) => Opposite::Symmetric,
165 (None, None) => Opposite::None,
166 (Some(false), None) => Opposite::None,
167 (None, Some(length)) => Opposite::Other(length),
168 (Some(false), Some(length)) => Opposite::Other(length),
169 };
170
171 for extrudable in &extrudables {
172 let extrude_cmd_id = exec_state.next_uuid();
173 let sketch_or_face_id = extrudable.id_to_extrude(exec_state, &args, false).await?;
174 let cmd = match (&twist_angle, &twist_angle_step, &twist_center, length.clone(), &to) {
175 (Some(angle), angle_step, center, Some(length), None) => {
176 let center = center.clone().map(point_to_mm).map(Point2d::from).unwrap_or_default();
177 let total_rotation_angle = Angle::from_degrees(angle.to_degrees(exec_state, args.source_range));
178 let angle_step_size = Angle::from_degrees(
179 angle_step
180 .clone()
181 .map(|a| a.to_degrees(exec_state, args.source_range))
182 .unwrap_or(15.0),
183 );
184 ModelingCmd::from(
185 mcmd::TwistExtrude::builder()
186 .target(sketch_or_face_id.into())
187 .distance(LengthUnit(length.to_mm()))
188 .center_2d(center)
189 .total_rotation_angle(total_rotation_angle)
190 .angle_step_size(angle_step_size)
191 .tolerance(tolerance)
192 .body_type(body_type)
193 .build(),
194 )
195 }
196 (None, None, None, Some(length), None) => ModelingCmd::from(
197 mcmd::Extrude::builder()
198 .target(sketch_or_face_id.into())
199 .distance(LengthUnit(length.to_mm()))
200 .opposite(opposite.clone())
201 .extrude_method(extrude_method)
202 .body_type(body_type)
203 .maybe_merge_coplanar_faces(hide_seams)
204 .build(),
205 ),
206 (None, None, None, None, Some(to)) => match to {
207 Point3dAxis3dOrGeometryReference::Point(point) => ModelingCmd::from(
208 mcmd::ExtrudeToReference::builder()
209 .target(sketch_or_face_id.into())
210 .reference(ExtrudeReference::Point {
211 point: KPoint3d {
212 x: LengthUnit(point[0].to_mm()),
213 y: LengthUnit(point[1].to_mm()),
214 z: LengthUnit(point[2].to_mm()),
215 },
216 })
217 .extrude_method(extrude_method)
218 .body_type(body_type)
219 .build(),
220 ),
221 Point3dAxis3dOrGeometryReference::Axis { direction, origin } => ModelingCmd::from(
222 mcmd::ExtrudeToReference::builder()
223 .target(sketch_or_face_id.into())
224 .reference(ExtrudeReference::Axis {
225 axis: KPoint3d {
226 x: direction[0].to_mm(),
227 y: direction[1].to_mm(),
228 z: direction[2].to_mm(),
229 },
230 point: KPoint3d {
231 x: LengthUnit(origin[0].to_mm()),
232 y: LengthUnit(origin[1].to_mm()),
233 z: LengthUnit(origin[2].to_mm()),
234 },
235 })
236 .extrude_method(extrude_method)
237 .body_type(body_type)
238 .build(),
239 ),
240 Point3dAxis3dOrGeometryReference::Plane(plane) => {
241 let plane_id = if plane.is_uninitialized() {
242 if plane.info.origin.units.is_none() {
243 return Err(KclError::new_semantic(KclErrorDetails::new(
244 "Origin of plane has unknown units".to_string(),
245 vec![args.source_range],
246 )));
247 }
248 let sketch_plane = crate::std::sketch::make_sketch_plane_from_orientation(
249 plane.clone().info.into_plane_data(),
250 exec_state,
251 &args,
252 )
253 .await?;
254 sketch_plane.id
255 } else {
256 plane.id
257 };
258 ModelingCmd::from(
259 mcmd::ExtrudeToReference::builder()
260 .target(sketch_or_face_id.into())
261 .reference(ExtrudeReference::EntityReference { entity_id: plane_id })
262 .extrude_method(extrude_method)
263 .body_type(body_type)
264 .build(),
265 )
266 }
267 Point3dAxis3dOrGeometryReference::Edge(edge_ref) => {
268 let edge_id = edge_ref.get_engine_id(exec_state, &args)?;
269 ModelingCmd::from(
270 mcmd::ExtrudeToReference::builder()
271 .target(sketch_or_face_id.into())
272 .reference(ExtrudeReference::EntityReference { entity_id: edge_id })
273 .extrude_method(extrude_method)
274 .body_type(body_type)
275 .build(),
276 )
277 }
278 Point3dAxis3dOrGeometryReference::Face(face_tag) => {
279 let face_id = face_tag.get_face_id_from_tag(exec_state, &args, false).await?;
280 ModelingCmd::from(
281 mcmd::ExtrudeToReference::builder()
282 .target(sketch_or_face_id.into())
283 .reference(ExtrudeReference::EntityReference { entity_id: face_id })
284 .extrude_method(extrude_method)
285 .body_type(body_type)
286 .build(),
287 )
288 }
289 Point3dAxis3dOrGeometryReference::Sketch(sketch_ref) => ModelingCmd::from(
290 mcmd::ExtrudeToReference::builder()
291 .target(sketch_or_face_id.into())
292 .reference(ExtrudeReference::EntityReference {
293 entity_id: sketch_ref.id,
294 })
295 .extrude_method(extrude_method)
296 .body_type(body_type)
297 .build(),
298 ),
299 Point3dAxis3dOrGeometryReference::Solid(solid) => ModelingCmd::from(
300 mcmd::ExtrudeToReference::builder()
301 .target(sketch_or_face_id.into())
302 .reference(ExtrudeReference::EntityReference { entity_id: solid.id })
303 .extrude_method(extrude_method)
304 .body_type(body_type)
305 .build(),
306 ),
307 Point3dAxis3dOrGeometryReference::TaggedEdgeOrFace(tag) => {
308 let tagged_edge_or_face = args.get_tag_engine_info(exec_state, tag)?;
309 let tagged_edge_or_face_id = tagged_edge_or_face.id;
310 ModelingCmd::from(
311 mcmd::ExtrudeToReference::builder()
312 .target(sketch_or_face_id.into())
313 .reference(ExtrudeReference::EntityReference {
314 entity_id: tagged_edge_or_face_id,
315 })
316 .extrude_method(extrude_method)
317 .body_type(body_type)
318 .build(),
319 )
320 }
321 },
322 (Some(_), _, _, None, None) => {
323 return Err(KclError::new_semantic(KclErrorDetails::new(
324 "The `length` parameter must be provided when using twist angle for extrusion.".to_owned(),
325 vec![args.source_range],
326 )));
327 }
328 (_, _, _, None, None) => {
329 return Err(KclError::new_semantic(KclErrorDetails::new(
330 "Either `length` or `to` parameter must be provided for extrusion.".to_owned(),
331 vec![args.source_range],
332 )));
333 }
334 (_, _, _, Some(_), Some(_)) => {
335 return Err(KclError::new_semantic(KclErrorDetails::new(
336 "You cannot give both `length` and `to` params, you have to choose one or the other".to_owned(),
337 vec![args.source_range],
338 )));
339 }
340 (_, _, _, _, _) => {
341 return Err(KclError::new_semantic(KclErrorDetails::new(
342 "Invalid combination of parameters for extrusion.".to_owned(),
343 vec![args.source_range],
344 )));
345 }
346 };
347
348 let being_extruded = match extrudable {
349 Extrudable::Sketch(..) => BeingExtruded::Sketch,
350 Extrudable::Face(face_tag) => {
351 let face_id = sketch_or_face_id;
352 let solid_id = match face_tag.geometry() {
353 Some(crate::execution::Geometry::Solid(solid)) => solid.id,
354 Some(crate::execution::Geometry::Sketch(sketch)) => match sketch.on {
355 SketchSurface::Face(face) => face.solid.id,
356 SketchSurface::Plane(_) => sketch.id,
357 },
358 None => face_id,
359 };
360 BeingExtruded::Face { face_id, solid_id }
361 }
362 };
363 if let Some(post_extr_sketch) = extrudable.as_sketch() {
364 let cmds = post_extr_sketch.build_sketch_mode_cmds(
365 exec_state,
366 ModelingCmdReq {
367 cmd_id: extrude_cmd_id.into(),
368 cmd,
369 },
370 );
371 exec_state
372 .batch_modeling_cmds(ModelingCmdMeta::from_args_id(exec_state, &args, extrude_cmd_id), &cmds)
373 .await?;
374 solids.push(
375 do_post_extrude(
376 &post_extr_sketch,
377 extrude_cmd_id.into(),
378 false,
379 &NamedCapTags {
380 start: tag_start.as_ref(),
381 end: tag_end.as_ref(),
382 },
383 extrude_method,
384 exec_state,
385 &args,
386 None,
387 None,
388 body_type,
389 being_extruded,
390 )
391 .await?,
392 );
393 } else {
394 return Err(KclError::new_type(KclErrorDetails::new(
395 "Expected a sketch for extrusion".to_owned(),
396 vec![args.source_range],
397 )));
398 }
399 }
400
401 Ok(solids)
402}
403
404#[derive(Debug, Default)]
405pub(crate) struct NamedCapTags<'a> {
406 pub start: Option<&'a TagNode>,
407 pub end: Option<&'a TagNode>,
408}
409
410#[derive(Debug, Clone, Copy)]
411pub enum BeingExtruded {
412 Sketch,
413 Face { face_id: Uuid, solid_id: Uuid },
414}
415
416#[allow(clippy::too_many_arguments)]
417pub(crate) async fn do_post_extrude<'a>(
418 sketch: &Sketch,
419 extrude_cmd_id: ArtifactId,
420 sectional: bool,
421 named_cap_tags: &'a NamedCapTags<'a>,
422 extrude_method: ExtrudeMethod,
423 exec_state: &mut ExecState,
424 args: &Args,
425 edge_id: Option<Uuid>,
426 clone_id_map: Option<&HashMap<Uuid, Uuid>>, body_type: BodyType,
428 being_extruded: BeingExtruded,
429) -> Result<Solid, KclError> {
430 exec_state
434 .batch_modeling_cmd(
435 ModelingCmdMeta::from_args(exec_state, args),
436 ModelingCmd::from(mcmd::ObjectBringToFront::builder().object_id(sketch.id).build()),
437 )
438 .await?;
439
440 let any_edge_id = if let Some(edge_id) = sketch.mirror {
441 edge_id
442 } else if let Some(id) = edge_id {
443 id
444 } else {
445 let Some(any_edge_id) = sketch.paths.first().map(|edge| edge.get_base().geo_meta.id) else {
448 return Err(KclError::new_type(KclErrorDetails::new(
449 "Expected a non-empty sketch".to_owned(),
450 vec![args.source_range],
451 )));
452 };
453 any_edge_id
454 };
455
456 let mut extrusion_info_edge_id = any_edge_id;
458 if sketch.clone.is_some() && clone_id_map.is_some() {
459 extrusion_info_edge_id = if let Some(clone_map) = clone_id_map {
460 if let Some(new_edge_id) = clone_map.get(&extrusion_info_edge_id) {
461 *new_edge_id
462 } else {
463 extrusion_info_edge_id
464 }
465 } else {
466 any_edge_id
467 };
468 }
469
470 let mut sketch = sketch.clone();
471 match body_type {
472 BodyType::Solid => {
473 sketch.is_closed = ProfileClosed::Explicitly;
474 }
475 BodyType::Surface => {}
476 _other => {
477 }
480 }
481
482 match (extrude_method, being_extruded) {
483 (ExtrudeMethod::Merge, BeingExtruded::Face { .. }) => {
484 if let SketchSurface::Face(ref face) = sketch.on {
487 sketch.id = face.solid.sketch_id().unwrap_or(face.solid.id);
490 }
491 }
492 (ExtrudeMethod::New, BeingExtruded::Face { .. }) => {
493 sketch.id = extrude_cmd_id.into();
496 }
497 (ExtrudeMethod::New, BeingExtruded::Sketch) => {
498 }
501 (ExtrudeMethod::Merge, BeingExtruded::Sketch) => {
502 if let SketchSurface::Face(ref face) = sketch.on {
503 sketch.id = face.solid.sketch_id().unwrap_or(face.solid.id);
506 }
507 }
508 (other, _) => {
509 return Err(KclError::new_internal(KclErrorDetails::new(
511 format!("Zoo does not yet support creating bodies via {other:?}"),
512 vec![args.source_range],
513 )));
514 }
515 }
516
517 let sketch_id = if let Some(cloned_from) = sketch.clone
519 && clone_id_map.is_some()
520 {
521 cloned_from
522 } else {
523 sketch.id
524 };
525
526 let solid3d_info = exec_state
527 .send_modeling_cmd(
528 ModelingCmdMeta::from_args(exec_state, args),
529 ModelingCmd::from(
530 mcmd::Solid3dGetExtrusionFaceInfo::builder()
531 .edge_id(extrusion_info_edge_id)
532 .object_id(sketch_id)
533 .build(),
534 ),
535 )
536 .await?;
537
538 let face_infos = if let OkWebSocketResponseData::Modeling {
539 modeling_response: OkModelingCmdResponse::Solid3dGetExtrusionFaceInfo(data),
540 } = solid3d_info
541 {
542 data.faces
543 } else {
544 vec![]
545 };
546
547 #[cfg(feature = "artifact-graph")]
549 {
550 if !sectional {
553 exec_state
554 .batch_modeling_cmd(
555 ModelingCmdMeta::from_args(exec_state, args),
556 ModelingCmd::from(
557 mcmd::Solid3dGetAdjacencyInfo::builder()
558 .object_id(sketch.id)
559 .edge_id(any_edge_id)
560 .build(),
561 ),
562 )
563 .await?;
564 }
565 }
566
567 let Faces {
568 sides: mut face_id_map,
569 start_cap_id,
570 end_cap_id,
571 } = analyze_faces(exec_state, args, face_infos).await;
572
573 if sketch.clone.is_some()
575 && let Some(clone_id_map) = clone_id_map
576 {
577 face_id_map = face_id_map
578 .into_iter()
579 .filter_map(|(k, v)| {
580 let fe_key = clone_id_map.get(&k)?;
581 let fe_value = clone_id_map.get(&(v?)).copied();
582 Some((*fe_key, fe_value))
583 })
584 .collect::<HashMap<Uuid, Option<Uuid>>>();
585 }
586
587 let no_engine_commands = args.ctx.no_engine_commands().await;
589 let mut new_value: Vec<ExtrudeSurface> = Vec::with_capacity(sketch.paths.len() + sketch.inner_paths.len() + 2);
590 let outer_surfaces = sketch.paths.iter().flat_map(|path| {
591 if let Some(Some(actual_face_id)) = face_id_map.get(&path.get_base().geo_meta.id) {
592 surface_of(path, *actual_face_id)
593 } else if no_engine_commands {
594 crate::log::logln!(
595 "No face ID found for path ID {:?}, but in no-engine-commands mode, so faking it",
596 path.get_base().geo_meta.id
597 );
598 fake_extrude_surface(exec_state, path)
600 } else if sketch.clone.is_some()
601 && let Some(clone_map) = clone_id_map
602 {
603 let new_path = clone_map.get(&(path.get_base().geo_meta.id));
604
605 if let Some(new_path) = new_path {
606 match face_id_map.get(new_path) {
607 Some(Some(actual_face_id)) => clone_surface_of(path, *new_path, *actual_face_id),
608 _ => {
609 let actual_face_id = face_id_map.iter().find_map(|(key, value)| {
610 if let Some(value) = value {
611 if value == new_path { Some(key) } else { None }
612 } else {
613 None
614 }
615 });
616 match actual_face_id {
617 Some(actual_face_id) => clone_surface_of(path, *new_path, *actual_face_id),
618 None => {
619 crate::log::logln!("No face ID found for clone path ID {:?}, so skipping it", new_path);
620 None
621 }
622 }
623 }
624 }
625 } else {
626 None
627 }
628 } else {
629 crate::log::logln!(
630 "No face ID found for path ID {:?}, and not in no-engine-commands mode, so skipping it",
631 path.get_base().geo_meta.id
632 );
633 None
634 }
635 });
636
637 new_value.extend(outer_surfaces);
638 let inner_surfaces = sketch.inner_paths.iter().flat_map(|path| {
639 if let Some(Some(actual_face_id)) = face_id_map.get(&path.get_base().geo_meta.id) {
640 surface_of(path, *actual_face_id)
641 } else if no_engine_commands {
642 fake_extrude_surface(exec_state, path)
644 } else {
645 None
646 }
647 });
648 new_value.extend(inner_surfaces);
649
650 if let Some(tag_start) = named_cap_tags.start {
652 let Some(start_cap_id) = start_cap_id else {
653 return Err(KclError::new_type(KclErrorDetails::new(
654 format!(
655 "Expected a start cap ID for tag `{}` for extrusion of sketch {:?}",
656 tag_start.name, sketch.id
657 ),
658 vec![args.source_range],
659 )));
660 };
661
662 new_value.push(ExtrudeSurface::ExtrudePlane(crate::execution::ExtrudePlane {
663 face_id: start_cap_id,
664 tag: Some(tag_start.clone()),
665 geo_meta: GeoMeta {
666 id: start_cap_id,
667 metadata: args.source_range.into(),
668 },
669 }));
670 }
671 if let Some(tag_end) = named_cap_tags.end {
672 let Some(end_cap_id) = end_cap_id else {
673 return Err(KclError::new_type(KclErrorDetails::new(
674 format!(
675 "Expected an end cap ID for tag `{}` for extrusion of sketch {:?}",
676 tag_end.name, sketch.id
677 ),
678 vec![args.source_range],
679 )));
680 };
681
682 new_value.push(ExtrudeSurface::ExtrudePlane(crate::execution::ExtrudePlane {
683 face_id: end_cap_id,
684 tag: Some(tag_end.clone()),
685 geo_meta: GeoMeta {
686 id: end_cap_id,
687 metadata: args.source_range.into(),
688 },
689 }));
690 }
691
692 let meta = sketch.meta.clone();
693 let units = sketch.units;
694 let id = sketch.id;
695 let creator = match being_extruded {
704 BeingExtruded::Sketch => SolidCreator::Sketch(sketch),
705 BeingExtruded::Face { face_id, solid_id } => SolidCreator::Face(CreatorFace {
706 face_id,
707 solid_id,
708 sketch,
709 }),
710 };
711
712 Ok(Solid {
713 id,
714 artifact_id: extrude_cmd_id,
715 value: new_value,
716 meta,
717 units,
718 sectional,
719 creator,
720 start_cap_id,
721 end_cap_id,
722 edge_cuts: vec![],
723 })
724}
725
726#[derive(Default)]
727struct Faces {
728 sides: HashMap<Uuid, Option<Uuid>>,
730 end_cap_id: Option<Uuid>,
732 start_cap_id: Option<Uuid>,
734}
735
736async fn analyze_faces(exec_state: &mut ExecState, args: &Args, face_infos: Vec<ExtrusionFaceInfo>) -> Faces {
737 let mut faces = Faces {
738 sides: HashMap::with_capacity(face_infos.len()),
739 ..Default::default()
740 };
741 if args.ctx.no_engine_commands().await {
742 faces.start_cap_id = Some(exec_state.next_uuid());
744 faces.end_cap_id = Some(exec_state.next_uuid());
745 }
746 for face_info in face_infos {
747 match face_info.cap {
748 ExtrusionFaceCapType::Bottom => faces.start_cap_id = face_info.face_id,
749 ExtrusionFaceCapType::Top => faces.end_cap_id = face_info.face_id,
750 ExtrusionFaceCapType::Both => {
751 faces.end_cap_id = face_info.face_id;
752 faces.start_cap_id = face_info.face_id;
753 }
754 ExtrusionFaceCapType::None => {
755 if let Some(curve_id) = face_info.curve_id {
756 faces.sides.insert(curve_id, face_info.face_id);
757 }
758 }
759 other => {
760 exec_state.warn(
761 crate::CompilationError {
762 source_range: args.source_range,
763 message: format!("unknown extrusion face type {other:?}"),
764 suggestion: None,
765 severity: crate::errors::Severity::Warning,
766 tag: crate::errors::Tag::Unnecessary,
767 },
768 annotations::WARN_NOT_YET_SUPPORTED,
769 );
770 }
771 }
772 }
773 faces
774}
775fn surface_of(path: &Path, actual_face_id: Uuid) -> Option<ExtrudeSurface> {
776 match path {
777 Path::Arc { .. }
778 | Path::TangentialArc { .. }
779 | Path::TangentialArcTo { .. }
780 | Path::Ellipse { .. }
782 | Path::Conic {.. }
783 | Path::Circle { .. }
784 | Path::CircleThreePoint { .. } => {
785 let extrude_surface = ExtrudeSurface::ExtrudeArc(crate::execution::ExtrudeArc {
786 face_id: actual_face_id,
787 tag: path.get_base().tag.clone(),
788 geo_meta: GeoMeta {
789 id: path.get_base().geo_meta.id,
790 metadata: path.get_base().geo_meta.metadata,
791 },
792 });
793 Some(extrude_surface)
794 }
795 Path::Base { .. } | Path::ToPoint { .. } | Path::Horizontal { .. } | Path::AngledLineTo { .. } | Path::Bezier { .. } => {
796 let extrude_surface = ExtrudeSurface::ExtrudePlane(crate::execution::ExtrudePlane {
797 face_id: actual_face_id,
798 tag: path.get_base().tag.clone(),
799 geo_meta: GeoMeta {
800 id: path.get_base().geo_meta.id,
801 metadata: path.get_base().geo_meta.metadata,
802 },
803 });
804 Some(extrude_surface)
805 }
806 Path::ArcThreePoint { .. } => {
807 let extrude_surface = ExtrudeSurface::ExtrudeArc(crate::execution::ExtrudeArc {
808 face_id: actual_face_id,
809 tag: path.get_base().tag.clone(),
810 geo_meta: GeoMeta {
811 id: path.get_base().geo_meta.id,
812 metadata: path.get_base().geo_meta.metadata,
813 },
814 });
815 Some(extrude_surface)
816 }
817 }
818}
819
820fn clone_surface_of(path: &Path, clone_path_id: Uuid, actual_face_id: Uuid) -> Option<ExtrudeSurface> {
821 match path {
822 Path::Arc { .. }
823 | Path::TangentialArc { .. }
824 | Path::TangentialArcTo { .. }
825 | Path::Ellipse { .. }
827 | Path::Conic {.. }
828 | Path::Circle { .. }
829 | Path::CircleThreePoint { .. } => {
830 let extrude_surface = ExtrudeSurface::ExtrudeArc(crate::execution::ExtrudeArc {
831 face_id: actual_face_id,
832 tag: path.get_base().tag.clone(),
833 geo_meta: GeoMeta {
834 id: clone_path_id,
835 metadata: path.get_base().geo_meta.metadata,
836 },
837 });
838 Some(extrude_surface)
839 }
840 Path::Base { .. } | Path::ToPoint { .. } | Path::Horizontal { .. } | Path::AngledLineTo { .. } | Path::Bezier { .. } => {
841 let extrude_surface = ExtrudeSurface::ExtrudePlane(crate::execution::ExtrudePlane {
842 face_id: actual_face_id,
843 tag: path.get_base().tag.clone(),
844 geo_meta: GeoMeta {
845 id: clone_path_id,
846 metadata: path.get_base().geo_meta.metadata,
847 },
848 });
849 Some(extrude_surface)
850 }
851 Path::ArcThreePoint { .. } => {
852 let extrude_surface = ExtrudeSurface::ExtrudeArc(crate::execution::ExtrudeArc {
853 face_id: actual_face_id,
854 tag: path.get_base().tag.clone(),
855 geo_meta: GeoMeta {
856 id: clone_path_id,
857 metadata: path.get_base().geo_meta.metadata,
858 },
859 });
860 Some(extrude_surface)
861 }
862 }
863}
864
865fn fake_extrude_surface(exec_state: &mut ExecState, path: &Path) -> Option<ExtrudeSurface> {
867 let extrude_surface = ExtrudeSurface::ExtrudePlane(crate::execution::ExtrudePlane {
868 face_id: exec_state.next_uuid(),
870 tag: path.get_base().tag.clone(),
871 geo_meta: GeoMeta {
872 id: path.get_base().geo_meta.id,
873 metadata: path.get_base().geo_meta.metadata,
874 },
875 });
876 Some(extrude_surface)
877}