Nadro/adhoc/stdlib arcTo (three point arc) (#4485)
* feat: implementing arcTo in standard library, first pass * feat: computing center and radius for arcto * fix: updating comment for arcTo * fix: cargo fmt fix * fix: bug, the x was used twice! * fix: Cleaning up some code and adding more comments * fix: this has to be removed * fix: resolved merge conflicts with main and updated the codebase to remove the JSON stuff * fix: addressing cargo clippy issues * fix: typos * fix: adding generated docs * Update doc test snapshots --------- Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
This commit is contained in:
@ -1486,6 +1486,17 @@ pub enum ArcData {
|
||||
},
|
||||
}
|
||||
|
||||
/// Data to draw a three point arc (arcTo).
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ArcToData {
|
||||
/// End point of the arc. A point in 3D space
|
||||
pub end: [f64; 2],
|
||||
/// Interior point of the arc. A point in 3D space
|
||||
pub interior: [f64; 2],
|
||||
}
|
||||
|
||||
/// Draw an arc.
|
||||
pub async fn arc(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let (data, sketch, tag): (ArcData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?;
|
||||
@ -1516,7 +1527,7 @@ pub async fn arc(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
|
||||
/// radius: 16
|
||||
/// }, %)
|
||||
/// |> close(%)
|
||||
// const example = extrude(10, exampleSketch)
|
||||
/// const example = extrude(10, exampleSketch)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "arc",
|
||||
@ -1595,6 +1606,104 @@ pub(crate) async fn inner_arc(
|
||||
Ok(new_sketch)
|
||||
}
|
||||
|
||||
/// Draw a three point arc.
|
||||
pub async fn arc_to(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let (data, sketch, tag): (ArcToData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?;
|
||||
|
||||
let new_sketch = inner_arc_to(data, sketch, tag, exec_state, args).await?;
|
||||
Ok(KclValue::Sketch {
|
||||
value: Box::new(new_sketch),
|
||||
})
|
||||
}
|
||||
|
||||
/// Draw a 3 point arc.
|
||||
///
|
||||
/// The arc is constructed such that the start point is the current position of the sketch and two more points defined as the end and interior point.
|
||||
/// The interior point is placed between the start point and end point. The radius of the arc will be controlled by how far the interior point is placed from
|
||||
/// the start and end.
|
||||
///
|
||||
/// ```no_run
|
||||
/// const exampleSketch = startSketchOn('XZ')
|
||||
/// |> startProfileAt([0, 0], %)
|
||||
/// |> arcTo({
|
||||
/// end: [10,0],
|
||||
/// interior: [5,5]
|
||||
/// }, %)
|
||||
/// |> close(%)
|
||||
/// const example = extrude(10, exampleSketch)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "arcTo",
|
||||
}]
|
||||
pub(crate) async fn inner_arc_to(
|
||||
data: ArcToData,
|
||||
sketch: Sketch,
|
||||
tag: Option<TagNode>,
|
||||
exec_state: &mut ExecState,
|
||||
args: Args,
|
||||
) -> Result<Sketch, KclError> {
|
||||
let from: Point2d = sketch.current_pen_position()?;
|
||||
let id = exec_state.id_generator.next_uuid();
|
||||
|
||||
// The start point is taken from the path you are extending.
|
||||
args.batch_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::from(mcmd::ExtendPath {
|
||||
path: sketch.id.into(),
|
||||
segment: PathSegment::ArcTo {
|
||||
end: kcmc::shared::Point3d {
|
||||
x: LengthUnit(data.end[0]),
|
||||
y: LengthUnit(data.end[1]),
|
||||
z: LengthUnit(0.0),
|
||||
},
|
||||
interior: kcmc::shared::Point3d {
|
||||
x: LengthUnit(data.interior[0]),
|
||||
y: LengthUnit(data.interior[1]),
|
||||
z: LengthUnit(0.0),
|
||||
},
|
||||
relative: false,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let start = [from.x, from.y];
|
||||
let interior = [data.interior[0], data.interior[1]];
|
||||
let end = [data.end[0], data.end[1]];
|
||||
|
||||
// compute the center of the circle since we do not have the value returned from the engine
|
||||
let center = calculate_circle_center(start, interior, end);
|
||||
|
||||
// compute the radius since we do not have the value returned from the engine
|
||||
// Pick any of the 3 points since they all lie along the circle
|
||||
let sum_of_square_differences =
|
||||
(center[0] - start[0] * center[0] - start[0]) + (center[1] - start[1] * center[1] - start[1]);
|
||||
let radius = sum_of_square_differences.sqrt();
|
||||
|
||||
let current_path = Path::Arc {
|
||||
base: BasePath {
|
||||
from: from.into(),
|
||||
to: data.end,
|
||||
tag: tag.clone(),
|
||||
geo_meta: GeoMeta {
|
||||
id,
|
||||
metadata: args.source_range.into(),
|
||||
},
|
||||
},
|
||||
center,
|
||||
radius,
|
||||
};
|
||||
|
||||
let mut new_sketch = sketch.clone();
|
||||
if let Some(tag) = &tag {
|
||||
new_sketch.add_tag(tag, ¤t_path);
|
||||
}
|
||||
|
||||
new_sketch.paths.push(current_path);
|
||||
|
||||
Ok(new_sketch)
|
||||
}
|
||||
|
||||
/// Data to draw a tangential arc.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
@ -1914,6 +2023,42 @@ async fn inner_tangential_arc_to_relative(
|
||||
Ok(new_sketch)
|
||||
}
|
||||
|
||||
// Calculate the center of 3 points
|
||||
// To calculate the center of the 3 point circle 2 perpendicular lines are created
|
||||
// These perpendicular lines will intersect at the center of the circle.
|
||||
fn calculate_circle_center(p1: [f64; 2], p2: [f64; 2], p3: [f64; 2]) -> [f64; 2] {
|
||||
// y2 - y1
|
||||
let y_2_1 = p2[1] - p1[1];
|
||||
// y3 - y2
|
||||
let y_3_2 = p3[1] - p2[1];
|
||||
// x2 - x1
|
||||
let x_2_1 = p2[0] - p1[0];
|
||||
// x3 - x2
|
||||
let x_3_2 = p3[0] - p2[0];
|
||||
|
||||
// Slope of two perpendicular lines
|
||||
let slope_a = y_2_1 / x_2_1;
|
||||
let slope_b = y_3_2 / x_3_2;
|
||||
|
||||
// Values for line intersection
|
||||
// y1 - y3
|
||||
let y_1_3 = p1[1] - p3[1];
|
||||
// x1 + x2
|
||||
let x_1_2 = p1[0] + p2[0];
|
||||
// x2 + x3
|
||||
let x_2_3 = p2[0] + p3[0];
|
||||
// y1 + y2
|
||||
let y_1_2 = p1[1] + p2[1];
|
||||
|
||||
// Solve for the intersection of these two lines
|
||||
let numerator = (slope_a * slope_b * y_1_3) + (slope_b * x_1_2) - (slope_a * x_2_3);
|
||||
let x = numerator / (2.0 * (slope_b - slope_a));
|
||||
|
||||
let y = ((-1.0 / slope_a) * (x - (x_1_2 / 2.0))) + (y_1_2 / 2.0);
|
||||
|
||||
[x, y]
|
||||
}
|
||||
|
||||
/// Data to draw a bezier curve.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
@ -2090,7 +2235,7 @@ mod tests {
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::{executor::TagIdentifier, std::sketch::PlaneData};
|
||||
use crate::{executor::TagIdentifier, std::sketch::calculate_circle_center, std::sketch::PlaneData};
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_plane_data() {
|
||||
@ -2161,4 +2306,11 @@ mod tests {
|
||||
crate::std::sketch::FaceTag::StartOrEnd(crate::std::sketch::StartOrEnd::Start)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_circle_center() {
|
||||
let actual = calculate_circle_center([0.0, 0.0], [5.0, 5.0], [10.0, 0.0]);
|
||||
assert_eq!(actual[0], 5.0);
|
||||
assert_eq!(actual[1], 0.0);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user