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 ;
use derive_docs ::stdlib ;
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
use crate ::{
errors ::{ KclError , KclErrorDetails } ,
2024-12-07 07:16:04 +13:00
execution ::{ ExecState , KclValue , Sketch , Solid } ,
2024-09-04 14:34:33 -07:00
std ::{ extrude ::do_post_extrude , fillet ::default_tolerance , Args } ,
} ;
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 > {
2024-12-13 13:07:52 -06:00
let sketches = args . get_unlabeled_kw_arg ( " sketches " ) ? ;
let v_degree : NonZeroU32 = args
. get_kw_arg_opt ( " vDegree " )
. 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.
let bez_approximate_rational = args . get_kw_arg_opt ( " bezApproximateRational " ) . unwrap_or ( false ) ;
// This can be set to override the automatically determined topological base curve, which is usually the first section encountered.
let base_curve_index : Option < u32 > = args . get_kw_arg_opt ( " baseCurveIndex " ) ;
// Tolerance for the loft operation.
let tolerance : Option < f64 > = args . get_kw_arg_opt ( " tolerance " ) ;
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 ,
tolerance ,
exec_state ,
args ,
)
. await ? ;
2025-01-22 09:42:09 +13:00
Ok ( KclValue ::Solid { value } )
2024-09-04 14:34:33 -07:00
}
/// Create a 3D surface or solid by interpolating between two or more sketches.
///
/// The sketches need to closed and on the same plane.
///
/// ```no_run
/// // Loft a square and a triangle.
2024-12-12 11:33:37 -05:00
/// squareSketch = startSketchOn('XY')
2024-09-04 14:34:33 -07:00
/// |> startProfileAt([-100, 200], %)
/// |> line([200, 0], %)
/// |> line([0, -200], %)
/// |> line([-200, 0], %)
/// |> lineTo([profileStartX(%), profileStartY(%)], %)
/// |> close(%)
///
2024-12-12 11:33:37 -05:00
/// triangleSketch = startSketchOn(offsetPlane('XY', 75))
2024-09-04 14:34:33 -07:00
/// |> startProfileAt([0, 125], %)
/// |> line([-15, -30], %)
/// |> line([30, 0], %)
/// |> lineTo([profileStartX(%), profileStartY(%)], %)
/// |> close(%)
///
/// loft([squareSketch, triangleSketch])
/// ```
///
/// ```no_run
/// // Loft a square, a circle, and another circle.
2024-12-12 11:33:37 -05:00
/// squareSketch = startSketchOn('XY')
2024-09-04 14:34:33 -07:00
/// |> startProfileAt([-100, 200], %)
/// |> line([200, 0], %)
/// |> line([0, -200], %)
/// |> line([-200, 0], %)
/// |> lineTo([profileStartX(%), profileStartY(%)], %)
/// |> close(%)
///
2024-12-12 11:33:37 -05:00
/// circleSketch0 = startSketchOn(offsetPlane('XY', 75))
2024-11-25 09:21:55 +13:00
/// |> circle({ center = [0, 100], radius = 50 }, %)
2024-09-04 14:34:33 -07:00
///
2024-12-12 11:33:37 -05:00
/// circleSketch1 = startSketchOn(offsetPlane('XY', 150))
2024-11-25 09:21:55 +13:00
/// |> circle({ center = [0, 100], radius = 20 }, %)
2024-09-04 14:34:33 -07:00
///
/// loft([squareSketch, circleSketch0, circleSketch1])
/// ```
2024-09-05 16:18:03 -07:00
///
/// ```no_run
/// // Loft a square, a circle, and another circle with options.
2024-12-12 11:33:37 -05:00
/// squareSketch = startSketchOn('XY')
2024-09-05 16:18:03 -07:00
/// |> startProfileAt([-100, 200], %)
/// |> line([200, 0], %)
/// |> line([0, -200], %)
/// |> line([-200, 0], %)
/// |> lineTo([profileStartX(%), profileStartY(%)], %)
/// |> close(%)
///
2024-12-12 11:33:37 -05:00
/// circleSketch0 = startSketchOn(offsetPlane('XY', 75))
2024-11-25 09:21:55 +13:00
/// |> circle({ center = [0, 100], radius = 50 }, %)
2024-09-05 16:18:03 -07:00
///
2024-12-12 11:33:37 -05:00
/// circleSketch1 = startSketchOn(offsetPlane('XY', 150))
2024-11-25 09:21:55 +13:00
/// |> circle({ center = [0, 100], radius = 20 }, %)
2024-09-05 16:18:03 -07:00
///
2024-12-13 13:07:52 -06:00
/// loft([squareSketch, circleSketch0, circleSketch1],
2024-11-25 09:21:55 +13:00
/// baseCurveIndex = 0,
/// bezApproximateRational = false,
/// tolerance = 0.000001,
/// vDegree = 2,
2024-12-13 13:07:52 -06:00
/// )
2024-09-05 16:18:03 -07:00
/// ```
2024-09-04 14:34:33 -07:00
#[ stdlib {
name = " loft " ,
2024-12-16 13:10:31 -05:00
feature_tree_operation = true ,
2024-12-13 13:07:52 -06:00
keywords = true ,
unlabeled_first = true ,
arg_docs = {
sketches = " Which sketches to loft. Must include at least 2 sketches. " ,
v_degree = " Degree of the interpolation. Must be greater than zero. For example, use 2 for quadratic, or 3 for cubic interpolation in the V direction. This defaults to 2, if not specified. " ,
bez_approximate_rational = " 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. " ,
base_curve_index = " This can be set to override the automatically determined topological base curve, which is usually the first section encountered. " ,
tolerance = " Tolerance for the loft operation. " ,
}
2024-09-04 14:34:33 -07:00
} ]
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 > ,
tolerance : Option < f64 > ,
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 {
2024-09-04 14:34:33 -07:00
return Err ( KclError ::Semantic ( KclErrorDetails {
message : format ! (
" 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
) ,
source_ranges : vec ! [ args . source_range ] ,
} ) ) ;
}
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 ,
tolerance : LengthUnit ( tolerance . unwrap_or ( default_tolerance ( & args . ctx . settings . units ) ) ) ,
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 ;
do_post_extrude ( sketch , 0.0 , exec_state , args ) . await
2024-09-04 14:34:33 -07:00
}