* Add backtrace to errors * Add display of backtraces with hints * Change pane badge to only show count of errors * Fix property name to not collide with Error superclass * Increase min stack again * Add e2e test that checks that the diagnostics are created in CodeMirror * Remove unneeded code * Change to the new hotness
396 lines
12 KiB
Rust
396 lines
12 KiB
Rust
//! Edge helper functions.
|
|
|
|
use anyhow::Result;
|
|
use kcl_derive_docs::stdlib;
|
|
use kcmc::{each_cmd as mcmd, ok_response::OkModelingCmdResponse, websocket::OkWebSocketResponseData, ModelingCmd};
|
|
use kittycad_modeling_cmds as kcmc;
|
|
use uuid::Uuid;
|
|
|
|
use crate::{
|
|
errors::{KclError, KclErrorDetails},
|
|
execution::{types::RuntimeType, ExecState, ExtrudeSurface, KclValue, TagIdentifier},
|
|
std::Args,
|
|
};
|
|
|
|
/// Get the opposite edge to the edge given.
|
|
pub async fn get_opposite_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
|
let input_edge = args.get_unlabeled_kw_arg_typed("edge", &RuntimeType::tag_identifier(), exec_state)?;
|
|
|
|
let edge = inner_get_opposite_edge(input_edge, exec_state, args.clone()).await?;
|
|
Ok(KclValue::Uuid {
|
|
value: edge,
|
|
meta: vec![args.source_range.into()],
|
|
})
|
|
}
|
|
|
|
/// Get the opposite edge to the edge given.
|
|
///
|
|
/// ```no_run
|
|
/// exampleSketch = startSketchOn(XZ)
|
|
/// |> startProfile(at = [0, 0])
|
|
/// |> line(end = [10, 0])
|
|
/// |> angledLine(
|
|
/// angle = 60,
|
|
/// length = 10,
|
|
/// )
|
|
/// |> angledLine(
|
|
/// angle = 120,
|
|
/// length = 10,
|
|
/// )
|
|
/// |> line(end = [-10, 0])
|
|
/// |> angledLine(
|
|
/// angle = 240,
|
|
/// length = 10,
|
|
/// tag = $referenceEdge,
|
|
/// )
|
|
/// |> close()
|
|
///
|
|
/// example = extrude(exampleSketch, length = 5)
|
|
/// |> fillet(
|
|
/// radius = 3,
|
|
/// tags = [getOppositeEdge(referenceEdge)],
|
|
/// )
|
|
/// ```
|
|
#[stdlib {
|
|
name = "getOppositeEdge",
|
|
keywords = true,
|
|
unlabeled_first = true,
|
|
args = {
|
|
edge = { docs = "The tag of the edge you want to find the opposite edge of." },
|
|
},
|
|
tags = ["sketch"]
|
|
}]
|
|
async fn inner_get_opposite_edge(
|
|
edge: TagIdentifier,
|
|
exec_state: &mut ExecState,
|
|
args: Args,
|
|
) -> Result<Uuid, KclError> {
|
|
if args.ctx.no_engine_commands().await {
|
|
return Ok(exec_state.next_uuid());
|
|
}
|
|
let face_id = args.get_adjacent_face_to_tag(exec_state, &edge, false).await?;
|
|
|
|
let id = exec_state.next_uuid();
|
|
let tagged_path = args.get_tag_engine_info(exec_state, &edge)?;
|
|
|
|
let resp = args
|
|
.send_modeling_cmd(
|
|
id,
|
|
ModelingCmd::from(mcmd::Solid3dGetOppositeEdge {
|
|
edge_id: tagged_path.id,
|
|
object_id: tagged_path.sketch,
|
|
face_id,
|
|
}),
|
|
)
|
|
.await?;
|
|
let OkWebSocketResponseData::Modeling {
|
|
modeling_response: OkModelingCmdResponse::Solid3dGetOppositeEdge(opposite_edge),
|
|
} = &resp
|
|
else {
|
|
return Err(KclError::Engine(KclErrorDetails::new(
|
|
format!("mcmd::Solid3dGetOppositeEdge response was not as expected: {:?}", resp),
|
|
vec![args.source_range],
|
|
)));
|
|
};
|
|
|
|
Ok(opposite_edge.edge)
|
|
}
|
|
|
|
/// Get the next adjacent edge to the edge given.
|
|
pub async fn get_next_adjacent_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
|
let input_edge = args.get_unlabeled_kw_arg_typed("edge", &RuntimeType::tag_identifier(), exec_state)?;
|
|
|
|
let edge = inner_get_next_adjacent_edge(input_edge, exec_state, args.clone()).await?;
|
|
Ok(KclValue::Uuid {
|
|
value: edge,
|
|
meta: vec![args.source_range.into()],
|
|
})
|
|
}
|
|
|
|
/// Get the next adjacent edge to the edge given.
|
|
///
|
|
/// ```no_run
|
|
/// exampleSketch = startSketchOn(XZ)
|
|
/// |> startProfile(at = [0, 0])
|
|
/// |> line(end = [10, 0])
|
|
/// |> angledLine(
|
|
/// angle = 60,
|
|
/// length = 10,
|
|
/// )
|
|
/// |> angledLine(
|
|
/// angle = 120,
|
|
/// length = 10,
|
|
/// )
|
|
/// |> line(end = [-10, 0])
|
|
/// |> angledLine(
|
|
/// angle = 240,
|
|
/// length = 10,
|
|
/// tag = $referenceEdge,
|
|
/// )
|
|
/// |> close()
|
|
///
|
|
/// example = extrude(exampleSketch, length = 5)
|
|
/// |> fillet(
|
|
/// radius = 3,
|
|
/// tags = [getNextAdjacentEdge(referenceEdge)],
|
|
/// )
|
|
/// ```
|
|
#[stdlib {
|
|
name = "getNextAdjacentEdge",
|
|
keywords = true,
|
|
unlabeled_first = true,
|
|
args = {
|
|
edge = { docs = "The tag of the edge you want to find the next adjacent edge of." },
|
|
},
|
|
tags = ["sketch"]
|
|
}]
|
|
async fn inner_get_next_adjacent_edge(
|
|
edge: TagIdentifier,
|
|
exec_state: &mut ExecState,
|
|
args: Args,
|
|
) -> Result<Uuid, KclError> {
|
|
if args.ctx.no_engine_commands().await {
|
|
return Ok(exec_state.next_uuid());
|
|
}
|
|
let face_id = args.get_adjacent_face_to_tag(exec_state, &edge, false).await?;
|
|
|
|
let id = exec_state.next_uuid();
|
|
let tagged_path = args.get_tag_engine_info(exec_state, &edge)?;
|
|
|
|
let resp = args
|
|
.send_modeling_cmd(
|
|
id,
|
|
ModelingCmd::from(mcmd::Solid3dGetNextAdjacentEdge {
|
|
edge_id: tagged_path.id,
|
|
object_id: tagged_path.sketch,
|
|
face_id,
|
|
}),
|
|
)
|
|
.await?;
|
|
|
|
let OkWebSocketResponseData::Modeling {
|
|
modeling_response: OkModelingCmdResponse::Solid3dGetNextAdjacentEdge(adjacent_edge),
|
|
} = &resp
|
|
else {
|
|
return Err(KclError::Engine(KclErrorDetails::new(
|
|
format!(
|
|
"mcmd::Solid3dGetNextAdjacentEdge response was not as expected: {:?}",
|
|
resp
|
|
),
|
|
vec![args.source_range],
|
|
)));
|
|
};
|
|
|
|
adjacent_edge.edge.ok_or_else(|| {
|
|
KclError::Type(KclErrorDetails::new(
|
|
format!("No edge found next adjacent to tag: `{}`", edge.value),
|
|
vec![args.source_range],
|
|
))
|
|
})
|
|
}
|
|
|
|
/// Get the previous adjacent edge to the edge given.
|
|
pub async fn get_previous_adjacent_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
|
let input_edge = args.get_unlabeled_kw_arg_typed("edge", &RuntimeType::tag_identifier(), exec_state)?;
|
|
|
|
let edge = inner_get_previous_adjacent_edge(input_edge, exec_state, args.clone()).await?;
|
|
Ok(KclValue::Uuid {
|
|
value: edge,
|
|
meta: vec![args.source_range.into()],
|
|
})
|
|
}
|
|
|
|
/// Get the previous adjacent edge to the edge given.
|
|
///
|
|
/// ```no_run
|
|
/// exampleSketch = startSketchOn(XZ)
|
|
/// |> startProfile(at = [0, 0])
|
|
/// |> line(end = [10, 0])
|
|
/// |> angledLine(
|
|
/// angle = 60,
|
|
/// length = 10,
|
|
/// )
|
|
/// |> angledLine(
|
|
/// angle = 120,
|
|
/// length = 10,
|
|
/// )
|
|
/// |> line(end = [-10, 0])
|
|
/// |> angledLine(
|
|
/// angle = 240,
|
|
/// length = 10,
|
|
/// tag = $referenceEdge,
|
|
/// )
|
|
/// |> close()
|
|
///
|
|
/// example = extrude(exampleSketch, length = 5)
|
|
/// |> fillet(
|
|
/// radius = 3,
|
|
/// tags = [getPreviousAdjacentEdge(referenceEdge)],
|
|
/// )
|
|
/// ```
|
|
#[stdlib {
|
|
name = "getPreviousAdjacentEdge",
|
|
keywords = true,
|
|
unlabeled_first = true,
|
|
args = {
|
|
edge = { docs = "The tag of the edge you want to find the previous adjacent edge of." },
|
|
},
|
|
tags = ["sketch"]
|
|
}]
|
|
async fn inner_get_previous_adjacent_edge(
|
|
edge: TagIdentifier,
|
|
exec_state: &mut ExecState,
|
|
args: Args,
|
|
) -> Result<Uuid, KclError> {
|
|
if args.ctx.no_engine_commands().await {
|
|
return Ok(exec_state.next_uuid());
|
|
}
|
|
let face_id = args.get_adjacent_face_to_tag(exec_state, &edge, false).await?;
|
|
|
|
let id = exec_state.next_uuid();
|
|
let tagged_path = args.get_tag_engine_info(exec_state, &edge)?;
|
|
|
|
let resp = args
|
|
.send_modeling_cmd(
|
|
id,
|
|
ModelingCmd::from(mcmd::Solid3dGetPrevAdjacentEdge {
|
|
edge_id: tagged_path.id,
|
|
object_id: tagged_path.sketch,
|
|
face_id,
|
|
}),
|
|
)
|
|
.await?;
|
|
let OkWebSocketResponseData::Modeling {
|
|
modeling_response: OkModelingCmdResponse::Solid3dGetPrevAdjacentEdge(adjacent_edge),
|
|
} = &resp
|
|
else {
|
|
return Err(KclError::Engine(KclErrorDetails::new(
|
|
format!(
|
|
"mcmd::Solid3dGetPrevAdjacentEdge response was not as expected: {:?}",
|
|
resp
|
|
),
|
|
vec![args.source_range],
|
|
)));
|
|
};
|
|
|
|
adjacent_edge.edge.ok_or_else(|| {
|
|
KclError::Type(KclErrorDetails::new(
|
|
format!("No edge found previous adjacent to tag: `{}`", edge.value),
|
|
vec![args.source_range],
|
|
))
|
|
})
|
|
}
|
|
|
|
/// Get the shared edge between two faces.
|
|
pub async fn get_common_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
|
let faces: Vec<TagIdentifier> = args.get_kw_arg("faces")?;
|
|
|
|
let edge = inner_get_common_edge(faces, exec_state, args.clone()).await?;
|
|
Ok(KclValue::Uuid {
|
|
value: edge,
|
|
meta: vec![args.source_range.into()],
|
|
})
|
|
}
|
|
|
|
/// Get the shared edge between two faces.
|
|
///
|
|
/// ```no_run
|
|
/// // Get an edge shared between two faces, created after a chamfer.
|
|
///
|
|
/// scale = 20
|
|
/// part001 = startSketchOn(XY)
|
|
/// |> startProfile(at = [0, 0])
|
|
/// |> line(end = [0, scale])
|
|
/// |> line(end = [scale, 0])
|
|
/// |> line(end = [0, -scale])
|
|
/// |> close(tag = $line0)
|
|
/// |> extrude(length = 20, tagEnd = $end0)
|
|
/// // We tag the chamfer to reference it later.
|
|
/// |> chamfer(length = 10, tags = [getOppositeEdge(line0)], tag = $chamfer0)
|
|
///
|
|
/// // Get the shared edge between the chamfer and the extrusion.
|
|
/// commonEdge = getCommonEdge(faces = [chamfer0, end0])
|
|
///
|
|
/// // Chamfer the shared edge.
|
|
/// // TODO: uncomment this when ssi for fillets lands
|
|
/// // chamfer(part001, length = 5, tags = [commonEdge])
|
|
/// ```
|
|
#[stdlib {
|
|
name = "getCommonEdge",
|
|
feature_tree_operation = false,
|
|
keywords = true,
|
|
unlabeled_first = false,
|
|
args = {
|
|
faces = { docs = "The tags of the faces you want to find the common edge between" },
|
|
},
|
|
tags = ["sketch"]
|
|
}]
|
|
async fn inner_get_common_edge(
|
|
faces: Vec<TagIdentifier>,
|
|
exec_state: &mut ExecState,
|
|
args: Args,
|
|
) -> Result<Uuid, KclError> {
|
|
let id = exec_state.next_uuid();
|
|
if args.ctx.no_engine_commands().await {
|
|
return Ok(id);
|
|
}
|
|
|
|
if faces.len() != 2 {
|
|
return Err(KclError::Type(KclErrorDetails::new(
|
|
"getCommonEdge requires exactly two tags for faces".to_string(),
|
|
vec![args.source_range],
|
|
)));
|
|
}
|
|
let first_face_id = args.get_adjacent_face_to_tag(exec_state, &faces[0], false).await?;
|
|
let second_face_id = args.get_adjacent_face_to_tag(exec_state, &faces[1], false).await?;
|
|
|
|
let first_tagged_path = args.get_tag_engine_info(exec_state, &faces[0])?.clone();
|
|
let second_tagged_path = args.get_tag_engine_info(exec_state, &faces[1])?;
|
|
|
|
if first_tagged_path.sketch != second_tagged_path.sketch {
|
|
return Err(KclError::Type(KclErrorDetails::new(
|
|
"getCommonEdge requires the faces to be in the same original sketch".to_string(),
|
|
vec![args.source_range],
|
|
)));
|
|
}
|
|
|
|
// Flush the batch for our fillets/chamfers if there are any.
|
|
// If we have a chamfer/fillet, flush the batch.
|
|
// TODO: we likely want to be a lot more persnickety _which_ fillets we are flushing
|
|
// but for now, we'll just flush everything.
|
|
if let Some(ExtrudeSurface::Chamfer { .. } | ExtrudeSurface::Fillet { .. }) = first_tagged_path.surface {
|
|
args.ctx.engine.flush_batch(true, args.source_range).await?;
|
|
} else if let Some(ExtrudeSurface::Chamfer { .. } | ExtrudeSurface::Fillet { .. }) = second_tagged_path.surface {
|
|
args.ctx.engine.flush_batch(true, args.source_range).await?;
|
|
}
|
|
|
|
let resp = args
|
|
.send_modeling_cmd(
|
|
id,
|
|
ModelingCmd::from(mcmd::Solid3dGetCommonEdge {
|
|
object_id: first_tagged_path.sketch,
|
|
face_ids: [first_face_id, second_face_id],
|
|
}),
|
|
)
|
|
.await?;
|
|
let OkWebSocketResponseData::Modeling {
|
|
modeling_response: OkModelingCmdResponse::Solid3dGetCommonEdge(common_edge),
|
|
} = &resp
|
|
else {
|
|
return Err(KclError::Engine(KclErrorDetails::new(
|
|
format!("mcmd::Solid3dGetCommonEdge response was not as expected: {:?}", resp),
|
|
vec![args.source_range],
|
|
)));
|
|
};
|
|
|
|
common_edge.edge.ok_or_else(|| {
|
|
KclError::Type(KclErrorDetails::new(
|
|
format!(
|
|
"No common edge was found between `{}` and `{}`",
|
|
faces[0].value, faces[1].value
|
|
),
|
|
vec![args.source_range],
|
|
))
|
|
})
|
|
}
|