1use anyhow::Result;
4use kcmc::ModelingCmd;
5use kcmc::each_cmd as mcmd;
6use kcmc::ok_response::OkModelingCmdResponse;
7use kcmc::websocket::OkWebSocketResponseData;
8use kittycad_modeling_cmds as kcmc;
9use serde::Deserialize;
10use serde::Serialize;
11use uuid::Uuid;
12
13use crate::SourceRange;
14use crate::errors::KclError;
15use crate::errors::KclErrorDetails;
16use crate::execution::BoundedEdge;
17use crate::execution::ExecState;
18use crate::execution::ExtrudeSurface;
19use crate::execution::KclObjectFields;
20use crate::execution::KclValue;
21use crate::execution::ModelingCmdMeta;
22use crate::execution::Solid;
23use crate::execution::TagIdentifier;
24use crate::execution::types::ArrayLen;
25use crate::execution::types::RuntimeType;
26use crate::std::Args;
27use crate::std::args::TyF64;
28use crate::std::fillet::EdgeReference;
29use crate::std::sketch::FaceTag;
30
31#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, ts_rs::TS)]
33#[serde(untagged)]
34pub enum TagOrUuid {
35 Uuid(Uuid),
36 Tag(Box<TagIdentifier>),
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, ts_rs::TS)]
41#[serde(rename_all = "camelCase")]
42pub struct UnresolvedEdgeSpecifier {
43 #[serde(default, skip_serializing_if = "Vec::is_empty")]
44 pub side_faces: Vec<TagOrUuid>,
45 #[serde(default, skip_serializing_if = "Vec::is_empty")]
46 pub end_faces: Vec<TagOrUuid>,
47 #[serde(default, skip_serializing_if = "Option::is_none")]
48 pub index: Option<u32>,
49}
50
51pub(crate) async fn get_face_ids_for_edge(
55 exec_state: &mut ExecState,
56 object_id: Uuid,
57 edge_id: Uuid,
58 args: &Args,
59) -> Result<Vec<Uuid>, KclError> {
60 if args.ctx.no_engine_commands().await {
61 return Ok(vec![exec_state.next_uuid(), exec_state.next_uuid()]);
64 }
65
66 let resp = exec_state
67 .send_modeling_cmd(
68 ModelingCmdMeta::from_args(exec_state, args),
69 ModelingCmd::from(
70 mcmd::Solid3dGetAllEdgeFaces::builder()
71 .object_id(object_id)
72 .edge_id(edge_id)
73 .build(),
74 ),
75 )
76 .await?;
77 let OkWebSocketResponseData::Modeling {
78 modeling_response: OkModelingCmdResponse::Solid3dGetAllEdgeFaces(info),
79 } = &resp
80 else {
81 return Err(KclError::new_engine(KclErrorDetails::new(
82 format!("Solid3dGetAllEdgeFaces response was not as expected: {resp:?}"),
83 vec![args.source_range],
84 )));
85 };
86 if info.faces.is_empty() || info.faces.len() > 2 {
87 return Err(KclError::new_engine(KclErrorDetails::new(
88 format!(
89 "Solid3dGetAllEdgeFaces returned {} face(s) for edge {edge_id}, expected 1 or 2",
90 info.faces.len()
91 ),
92 vec![args.source_range],
93 )));
94 }
95 Ok(info.faces.clone())
96}
97
98pub(super) fn check_tag_not_ambiguous(tag: &TagIdentifier, args: &Args) -> Result<(), KclError> {
100 let all_infos = tag.get_all_cur_info();
101 if all_infos.len() > 1 {
102 return Err(KclError::new_semantic(KclErrorDetails::new(
103 format!(
104 "Tag `{}` is ambiguous: it maps to {} edges in the region. Use a more specific reference.",
105 tag.value,
106 all_infos.len()
107 ),
108 vec![args.source_range],
109 )));
110 }
111 Ok(())
112}
113
114pub async fn get_opposite_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
116 let input_edge = args.get_unlabeled_kw_arg("edge", &RuntimeType::tagged_edge(), exec_state)?;
117
118 let edge = inner_get_opposite_edge(input_edge, exec_state, args.clone()).await?;
119 Ok(KclValue::Uuid {
120 value: edge,
121 meta: vec![args.source_range.into()],
122 })
123}
124
125async fn inner_get_opposite_edge(
126 edge: TagIdentifier,
127 exec_state: &mut ExecState,
128 args: Args,
129) -> Result<Uuid, KclError> {
130 check_tag_not_ambiguous(&edge, &args)?;
131 if args.ctx.no_engine_commands().await {
132 return Ok(exec_state.next_uuid());
133 }
134 let face_id = args.get_adjacent_face_to_tag(exec_state, &edge, false).await?;
135
136 let tagged_path = args.get_tag_engine_info(exec_state, &edge)?;
137 let tagged_path_id = tagged_path.id;
138 let sketch_id = tagged_path.geometry.id();
139
140 let resp = exec_state
141 .send_modeling_cmd(
142 ModelingCmdMeta::from_args(exec_state, &args),
143 ModelingCmd::from(
144 mcmd::Solid3dGetOppositeEdge::builder()
145 .edge_id(tagged_path_id)
146 .object_id(sketch_id)
147 .face_id(face_id)
148 .build(),
149 ),
150 )
151 .await?;
152 let OkWebSocketResponseData::Modeling {
153 modeling_response: OkModelingCmdResponse::Solid3dGetOppositeEdge(opposite_edge),
154 } = &resp
155 else {
156 return Err(KclError::new_engine(KclErrorDetails::new(
157 format!("mcmd::Solid3dGetOppositeEdge response was not as expected: {resp:?}"),
158 vec![args.source_range],
159 )));
160 };
161
162 Ok(opposite_edge.edge)
163}
164
165pub async fn get_next_adjacent_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
167 let input_edge = args.get_unlabeled_kw_arg("edge", &RuntimeType::tagged_edge(), exec_state)?;
168
169 let edge = inner_get_next_adjacent_edge(input_edge, exec_state, args.clone()).await?;
170 Ok(KclValue::Uuid {
171 value: edge,
172 meta: vec![args.source_range.into()],
173 })
174}
175
176async fn inner_get_next_adjacent_edge(
177 edge: TagIdentifier,
178 exec_state: &mut ExecState,
179 args: Args,
180) -> Result<Uuid, KclError> {
181 check_tag_not_ambiguous(&edge, &args)?;
182 if args.ctx.no_engine_commands().await {
183 return Ok(exec_state.next_uuid());
184 }
185 let face_id = args.get_adjacent_face_to_tag(exec_state, &edge, false).await?;
186
187 let tagged_path = args.get_tag_engine_info(exec_state, &edge)?;
188 let tagged_path_id = tagged_path.id;
189 let sketch_id = tagged_path.geometry.id();
190
191 let resp = exec_state
192 .send_modeling_cmd(
193 ModelingCmdMeta::from_args(exec_state, &args),
194 ModelingCmd::from(
195 mcmd::Solid3dGetNextAdjacentEdge::builder()
196 .edge_id(tagged_path_id)
197 .object_id(sketch_id)
198 .face_id(face_id)
199 .build(),
200 ),
201 )
202 .await?;
203
204 let OkWebSocketResponseData::Modeling {
205 modeling_response: OkModelingCmdResponse::Solid3dGetNextAdjacentEdge(adjacent_edge),
206 } = &resp
207 else {
208 return Err(KclError::new_engine(KclErrorDetails::new(
209 format!("mcmd::Solid3dGetNextAdjacentEdge response was not as expected: {resp:?}"),
210 vec![args.source_range],
211 )));
212 };
213
214 adjacent_edge.edge.ok_or_else(|| {
215 KclError::new_type(KclErrorDetails::new(
216 format!("No edge found next adjacent to tag: `{}`", edge.value),
217 vec![args.source_range],
218 ))
219 })
220}
221
222pub async fn get_previous_adjacent_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
224 let input_edge = args.get_unlabeled_kw_arg("edge", &RuntimeType::tagged_edge(), exec_state)?;
225
226 let edge = inner_get_previous_adjacent_edge(input_edge, exec_state, args.clone()).await?;
227 Ok(KclValue::Uuid {
228 value: edge,
229 meta: vec![args.source_range.into()],
230 })
231}
232
233async fn inner_get_previous_adjacent_edge(
234 edge: TagIdentifier,
235 exec_state: &mut ExecState,
236 args: Args,
237) -> Result<Uuid, KclError> {
238 check_tag_not_ambiguous(&edge, &args)?;
239 if args.ctx.no_engine_commands().await {
240 return Ok(exec_state.next_uuid());
241 }
242 let face_id = args.get_adjacent_face_to_tag(exec_state, &edge, false).await?;
243
244 let tagged_path = args.get_tag_engine_info(exec_state, &edge)?;
245 let tagged_path_id = tagged_path.id;
246 let sketch_id = tagged_path.geometry.id();
247
248 let resp = exec_state
249 .send_modeling_cmd(
250 ModelingCmdMeta::from_args(exec_state, &args),
251 ModelingCmd::from(
252 mcmd::Solid3dGetPrevAdjacentEdge::builder()
253 .edge_id(tagged_path_id)
254 .object_id(sketch_id)
255 .face_id(face_id)
256 .build(),
257 ),
258 )
259 .await?;
260 let OkWebSocketResponseData::Modeling {
261 modeling_response: OkModelingCmdResponse::Solid3dGetPrevAdjacentEdge(adjacent_edge),
262 } = &resp
263 else {
264 return Err(KclError::new_engine(KclErrorDetails::new(
265 format!("mcmd::Solid3dGetPrevAdjacentEdge response was not as expected: {resp:?}"),
266 vec![args.source_range],
267 )));
268 };
269
270 adjacent_edge.edge.ok_or_else(|| {
271 KclError::new_type(KclErrorDetails::new(
272 format!("No edge found previous adjacent to tag: `{}`", edge.value),
273 vec![args.source_range],
274 ))
275 })
276}
277
278pub async fn get_common_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
280 let faces: Vec<FaceTag> = args.get_kw_arg(
281 "faces",
282 &RuntimeType::Array(Box::new(RuntimeType::tagged_face()), ArrayLen::Known(2)),
283 exec_state,
284 )?;
285
286 fn into_tag(face: FaceTag, source_range: SourceRange) -> Result<TagIdentifier, KclError> {
287 match face {
288 FaceTag::StartOrEnd(_) => Err(KclError::new_type(KclErrorDetails::new(
289 "getCommonEdge requires a tagged face, it cannot use `START` or `END` faces".to_owned(),
290 vec![source_range],
291 ))),
292 FaceTag::Tag(tag_identifier) => Ok(*tag_identifier),
293 }
294 }
295
296 let [face1, face2]: [FaceTag; 2] = faces.try_into().map_err(|_: Vec<FaceTag>| {
297 KclError::new_type(KclErrorDetails::new(
298 "getCommonEdge requires exactly two tags for faces".to_owned(),
299 vec![args.source_range],
300 ))
301 })?;
302
303 let face1 = into_tag(face1, args.source_range)?;
304 let face2 = into_tag(face2, args.source_range)?;
305
306 let edge = inner_get_common_edge(face1, face2, exec_state, args.clone()).await?;
307 Ok(KclValue::Uuid {
308 value: edge,
309 meta: vec![args.source_range.into()],
310 })
311}
312
313async fn inner_get_common_edge(
314 face1: TagIdentifier,
315 face2: TagIdentifier,
316 exec_state: &mut ExecState,
317 args: Args,
318) -> Result<Uuid, KclError> {
319 check_tag_not_ambiguous(&face1, &args)?;
320 check_tag_not_ambiguous(&face2, &args)?;
321 let id = exec_state.next_uuid();
322 if args.ctx.no_engine_commands().await {
323 return Ok(id);
324 }
325
326 let first_face_id = args.get_adjacent_face_to_tag(exec_state, &face1, false).await?;
327 let second_face_id = args.get_adjacent_face_to_tag(exec_state, &face2, false).await?;
328
329 let first_tagged_path = args.get_tag_engine_info(exec_state, &face1)?.clone();
330 let second_tagged_path = args.get_tag_engine_info(exec_state, &face2)?;
331
332 if first_tagged_path.geometry.id() != second_tagged_path.geometry.id() {
333 return Err(KclError::new_type(KclErrorDetails::new(
334 "getCommonEdge requires the faces to be in the same original sketch".to_string(),
335 vec![args.source_range],
336 )));
337 }
338
339 if let Some(ExtrudeSurface::Chamfer { .. } | ExtrudeSurface::Fillet { .. }) = first_tagged_path.surface {
344 exec_state
345 .flush_batch(ModelingCmdMeta::from_args(exec_state, &args), true)
346 .await?;
347 } else if let Some(ExtrudeSurface::Chamfer { .. } | ExtrudeSurface::Fillet { .. }) = second_tagged_path.surface {
348 exec_state
349 .flush_batch(ModelingCmdMeta::from_args(exec_state, &args), true)
350 .await?;
351 }
352
353 let resp = exec_state
354 .send_modeling_cmd(
355 ModelingCmdMeta::from_args_id(exec_state, &args, id),
356 ModelingCmd::from(
357 mcmd::Solid3dGetCommonEdge::builder()
358 .object_id(first_tagged_path.geometry.id())
359 .face_ids([first_face_id, second_face_id])
360 .build(),
361 ),
362 )
363 .await?;
364 let OkWebSocketResponseData::Modeling {
365 modeling_response: OkModelingCmdResponse::Solid3dGetCommonEdge(common_edge),
366 } = &resp
367 else {
368 return Err(KclError::new_engine(KclErrorDetails::new(
369 format!("mcmd::Solid3dGetCommonEdge response was not as expected: {resp:?}"),
370 vec![args.source_range],
371 )));
372 };
373
374 common_edge.edge.ok_or_else(|| {
375 KclError::new_type(KclErrorDetails::new(
376 format!(
377 "No common edge was found between `{}` and `{}`",
378 face1.value, face2.value
379 ),
380 vec![args.source_range],
381 ))
382 })
383}
384
385pub async fn get_bounded_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
386 let face = args.get_unlabeled_kw_arg("solid", &RuntimeType::solid(), exec_state)?;
387 let edge_val = args.get_kw_arg("edge", &RuntimeType::any(), exec_state)?;
388 let lower_bound = args.get_kw_arg_opt("lowerBound", &RuntimeType::num_any(), exec_state)?;
389 let upper_bound = args.get_kw_arg_opt("upperBound", &RuntimeType::num_any(), exec_state)?;
390
391 let bounded_edge = match &edge_val {
392 KclValue::Uuid { value, .. } => {
393 inner_get_bounded_edge_with_id(face, EdgeReference::Uuid(*value), lower_bound, upper_bound, exec_state, args.clone()).await?
394 }
395 KclValue::TagIdentifier(tag) => {
396 inner_get_bounded_edge_with_id(face, EdgeReference::Tag(tag.clone()), lower_bound, upper_bound, exec_state, args.clone()).await?
397 }
398 KclValue::Object { value: obj, .. } => {
399 let spec = parse_edge_specifier_object(obj, &args)?;
400 inner_get_bounded_edge_with_specifier(face, spec, lower_bound, upper_bound, &args)?
401 }
402 _ => {
403 return Err(KclError::new_type(KclErrorDetails::new(
404 "edge must be a tagged edge, edge UUID, or edge specifier object (e.g. { sideFaces = [...], endFaces = [...], index = 0 })".to_owned(),
405 vec![args.source_range],
406 )))
407 }
408 };
409 Ok(KclValue::BoundedEdge {
410 value: bounded_edge,
411 meta: vec![args.source_range.into()],
412 })
413}
414
415fn tag_or_uuid_from_value(value: &KclValue, field_name: &str, args: &Args) -> Result<TagOrUuid, KclError> {
416 match value {
417 KclValue::Uuid { value, .. } => Ok(TagOrUuid::Uuid(*value)),
418 KclValue::TagIdentifier(tag) => Ok(TagOrUuid::Tag(tag.clone())),
419 _ => Err(KclError::new_type(KclErrorDetails::new(
420 format!("{field_name} elements must be tags or UUIDs"),
421 vec![args.source_range],
422 ))),
423 }
424}
425
426fn parse_tag_or_uuid_array(
427 obj: &KclObjectFields,
428 field_name: &str,
429 required: bool,
430 args: &Args,
431) -> Result<Vec<TagOrUuid>, KclError> {
432 let Some(value) = obj.get(field_name) else {
433 return if required {
434 Err(KclError::new_type(KclErrorDetails::new(
435 format!("edge specifier object must have {field_name}"),
436 vec![args.source_range],
437 )))
438 } else {
439 Ok(Vec::new())
440 };
441 };
442 let values = value.as_slice().ok_or_else(|| {
443 KclError::new_type(KclErrorDetails::new(
444 format!("{field_name} must be an array"),
445 vec![args.source_range],
446 ))
447 })?;
448 values
449 .iter()
450 .map(|value| tag_or_uuid_from_value(value, field_name, args))
451 .collect()
452}
453
454fn parse_edge_specifier_index(obj: &KclObjectFields, args: &Args) -> Result<Option<u32>, KclError> {
455 let Some(index) = obj.get("index") else {
456 return Ok(None);
457 };
458 let KclValue::Number { value, .. } = index else {
459 return Err(KclError::new_type(KclErrorDetails::new(
460 "edge specifier 'index' must be a non-negative integer".to_owned(),
461 vec![args.source_range],
462 )));
463 };
464 if !value.is_finite() || value.fract() != 0.0 || *value < 0.0 || *value > u32::MAX as f64 {
465 return Err(KclError::new_type(KclErrorDetails::new(
466 "edge specifier 'index' must be a non-negative integer".to_owned(),
467 vec![args.source_range],
468 )));
469 }
470 Ok(Some(*value as u32))
471}
472
473pub(crate) fn is_edge_specifier_object(value: &KclValue) -> bool {
474 matches!(value, KclValue::Object { value, .. } if value.contains_key("sideFaces"))
475}
476
477pub(crate) fn parse_edge_specifier_value(value: &KclValue, args: &Args) -> Result<UnresolvedEdgeSpecifier, KclError> {
478 let KclValue::Object { value: obj, .. } = value else {
479 return Err(KclError::new_type(KclErrorDetails::new(
480 "edge specifier must be an object with 'sideFaces'".to_owned(),
481 vec![args.source_range],
482 )));
483 };
484 parse_edge_specifier_object(obj, args)
485}
486
487pub(crate) fn parse_edge_specifier_object(
489 obj: &KclObjectFields,
490 args: &Args,
491) -> Result<UnresolvedEdgeSpecifier, KclError> {
492 let side_faces = parse_tag_or_uuid_array(obj, "sideFaces", true, args)?;
493 if side_faces.is_empty() {
494 return Err(KclError::new_semantic(KclErrorDetails::new(
495 "sideFaces must be an array of at least one face, but zero were given".to_owned(),
496 vec![args.source_range],
497 )));
498 }
499 let end_faces = parse_tag_or_uuid_array(obj, "endFaces", false, args)?;
500 let index = parse_edge_specifier_index(obj, args)?;
501 Ok(UnresolvedEdgeSpecifier {
502 side_faces,
503 end_faces,
504 index,
505 })
506}
507
508async fn resolve_as_face_id(value: &TagOrUuid, exec_state: &mut ExecState, args: &Args) -> Result<Uuid, KclError> {
509 match value {
510 TagOrUuid::Uuid(uuid) => Ok(*uuid),
511 TagOrUuid::Tag(tag) => {
512 FaceTag::Tag(tag.clone())
513 .get_face_id_from_tag(exec_state, args, false)
514 .await
515 }
516 }
517}
518
519async fn resolve_as_adjacent_face_or_tag_id(
520 value: &TagOrUuid,
521 exec_state: &mut ExecState,
522 args: &Args,
523) -> Result<Uuid, KclError> {
524 match value {
525 TagOrUuid::Uuid(uuid) => Ok(*uuid),
526 TagOrUuid::Tag(tag) => match args.get_adjacent_face_to_tag(exec_state, tag, false).await {
527 Ok(face_id) => Ok(face_id),
528 Err(_) => Ok(args.get_tag_engine_info(exec_state, tag)?.id),
529 },
530 }
531}
532
533async fn resolve_as_edge_faces(
534 value: &TagOrUuid,
535 object_id: Uuid,
536 exec_state: &mut ExecState,
537 args: &Args,
538) -> Result<Vec<Uuid>, KclError> {
539 match value {
540 TagOrUuid::Uuid(uuid) => Ok(vec![*uuid]),
541 TagOrUuid::Tag(tag) => {
542 let edge_id = args.get_tag_engine_info(exec_state, tag)?.id;
543 get_face_ids_for_edge(exec_state, object_id, edge_id, args).await
544 }
545 }
546}
547
548pub(crate) async fn resolve_edge_specifier_with_face_tags(
549 unresolved: &UnresolvedEdgeSpecifier,
550 exec_state: &mut ExecState,
551 args: &Args,
552) -> Result<kcmc::shared::EdgeSpecifier, KclError> {
553 let mut side_faces = Vec::with_capacity(unresolved.side_faces.len());
554 for value in &unresolved.side_faces {
555 side_faces.push(resolve_as_face_id(value, exec_state, args).await?);
556 }
557 let mut end_faces = Vec::with_capacity(unresolved.end_faces.len());
558 for value in &unresolved.end_faces {
559 end_faces.push(resolve_as_face_id(value, exec_state, args).await?);
560 }
561 Ok(kcmc::shared::EdgeSpecifier::builder()
562 .side_faces(side_faces)
563 .end_faces(end_faces)
564 .maybe_index(unresolved.index)
565 .build())
566}
567
568pub(crate) async fn resolve_edge_specifier_with_adjacent_faces_or_tag_ids(
569 unresolved: &UnresolvedEdgeSpecifier,
570 exec_state: &mut ExecState,
571 args: &Args,
572) -> Result<kcmc::shared::EdgeSpecifier, KclError> {
573 let mut side_faces = Vec::with_capacity(unresolved.side_faces.len());
574 for value in &unresolved.side_faces {
575 side_faces.push(resolve_as_adjacent_face_or_tag_id(value, exec_state, args).await?);
576 }
577 let mut end_faces = Vec::with_capacity(unresolved.end_faces.len());
578 for value in &unresolved.end_faces {
579 end_faces.push(resolve_as_adjacent_face_or_tag_id(value, exec_state, args).await?);
580 }
581 Ok(kcmc::shared::EdgeSpecifier::builder()
582 .side_faces(side_faces)
583 .end_faces(end_faces)
584 .maybe_index(unresolved.index)
585 .build())
586}
587
588pub(crate) async fn parse_edge_refs_to_references(
589 edge_refs: Vec<KclValue>,
590 exec_state: &mut ExecState,
591 args: &Args,
592) -> Result<Vec<kcmc::shared::EdgeSpecifier>, KclError> {
593 if edge_refs.is_empty() {
594 return Err(KclError::new_semantic(KclErrorDetails::new(
595 "You must provide at least one edge".to_owned(),
596 vec![args.source_range],
597 )));
598 }
599
600 let mut edge_references = Vec::with_capacity(edge_refs.len());
601 for edge_ref_value in &edge_refs {
602 let spec = parse_edge_specifier_value(edge_ref_value, args)?;
603 edge_references.push(resolve_edge_specifier_with_face_tags(&spec, exec_state, args).await?);
604 }
605 Ok(edge_references)
606}
607
608pub(super) fn face_id_from_first_side_face(
612 spec: &UnresolvedEdgeSpecifier,
613 exec_state: &mut ExecState,
614 args: &Args,
615) -> Result<Uuid, KclError> {
616 let first = spec.side_faces.first().ok_or_else(|| {
617 KclError::new_type(KclErrorDetails::new(
618 "edge specifier must have at least one sideFace".to_owned(),
619 vec![args.source_range],
620 ))
621 })?;
622 match first {
623 TagOrUuid::Uuid(u) => Ok(*u),
624 TagOrUuid::Tag(t) => {
625 let info = args.get_tag_engine_info(exec_state, t)?;
626 Ok(info.geometry.id())
627 }
628 }
629}
630
631pub(crate) async fn inner_get_bounded_edge_with_id(
632 face: Solid,
633 edge: EdgeReference,
634 lower_bound: Option<TyF64>,
635 upper_bound: Option<TyF64>,
636 exec_state: &mut ExecState,
637 args: Args,
638) -> Result<BoundedEdge, KclError> {
639 let (lb, ub) = bounds_from_opts(lower_bound, upper_bound, &args)?;
640 let edge_id = edge.get_engine_id(exec_state, &args)?;
641 Ok(BoundedEdge {
642 face_id: face.id,
643 edge_id: Some(edge_id),
644 edge_specifier: None,
645 lower_bound: lb,
646 upper_bound: ub,
647 })
648}
649
650fn inner_get_bounded_edge_with_specifier(
651 face: Solid,
652 spec: UnresolvedEdgeSpecifier,
653 lower_bound: Option<TyF64>,
654 upper_bound: Option<TyF64>,
655 args: &Args,
656) -> Result<BoundedEdge, KclError> {
657 let (lb, ub) = bounds_from_opts(lower_bound, upper_bound, args)?;
658 Ok(BoundedEdge {
659 face_id: face.id,
660 edge_id: None,
661 edge_specifier: Some(spec),
662 lower_bound: lb,
663 upper_bound: ub,
664 })
665}
666
667fn bounds_from_opts(
668 lower_bound: Option<TyF64>,
669 upper_bound: Option<TyF64>,
670 args: &Args,
671) -> Result<(f32, f32), KclError> {
672 let lower_bound = if let Some(lower_bound) = lower_bound {
673 let val = lower_bound.n as f32;
674 if !(0.0..=1.0).contains(&val) {
675 return Err(KclError::new_semantic(KclErrorDetails::new(
676 format!(
677 "Invalid value: lowerBound must be between 0.0 and 1.0, provided {}",
678 val
679 ),
680 vec![args.source_range],
681 )));
682 }
683 val
684 } else {
685 0.0_f32
686 };
687 let upper_bound = if let Some(upper_bound) = upper_bound {
688 let val = upper_bound.n as f32;
689 if !(0.0..=1.0).contains(&val) {
690 return Err(KclError::new_semantic(KclErrorDetails::new(
691 format!(
692 "Invalid value: upperBound must be between 0.0 and 1.0, provided {}",
693 val
694 ),
695 vec![args.source_range],
696 )));
697 }
698 val
699 } else {
700 1.0_f32
701 };
702 Ok((lower_bound, upper_bound))
703}
704
705pub(crate) async fn resolve_unresolved_edge_specifier(
707 object_id: Uuid,
708 unresolved: &UnresolvedEdgeSpecifier,
709 exec_state: &mut ExecState,
710 args: &Args,
711) -> Result<kcmc::shared::EdgeSpecifier, KclError> {
712 let mut side_faces = Vec::new();
713 for v in &unresolved.side_faces {
714 side_faces.extend(resolve_as_edge_faces(v, object_id, exec_state, args).await?);
715 }
716 let mut end_faces = Vec::new();
717 for v in &unresolved.end_faces {
718 end_faces.extend(resolve_as_edge_faces(v, object_id, exec_state, args).await?);
719 }
720 Ok(kcmc::shared::EdgeSpecifier::builder()
721 .side_faces(side_faces)
722 .end_faces(end_faces)
723 .maybe_index(unresolved.index)
724 .build())
725}