2024-09-04 14:34:33 -07:00
|
|
|
//! Standard library lofts.
|
|
|
|
|
2024-12-13 13:07:52 -06:00
|
|
|
use std::num::NonZeroU32;
|
|
|
|
|
2024-09-04 14:34:33 -07:00
|
|
|
use anyhow::Result;
|
2024-09-19 14:06:29 -07:00
|
|
|
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, ModelingCmd};
|
2024-09-18 17:04:04 -05:00
|
|
|
use kittycad_modeling_cmds as kcmc;
|
2024-09-04 14:34:33 -07:00
|
|
|
|
2025-04-14 05:58:19 -04:00
|
|
|
use super::{args::TyF64, DEFAULT_TOLERANCE};
|
2024-09-04 14:34:33 -07:00
|
|
|
use crate::{
|
|
|
|
errors::{KclError, KclErrorDetails},
|
2025-04-23 10:58:35 +12:00
|
|
|
execution::{
|
|
|
|
types::{NumericType, RuntimeType},
|
|
|
|
ExecState, KclValue, Sketch, Solid,
|
|
|
|
},
|
2025-03-19 12:18:19 -07:00
|
|
|
parsing::ast::types::TagNode,
|
2025-03-31 15:28:15 -04:00
|
|
|
std::{extrude::do_post_extrude, Args},
|
2024-09-04 14:34:33 -07:00
|
|
|
};
|
|
|
|
|
2024-09-05 17:29:07 -07:00
|
|
|
const DEFAULT_V_DEGREE: u32 = 2;
|
2024-09-04 14:34:33 -07:00
|
|
|
|
|
|
|
/// Create a 3D surface or solid by interpolating between two or more sketches.
|
2024-10-09 19:38:40 -04:00
|
|
|
pub async fn loft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
2025-03-21 10:56:55 +13:00
|
|
|
let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?;
|
2024-12-13 13:07:52 -06:00
|
|
|
let v_degree: NonZeroU32 = args
|
2025-05-29 16:48:47 +12:00
|
|
|
.get_kw_arg_opt_typed("vDegree", &RuntimeType::count(), exec_state)?
|
2024-12-13 13:07:52 -06:00
|
|
|
.unwrap_or(NonZeroU32::new(DEFAULT_V_DEGREE).unwrap());
|
|
|
|
// Attempt to approximate rational curves (such as arcs) using a bezier.
|
|
|
|
// This will remove banding around interpolations between arcs and non-arcs. It may produce errors in other scenarios
|
|
|
|
// Over time, this field won't be necessary.
|
2025-05-29 16:48:47 +12:00
|
|
|
let bez_approximate_rational = args
|
|
|
|
.get_kw_arg_opt_typed("bezApproximateRational", &RuntimeType::bool(), exec_state)?
|
|
|
|
.unwrap_or(false);
|
2024-12-13 13:07:52 -06:00
|
|
|
// This can be set to override the automatically determined topological base curve, which is usually the first section encountered.
|
2025-05-29 16:48:47 +12:00
|
|
|
let base_curve_index: Option<u32> =
|
|
|
|
args.get_kw_arg_opt_typed("baseCurveIndex", &RuntimeType::count(), exec_state)?;
|
2024-12-13 13:07:52 -06:00
|
|
|
// Tolerance for the loft operation.
|
2025-04-23 10:58:35 +12:00
|
|
|
let tolerance: Option<TyF64> = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::length(), exec_state)?;
|
2025-03-19 12:18:19 -07:00
|
|
|
let tag_start = args.get_kw_arg_opt("tagStart")?;
|
|
|
|
let tag_end = args.get_kw_arg_opt("tagEnd")?;
|
2024-09-04 14:34:33 -07:00
|
|
|
|
2025-01-22 09:42:09 +13:00
|
|
|
let value = inner_loft(
|
2024-12-13 13:07:52 -06:00
|
|
|
sketches,
|
|
|
|
v_degree,
|
|
|
|
bez_approximate_rational,
|
|
|
|
base_curve_index,
|
2025-04-23 10:58:35 +12:00
|
|
|
tolerance,
|
2025-03-19 12:18:19 -07:00
|
|
|
tag_start,
|
|
|
|
tag_end,
|
2024-12-13 13:07:52 -06:00
|
|
|
exec_state,
|
|
|
|
args,
|
|
|
|
)
|
|
|
|
.await?;
|
2025-01-22 09:42:09 +13:00
|
|
|
Ok(KclValue::Solid { value })
|
2024-09-04 14:34:33 -07:00
|
|
|
}
|
|
|
|
|
2025-03-19 12:18:19 -07:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
2024-10-09 19:38:40 -04:00
|
|
|
async fn inner_loft(
|
|
|
|
sketches: Vec<Sketch>,
|
2024-12-13 13:07:52 -06:00
|
|
|
v_degree: NonZeroU32,
|
|
|
|
bez_approximate_rational: bool,
|
|
|
|
base_curve_index: Option<u32>,
|
2025-04-23 10:58:35 +12:00
|
|
|
tolerance: Option<TyF64>,
|
2025-03-19 12:18:19 -07:00
|
|
|
tag_start: Option<TagNode>,
|
|
|
|
tag_end: Option<TagNode>,
|
2024-10-09 19:38:40 -04:00
|
|
|
exec_state: &mut ExecState,
|
|
|
|
args: Args,
|
|
|
|
) -> Result<Box<Solid>, KclError> {
|
2024-09-04 14:34:33 -07:00
|
|
|
// Make sure we have at least two sketches.
|
2024-09-27 15:44:44 -07:00
|
|
|
if sketches.len() < 2 {
|
2025-06-02 13:30:57 -05:00
|
|
|
return Err(KclError::new_semantic(KclErrorDetails::new(
|
2025-05-19 14:13:10 -04:00
|
|
|
format!(
|
2024-09-04 14:34:33 -07:00
|
|
|
"Loft requires at least two sketches, but only {} were provided.",
|
2024-09-27 15:44:44 -07:00
|
|
|
sketches.len()
|
2024-09-04 14:34:33 -07:00
|
|
|
),
|
2025-05-19 14:13:10 -04:00
|
|
|
vec![args.source_range],
|
|
|
|
)));
|
2024-09-04 14:34:33 -07:00
|
|
|
}
|
|
|
|
|
2024-12-17 09:38:32 +13:00
|
|
|
let id = exec_state.next_uuid();
|
2024-09-04 14:34:33 -07:00
|
|
|
args.batch_modeling_cmd(
|
|
|
|
id,
|
2024-09-18 17:04:04 -05:00
|
|
|
ModelingCmd::from(mcmd::Loft {
|
2024-09-27 15:44:44 -07:00
|
|
|
section_ids: sketches.iter().map(|group| group.id).collect(),
|
2024-12-13 13:07:52 -06:00
|
|
|
base_curve_index,
|
|
|
|
bez_approximate_rational,
|
2025-04-23 10:58:35 +12:00
|
|
|
tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)),
|
2024-12-13 13:07:52 -06:00
|
|
|
v_degree,
|
2024-09-18 17:04:04 -05:00
|
|
|
}),
|
2024-09-04 14:34:33 -07:00
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
// Using the first sketch as the base curve, idk we might want to change this later.
|
2025-01-09 15:36:50 -05:00
|
|
|
let mut sketch = sketches[0].clone();
|
|
|
|
// Override its id with the loft id so we can get its faces later
|
|
|
|
sketch.id = id;
|
2025-03-17 17:57:26 +13:00
|
|
|
Ok(Box::new(
|
2025-03-19 12:18:19 -07:00
|
|
|
do_post_extrude(
|
|
|
|
&sketch,
|
|
|
|
id.into(),
|
2025-04-23 10:58:35 +12:00
|
|
|
TyF64::new(0.0, NumericType::mm()),
|
2025-03-20 20:42:41 -04:00
|
|
|
false,
|
2025-03-19 12:18:19 -07:00
|
|
|
&super::extrude::NamedCapTags {
|
|
|
|
start: tag_start.as_ref(),
|
|
|
|
end: tag_end.as_ref(),
|
|
|
|
},
|
|
|
|
exec_state,
|
|
|
|
&args,
|
2025-05-16 23:02:30 +01:00
|
|
|
None,
|
2025-03-19 12:18:19 -07:00
|
|
|
)
|
|
|
|
.await?,
|
2025-03-17 17:57:26 +13:00
|
|
|
))
|
2024-09-04 14:34:33 -07:00
|
|
|
}
|