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, 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, ExecState, ExtrudeSurface, GeoMeta, KclValue, ModelingCmdMeta, Path, Sketch, SketchSurface, Solid,
26 types::{PrimitiveType, RuntimeType},
27 },
28 parsing::ast::types::TagNode,
29 std::{Args, axis_or_reference::Point3dAxis3dOrGeometryReference},
30};
31
32pub async fn extrude(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
34 let sketches = args.get_unlabeled_kw_arg("sketches", &RuntimeType::sketches(), exec_state)?;
35 let length: Option<TyF64> = args.get_kw_arg_opt("length", &RuntimeType::length(), exec_state)?;
36 let to = args.get_kw_arg_opt(
37 "to",
38 &RuntimeType::Union(vec![
39 RuntimeType::point3d(),
40 RuntimeType::Primitive(PrimitiveType::Axis3d),
41 RuntimeType::Primitive(PrimitiveType::Edge),
42 RuntimeType::plane(),
43 RuntimeType::Primitive(PrimitiveType::Face),
44 RuntimeType::sketch(),
45 RuntimeType::Primitive(PrimitiveType::Solid),
46 RuntimeType::tagged_edge(),
47 RuntimeType::tagged_face(),
48 ]),
49 exec_state,
50 )?;
51 let symmetric = args.get_kw_arg_opt("symmetric", &RuntimeType::bool(), exec_state)?;
52 let bidirectional_length: Option<TyF64> =
53 args.get_kw_arg_opt("bidirectionalLength", &RuntimeType::length(), exec_state)?;
54 let tag_start = args.get_kw_arg_opt("tagStart", &RuntimeType::tag_decl(), exec_state)?;
55 let tag_end = args.get_kw_arg_opt("tagEnd", &RuntimeType::tag_decl(), exec_state)?;
56 let twist_angle: Option<TyF64> = args.get_kw_arg_opt("twistAngle", &RuntimeType::degrees(), exec_state)?;
57 let twist_angle_step: Option<TyF64> = args.get_kw_arg_opt("twistAngleStep", &RuntimeType::degrees(), exec_state)?;
58 let twist_center: Option<[TyF64; 2]> = args.get_kw_arg_opt("twistCenter", &RuntimeType::point2d(), exec_state)?;
59 let tolerance: Option<TyF64> = args.get_kw_arg_opt("tolerance", &RuntimeType::length(), exec_state)?;
60 let method: Option<String> = args.get_kw_arg_opt("method", &RuntimeType::string(), exec_state)?;
61
62 let result = inner_extrude(
63 sketches,
64 length,
65 to,
66 symmetric,
67 bidirectional_length,
68 tag_start,
69 tag_end,
70 twist_angle,
71 twist_angle_step,
72 twist_center,
73 tolerance,
74 method,
75 exec_state,
76 args,
77 )
78 .await?;
79
80 Ok(result.into())
81}
82
83#[allow(clippy::too_many_arguments)]
84async fn inner_extrude(
85 sketches: Vec<Sketch>,
86 length: Option<TyF64>,
87 to: Option<Point3dAxis3dOrGeometryReference>,
88 symmetric: Option<bool>,
89 bidirectional_length: Option<TyF64>,
90 tag_start: Option<TagNode>,
91 tag_end: Option<TagNode>,
92 twist_angle: Option<TyF64>,
93 twist_angle_step: Option<TyF64>,
94 twist_center: Option<[TyF64; 2]>,
95 tolerance: Option<TyF64>,
96 method: Option<String>,
97 exec_state: &mut ExecState,
98 args: Args,
99) -> Result<Vec<Solid>, KclError> {
100 let mut solids = Vec::new();
102 let tolerance = LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE_MM));
103
104 let extrude_method = match method.as_deref() {
105 Some("new" | "NEW") => ExtrudeMethod::New,
106 Some("merge" | "MERGE") => ExtrudeMethod::Merge,
107 None => ExtrudeMethod::default(),
108 Some(other) => {
109 return Err(KclError::new_semantic(KclErrorDetails::new(
110 format!("Unknown merge method {other}, try using `MERGE` or `NEW`"),
111 vec![args.source_range],
112 )));
113 }
114 };
115
116 if symmetric.unwrap_or(false) && bidirectional_length.is_some() {
117 return Err(KclError::new_semantic(KclErrorDetails::new(
118 "You cannot give both `symmetric` and `bidirectional` params, you have to choose one or the other"
119 .to_owned(),
120 vec![args.source_range],
121 )));
122 }
123
124 if (length.is_some() || twist_angle.is_some()) && to.is_some() {
125 return Err(KclError::new_semantic(KclErrorDetails::new(
126 "You cannot give `length` or `twist` params with the `to` param, you have to choose one or the other"
127 .to_owned(),
128 vec![args.source_range],
129 )));
130 }
131
132 let bidirection = bidirectional_length.map(|l| LengthUnit(l.to_mm()));
133
134 let opposite = match (symmetric, bidirection) {
135 (Some(true), _) => Opposite::Symmetric,
136 (None, None) => Opposite::None,
137 (Some(false), None) => Opposite::None,
138 (None, Some(length)) => Opposite::Other(length),
139 (Some(false), Some(length)) => Opposite::Other(length),
140 };
141
142 for sketch in &sketches {
143 let id = exec_state.next_uuid();
144 let cmd = match (&twist_angle, &twist_angle_step, &twist_center, length.clone(), &to) {
145 (Some(angle), angle_step, center, Some(length), None) => {
146 let center = center.clone().map(point_to_mm).map(Point2d::from).unwrap_or_default();
147 let total_rotation_angle = Angle::from_degrees(angle.to_degrees(exec_state, args.source_range));
148 let angle_step_size = Angle::from_degrees(
149 angle_step
150 .clone()
151 .map(|a| a.to_degrees(exec_state, args.source_range))
152 .unwrap_or(15.0),
153 );
154 ModelingCmd::from(mcmd::TwistExtrude {
155 target: sketch.id.into(),
156 distance: LengthUnit(length.to_mm()),
157 faces: Default::default(),
158 center_2d: center,
159 total_rotation_angle,
160 angle_step_size,
161 tolerance,
162 })
163 }
164 (None, None, None, Some(length), None) => ModelingCmd::from(mcmd::Extrude {
165 target: sketch.id.into(),
166 distance: LengthUnit(length.to_mm()),
167 faces: Default::default(),
168 opposite: opposite.clone(),
169 extrude_method,
170 }),
171 (None, None, None, None, Some(to)) => match to {
172 Point3dAxis3dOrGeometryReference::Point(point) => ModelingCmd::from(mcmd::ExtrudeToReference {
173 target: sketch.id.into(),
174 reference: ExtrudeReference::Point {
175 point: KPoint3d {
176 x: LengthUnit(point[0].to_mm()),
177 y: LengthUnit(point[1].to_mm()),
178 z: LengthUnit(point[2].to_mm()),
179 },
180 },
181 faces: Default::default(),
182 extrude_method,
183 }),
184 Point3dAxis3dOrGeometryReference::Axis { direction, origin } => {
185 ModelingCmd::from(mcmd::ExtrudeToReference {
186 target: sketch.id.into(),
187 reference: ExtrudeReference::Axis {
188 axis: KPoint3d {
189 x: direction[0].to_mm(),
190 y: direction[1].to_mm(),
191 z: direction[2].to_mm(),
192 },
193 point: KPoint3d {
194 x: LengthUnit(origin[0].to_mm()),
195 y: LengthUnit(origin[1].to_mm()),
196 z: LengthUnit(origin[2].to_mm()),
197 },
198 },
199 faces: Default::default(),
200 extrude_method,
201 })
202 }
203 Point3dAxis3dOrGeometryReference::Plane(plane) => {
204 let plane_id = if plane.value == crate::exec::PlaneType::Uninit {
205 if plane.info.origin.units.is_none() {
206 return Err(KclError::new_semantic(KclErrorDetails::new(
207 "Origin of plane has unknown units".to_string(),
208 vec![args.source_range],
209 )));
210 }
211 let sketch_plane = crate::std::sketch::make_sketch_plane_from_orientation(
212 plane.clone().info.into_plane_data(),
213 exec_state,
214 &args,
215 )
216 .await?;
217 sketch_plane.id
218 } else {
219 plane.id
220 };
221 ModelingCmd::from(mcmd::ExtrudeToReference {
222 target: sketch.id.into(),
223 reference: ExtrudeReference::EntityReference { entity_id: plane_id },
224 faces: Default::default(),
225 extrude_method,
226 })
227 }
228 Point3dAxis3dOrGeometryReference::Edge(edge_ref) => {
229 let edge_id = edge_ref.get_engine_id(exec_state, &args)?;
230 ModelingCmd::from(mcmd::ExtrudeToReference {
231 target: sketch.id.into(),
232 reference: ExtrudeReference::EntityReference { entity_id: edge_id },
233 faces: Default::default(),
234 extrude_method,
235 })
236 }
237 Point3dAxis3dOrGeometryReference::Face(face_tag) => {
238 let face_id = face_tag.get_face_id_from_tag(exec_state, &args, false).await?;
239 ModelingCmd::from(mcmd::ExtrudeToReference {
240 target: sketch.id.into(),
241 reference: ExtrudeReference::EntityReference { entity_id: face_id },
242 faces: Default::default(),
243 extrude_method,
244 })
245 }
246 Point3dAxis3dOrGeometryReference::Sketch(sketch_ref) => ModelingCmd::from(mcmd::ExtrudeToReference {
247 target: sketch.id.into(),
248 reference: ExtrudeReference::EntityReference {
249 entity_id: sketch_ref.id,
250 },
251 faces: Default::default(),
252 extrude_method,
253 }),
254 Point3dAxis3dOrGeometryReference::Solid(solid) => ModelingCmd::from(mcmd::ExtrudeToReference {
255 target: sketch.id.into(),
256 reference: ExtrudeReference::EntityReference { entity_id: solid.id },
257 faces: Default::default(),
258 extrude_method,
259 }),
260 Point3dAxis3dOrGeometryReference::TaggedEdgeOrFace(tag) => {
261 let tagged_edge_or_face = args.get_tag_engine_info(exec_state, tag)?;
262 let tagged_edge_or_face_id = tagged_edge_or_face.id;
263 ModelingCmd::from(mcmd::ExtrudeToReference {
264 target: sketch.id.into(),
265 reference: ExtrudeReference::EntityReference {
266 entity_id: tagged_edge_or_face_id,
267 },
268 faces: Default::default(),
269 extrude_method,
270 })
271 }
272 },
273 (Some(_), _, _, None, None) => {
274 return Err(KclError::new_semantic(KclErrorDetails::new(
275 "The `length` parameter must be provided when using twist angle for extrusion.".to_owned(),
276 vec![args.source_range],
277 )));
278 }
279 (_, _, _, None, None) => {
280 return Err(KclError::new_semantic(KclErrorDetails::new(
281 "Either `length` or `to` parameter must be provided for extrusion.".to_owned(),
282 vec![args.source_range],
283 )));
284 }
285 (_, _, _, Some(_), Some(_)) => {
286 return Err(KclError::new_semantic(KclErrorDetails::new(
287 "You cannot give both `length` and `to` params, you have to choose one or the other".to_owned(),
288 vec![args.source_range],
289 )));
290 }
291 (_, _, _, _, _) => {
292 return Err(KclError::new_semantic(KclErrorDetails::new(
293 "Invalid combination of parameters for extrusion.".to_owned(),
294 vec![args.source_range],
295 )));
296 }
297 };
298 let cmds = sketch.build_sketch_mode_cmds(exec_state, ModelingCmdReq { cmd_id: id.into(), cmd });
299 exec_state
300 .batch_modeling_cmds(ModelingCmdMeta::from_args_id(&args, id), &cmds)
301 .await?;
302
303 solids.push(
304 do_post_extrude(
305 sketch,
306 id.into(),
307 false,
308 &NamedCapTags {
309 start: tag_start.as_ref(),
310 end: tag_end.as_ref(),
311 },
312 extrude_method,
313 exec_state,
314 &args,
315 None,
316 None,
317 )
318 .await?,
319 );
320 }
321
322 Ok(solids)
323}
324
325#[derive(Debug, Default)]
326pub(crate) struct NamedCapTags<'a> {
327 pub start: Option<&'a TagNode>,
328 pub end: Option<&'a TagNode>,
329}
330
331#[allow(clippy::too_many_arguments)]
332pub(crate) async fn do_post_extrude<'a>(
333 sketch: &Sketch,
334 solid_id: ArtifactId,
335 sectional: bool,
336 named_cap_tags: &'a NamedCapTags<'a>,
337 extrude_method: ExtrudeMethod,
338 exec_state: &mut ExecState,
339 args: &Args,
340 edge_id: Option<Uuid>,
341 clone_id_map: Option<&HashMap<Uuid, Uuid>>, ) -> Result<Solid, KclError> {
343 exec_state
346 .batch_modeling_cmd(
347 args.into(),
348 ModelingCmd::from(mcmd::ObjectBringToFront { object_id: sketch.id }),
349 )
350 .await?;
351
352 let any_edge_id = if let Some(edge_id) = sketch.mirror {
353 edge_id
354 } else if let Some(id) = edge_id {
355 id
356 } else {
357 let Some(any_edge_id) = sketch.paths.first().map(|edge| edge.get_base().geo_meta.id) else {
360 return Err(KclError::new_type(KclErrorDetails::new(
361 "Expected a non-empty sketch".to_owned(),
362 vec![args.source_range],
363 )));
364 };
365 any_edge_id
366 };
367
368 let mut extrusion_info_edge_id = any_edge_id;
370 if sketch.clone.is_some() && clone_id_map.is_some() {
371 extrusion_info_edge_id = if let Some(clone_map) = clone_id_map {
372 if let Some(new_edge_id) = clone_map.get(&extrusion_info_edge_id) {
373 *new_edge_id
374 } else {
375 extrusion_info_edge_id
376 }
377 } else {
378 any_edge_id
379 };
380 }
381
382 let mut sketch = sketch.clone();
383 sketch.is_closed = true;
384
385 if let SketchSurface::Face(ref face) = sketch.on {
387 if extrude_method != ExtrudeMethod::New {
389 sketch.id = face.solid.sketch.id;
390 }
391 }
392
393 let sketch_id = if let Some(cloned_from) = sketch.clone
395 && clone_id_map.is_some()
396 {
397 cloned_from
398 } else {
399 sketch.id
400 };
401
402 let solid3d_info = exec_state
403 .send_modeling_cmd(
404 args.into(),
405 ModelingCmd::from(mcmd::Solid3dGetExtrusionFaceInfo {
406 edge_id: extrusion_info_edge_id,
407 object_id: sketch_id,
408 }),
409 )
410 .await?;
411
412 let face_infos = if let OkWebSocketResponseData::Modeling {
413 modeling_response: OkModelingCmdResponse::Solid3dGetExtrusionFaceInfo(data),
414 } = solid3d_info
415 {
416 data.faces
417 } else {
418 vec![]
419 };
420
421 #[cfg(feature = "artifact-graph")]
423 {
424 if !sectional {
427 exec_state
428 .batch_modeling_cmd(
429 args.into(),
430 ModelingCmd::from(mcmd::Solid3dGetAdjacencyInfo {
431 object_id: sketch.id,
432 edge_id: any_edge_id,
433 }),
434 )
435 .await?;
436 }
437 }
438
439 let Faces {
440 sides: mut face_id_map,
441 start_cap_id,
442 end_cap_id,
443 } = analyze_faces(exec_state, args, face_infos).await;
444
445 if sketch.clone.is_some()
447 && let Some(clone_id_map) = clone_id_map
448 {
449 face_id_map = face_id_map
450 .into_iter()
451 .filter_map(|(k, v)| {
452 let fe_key = clone_id_map.get(&k)?;
453 let fe_value = clone_id_map.get(&(v?)).copied();
454 Some((*fe_key, fe_value))
455 })
456 .collect::<HashMap<Uuid, Option<Uuid>>>();
457 }
458
459 let no_engine_commands = args.ctx.no_engine_commands().await;
461 let mut new_value: Vec<ExtrudeSurface> = Vec::with_capacity(sketch.paths.len() + sketch.inner_paths.len() + 2);
462 let outer_surfaces = sketch.paths.iter().flat_map(|path| {
463 if let Some(Some(actual_face_id)) = face_id_map.get(&path.get_base().geo_meta.id) {
464 surface_of(path, *actual_face_id)
465 } else if no_engine_commands {
466 crate::log::logln!(
467 "No face ID found for path ID {:?}, but in no-engine-commands mode, so faking it",
468 path.get_base().geo_meta.id
469 );
470 fake_extrude_surface(exec_state, path)
472 } else if sketch.clone.is_some()
473 && let Some(clone_map) = clone_id_map
474 {
475 let new_path = clone_map.get(&(path.get_base().geo_meta.id));
476
477 if let Some(new_path) = new_path {
478 match face_id_map.get(new_path) {
479 Some(Some(actual_face_id)) => clone_surface_of(path, *new_path, *actual_face_id),
480 _ => {
481 let actual_face_id = face_id_map.iter().find_map(|(key, value)| {
482 if let Some(value) = value {
483 if value == new_path { Some(key) } else { None }
484 } else {
485 None
486 }
487 });
488 match actual_face_id {
489 Some(actual_face_id) => clone_surface_of(path, *new_path, *actual_face_id),
490 None => {
491 crate::log::logln!("No face ID found for clone path ID {:?}, so skipping it", new_path);
492 None
493 }
494 }
495 }
496 }
497 } else {
498 None
499 }
500 } else {
501 crate::log::logln!(
502 "No face ID found for path ID {:?}, and not in no-engine-commands mode, so skipping it",
503 path.get_base().geo_meta.id
504 );
505 None
506 }
507 });
508
509 new_value.extend(outer_surfaces);
510 let inner_surfaces = sketch.inner_paths.iter().flat_map(|path| {
511 if let Some(Some(actual_face_id)) = face_id_map.get(&path.get_base().geo_meta.id) {
512 surface_of(path, *actual_face_id)
513 } else if no_engine_commands {
514 fake_extrude_surface(exec_state, path)
516 } else {
517 None
518 }
519 });
520 new_value.extend(inner_surfaces);
521
522 if let Some(tag_start) = named_cap_tags.start {
524 let Some(start_cap_id) = start_cap_id else {
525 return Err(KclError::new_type(KclErrorDetails::new(
526 format!(
527 "Expected a start cap ID for tag `{}` for extrusion of sketch {:?}",
528 tag_start.name, sketch.id
529 ),
530 vec![args.source_range],
531 )));
532 };
533
534 new_value.push(ExtrudeSurface::ExtrudePlane(crate::execution::ExtrudePlane {
535 face_id: start_cap_id,
536 tag: Some(tag_start.clone()),
537 geo_meta: GeoMeta {
538 id: start_cap_id,
539 metadata: args.source_range.into(),
540 },
541 }));
542 }
543 if let Some(tag_end) = named_cap_tags.end {
544 let Some(end_cap_id) = end_cap_id else {
545 return Err(KclError::new_type(KclErrorDetails::new(
546 format!(
547 "Expected an end cap ID for tag `{}` for extrusion of sketch {:?}",
548 tag_end.name, sketch.id
549 ),
550 vec![args.source_range],
551 )));
552 };
553
554 new_value.push(ExtrudeSurface::ExtrudePlane(crate::execution::ExtrudePlane {
555 face_id: end_cap_id,
556 tag: Some(tag_end.clone()),
557 geo_meta: GeoMeta {
558 id: end_cap_id,
559 metadata: args.source_range.into(),
560 },
561 }));
562 }
563
564 Ok(Solid {
565 id: sketch.id,
572 artifact_id: solid_id,
573 value: new_value,
574 meta: sketch.meta.clone(),
575 units: sketch.units,
576 sectional,
577 sketch,
578 start_cap_id,
579 end_cap_id,
580 edge_cuts: vec![],
581 })
582}
583
584#[derive(Default)]
585struct Faces {
586 sides: HashMap<Uuid, Option<Uuid>>,
588 end_cap_id: Option<Uuid>,
590 start_cap_id: Option<Uuid>,
592}
593
594async fn analyze_faces(exec_state: &mut ExecState, args: &Args, face_infos: Vec<ExtrusionFaceInfo>) -> Faces {
595 let mut faces = Faces {
596 sides: HashMap::with_capacity(face_infos.len()),
597 ..Default::default()
598 };
599 if args.ctx.no_engine_commands().await {
600 faces.start_cap_id = Some(exec_state.next_uuid());
602 faces.end_cap_id = Some(exec_state.next_uuid());
603 }
604 for face_info in face_infos {
605 match face_info.cap {
606 ExtrusionFaceCapType::Bottom => faces.start_cap_id = face_info.face_id,
607 ExtrusionFaceCapType::Top => faces.end_cap_id = face_info.face_id,
608 ExtrusionFaceCapType::Both => {
609 faces.end_cap_id = face_info.face_id;
610 faces.start_cap_id = face_info.face_id;
611 }
612 ExtrusionFaceCapType::None => {
613 if let Some(curve_id) = face_info.curve_id {
614 faces.sides.insert(curve_id, face_info.face_id);
615 }
616 }
617 }
618 }
619 faces
620}
621fn surface_of(path: &Path, actual_face_id: Uuid) -> Option<ExtrudeSurface> {
622 match path {
623 Path::Arc { .. }
624 | Path::TangentialArc { .. }
625 | Path::TangentialArcTo { .. }
626 | Path::Ellipse { .. }
628 | Path::Conic {.. }
629 | Path::Circle { .. }
630 | Path::CircleThreePoint { .. } => {
631 let extrude_surface = ExtrudeSurface::ExtrudeArc(crate::execution::ExtrudeArc {
632 face_id: actual_face_id,
633 tag: path.get_base().tag.clone(),
634 geo_meta: GeoMeta {
635 id: path.get_base().geo_meta.id,
636 metadata: path.get_base().geo_meta.metadata,
637 },
638 });
639 Some(extrude_surface)
640 }
641 Path::Base { .. } | Path::ToPoint { .. } | Path::Horizontal { .. } | Path::AngledLineTo { .. } => {
642 let extrude_surface = ExtrudeSurface::ExtrudePlane(crate::execution::ExtrudePlane {
643 face_id: actual_face_id,
644 tag: path.get_base().tag.clone(),
645 geo_meta: GeoMeta {
646 id: path.get_base().geo_meta.id,
647 metadata: path.get_base().geo_meta.metadata,
648 },
649 });
650 Some(extrude_surface)
651 }
652 Path::ArcThreePoint { .. } => {
653 let extrude_surface = ExtrudeSurface::ExtrudeArc(crate::execution::ExtrudeArc {
654 face_id: actual_face_id,
655 tag: path.get_base().tag.clone(),
656 geo_meta: GeoMeta {
657 id: path.get_base().geo_meta.id,
658 metadata: path.get_base().geo_meta.metadata,
659 },
660 });
661 Some(extrude_surface)
662 }
663 }
664}
665
666fn clone_surface_of(path: &Path, clone_path_id: Uuid, actual_face_id: Uuid) -> Option<ExtrudeSurface> {
667 match path {
668 Path::Arc { .. }
669 | Path::TangentialArc { .. }
670 | Path::TangentialArcTo { .. }
671 | Path::Ellipse { .. }
673 | Path::Conic {.. }
674 | Path::Circle { .. }
675 | Path::CircleThreePoint { .. } => {
676 let extrude_surface = ExtrudeSurface::ExtrudeArc(crate::execution::ExtrudeArc {
677 face_id: actual_face_id,
678 tag: path.get_base().tag.clone(),
679 geo_meta: GeoMeta {
680 id: clone_path_id,
681 metadata: path.get_base().geo_meta.metadata,
682 },
683 });
684 Some(extrude_surface)
685 }
686 Path::Base { .. } | Path::ToPoint { .. } | Path::Horizontal { .. } | Path::AngledLineTo { .. } => {
687 let extrude_surface = ExtrudeSurface::ExtrudePlane(crate::execution::ExtrudePlane {
688 face_id: actual_face_id,
689 tag: path.get_base().tag.clone(),
690 geo_meta: GeoMeta {
691 id: clone_path_id,
692 metadata: path.get_base().geo_meta.metadata,
693 },
694 });
695 Some(extrude_surface)
696 }
697 Path::ArcThreePoint { .. } => {
698 let extrude_surface = ExtrudeSurface::ExtrudeArc(crate::execution::ExtrudeArc {
699 face_id: actual_face_id,
700 tag: path.get_base().tag.clone(),
701 geo_meta: GeoMeta {
702 id: clone_path_id,
703 metadata: path.get_base().geo_meta.metadata,
704 },
705 });
706 Some(extrude_surface)
707 }
708 }
709}
710
711fn fake_extrude_surface(exec_state: &mut ExecState, path: &Path) -> Option<ExtrudeSurface> {
713 let extrude_surface = ExtrudeSurface::ExtrudePlane(crate::execution::ExtrudePlane {
714 face_id: exec_state.next_uuid(),
716 tag: path.get_base().tag.clone(),
717 geo_meta: GeoMeta {
718 id: path.get_base().geo_meta.id,
719 metadata: path.get_base().geo_meta.metadata,
720 },
721 });
722 Some(extrude_surface)
723}