use anyhow::Result;
use kcmc::ModelingCmd;
use kcmc::each_cmd as mcmd;
use kcmc::ok_response::OkModelingCmdResponse;
use kcmc::websocket::OkWebSocketResponseData;
use kittycad_modeling_cmds as kcmc;
use uuid::Uuid;
use crate::SourceRange;
use crate::errors::KclError;
use crate::errors::KclErrorDetails;
use crate::execution::BoundedEdge;
use crate::execution::ExecState;
use crate::execution::ExtrudeSurface;
use crate::execution::KclValue;
use crate::execution::ModelingCmdMeta;
use crate::execution::Solid;
use crate::execution::TagIdentifier;
use crate::execution::types::ArrayLen;
use crate::execution::types::RuntimeType;
use crate::std::Args;
use crate::std::args::TyF64;
use crate::std::fillet::EdgeReference;
use crate::std::sketch::FaceTag;
pub(super) fn check_tag_not_ambiguous(tag: &TagIdentifier, args: &Args) -> Result<(), KclError> {
let all_infos = tag.get_all_cur_info();
if all_infos.len() > 1 {
return Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"Tag `{}` is ambiguous: it maps to {} edges in the region. Use a more specific reference.",
tag.value,
all_infos.len()
),
vec![args.source_range],
)));
}
Ok(())
}
pub async fn get_opposite_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let input_edge = args.get_unlabeled_kw_arg("edge", &RuntimeType::tagged_edge(), 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()],
})
}
async fn inner_get_opposite_edge(
edge: TagIdentifier,
exec_state: &mut ExecState,
args: Args,
) -> Result<Uuid, KclError> {
check_tag_not_ambiguous(&edge, &args)?;
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 tagged_path = args.get_tag_engine_info(exec_state, &edge)?;
let tagged_path_id = tagged_path.id;
let sketch_id = tagged_path.geometry.id();
let resp = exec_state
.send_modeling_cmd(
ModelingCmdMeta::from_args(exec_state, &args),
ModelingCmd::from(
mcmd::Solid3dGetOppositeEdge::builder()
.edge_id(tagged_path_id)
.object_id(sketch_id)
.face_id(face_id)
.build(),
),
)
.await?;
let OkWebSocketResponseData::Modeling {
modeling_response: OkModelingCmdResponse::Solid3dGetOppositeEdge(opposite_edge),
} = &resp
else {
return Err(KclError::new_engine(KclErrorDetails::new(
format!("mcmd::Solid3dGetOppositeEdge response was not as expected: {resp:?}"),
vec![args.source_range],
)));
};
Ok(opposite_edge.edge)
}
pub async fn get_next_adjacent_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let input_edge = args.get_unlabeled_kw_arg("edge", &RuntimeType::tagged_edge(), 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()],
})
}
async fn inner_get_next_adjacent_edge(
edge: TagIdentifier,
exec_state: &mut ExecState,
args: Args,
) -> Result<Uuid, KclError> {
check_tag_not_ambiguous(&edge, &args)?;
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 tagged_path = args.get_tag_engine_info(exec_state, &edge)?;
let tagged_path_id = tagged_path.id;
let sketch_id = tagged_path.geometry.id();
let resp = exec_state
.send_modeling_cmd(
ModelingCmdMeta::from_args(exec_state, &args),
ModelingCmd::from(
mcmd::Solid3dGetNextAdjacentEdge::builder()
.edge_id(tagged_path_id)
.object_id(sketch_id)
.face_id(face_id)
.build(),
),
)
.await?;
let OkWebSocketResponseData::Modeling {
modeling_response: OkModelingCmdResponse::Solid3dGetNextAdjacentEdge(adjacent_edge),
} = &resp
else {
return Err(KclError::new_engine(KclErrorDetails::new(
format!("mcmd::Solid3dGetNextAdjacentEdge response was not as expected: {resp:?}"),
vec![args.source_range],
)));
};
adjacent_edge.edge.ok_or_else(|| {
KclError::new_type(KclErrorDetails::new(
format!("No edge found next adjacent to tag: `{}`", edge.value),
vec![args.source_range],
))
})
}
pub async fn get_previous_adjacent_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let input_edge = args.get_unlabeled_kw_arg("edge", &RuntimeType::tagged_edge(), 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()],
})
}
async fn inner_get_previous_adjacent_edge(
edge: TagIdentifier,
exec_state: &mut ExecState,
args: Args,
) -> Result<Uuid, KclError> {
check_tag_not_ambiguous(&edge, &args)?;
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 tagged_path = args.get_tag_engine_info(exec_state, &edge)?;
let tagged_path_id = tagged_path.id;
let sketch_id = tagged_path.geometry.id();
let resp = exec_state
.send_modeling_cmd(
ModelingCmdMeta::from_args(exec_state, &args),
ModelingCmd::from(
mcmd::Solid3dGetPrevAdjacentEdge::builder()
.edge_id(tagged_path_id)
.object_id(sketch_id)
.face_id(face_id)
.build(),
),
)
.await?;
let OkWebSocketResponseData::Modeling {
modeling_response: OkModelingCmdResponse::Solid3dGetPrevAdjacentEdge(adjacent_edge),
} = &resp
else {
return Err(KclError::new_engine(KclErrorDetails::new(
format!("mcmd::Solid3dGetPrevAdjacentEdge response was not as expected: {resp:?}"),
vec![args.source_range],
)));
};
adjacent_edge.edge.ok_or_else(|| {
KclError::new_type(KclErrorDetails::new(
format!("No edge found previous adjacent to tag: `{}`", edge.value),
vec![args.source_range],
))
})
}
pub async fn get_common_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let faces: Vec<FaceTag> = args.get_kw_arg(
"faces",
&RuntimeType::Array(Box::new(RuntimeType::tagged_face()), ArrayLen::Known(2)),
exec_state,
)?;
fn into_tag(face: FaceTag, source_range: SourceRange) -> Result<TagIdentifier, KclError> {
match face {
FaceTag::StartOrEnd(_) => Err(KclError::new_type(KclErrorDetails::new(
"getCommonEdge requires a tagged face, it cannot use `START` or `END` faces".to_owned(),
vec![source_range],
))),
FaceTag::Tag(tag_identifier) => Ok(*tag_identifier),
}
}
let [face1, face2]: [FaceTag; 2] = faces.try_into().map_err(|_: Vec<FaceTag>| {
KclError::new_type(KclErrorDetails::new(
"getCommonEdge requires exactly two tags for faces".to_owned(),
vec![args.source_range],
))
})?;
let face1 = into_tag(face1, args.source_range)?;
let face2 = into_tag(face2, args.source_range)?;
let edge = inner_get_common_edge(face1, face2, exec_state, args.clone()).await?;
Ok(KclValue::Uuid {
value: edge,
meta: vec![args.source_range.into()],
})
}
async fn inner_get_common_edge(
face1: TagIdentifier,
face2: TagIdentifier,
exec_state: &mut ExecState,
args: Args,
) -> Result<Uuid, KclError> {
check_tag_not_ambiguous(&face1, &args)?;
check_tag_not_ambiguous(&face2, &args)?;
let id = exec_state.next_uuid();
if args.ctx.no_engine_commands().await {
return Ok(id);
}
let first_face_id = args.get_adjacent_face_to_tag(exec_state, &face1, false).await?;
let second_face_id = args.get_adjacent_face_to_tag(exec_state, &face2, false).await?;
let first_tagged_path = args.get_tag_engine_info(exec_state, &face1)?.clone();
let second_tagged_path = args.get_tag_engine_info(exec_state, &face2)?;
if first_tagged_path.geometry.id() != second_tagged_path.geometry.id() {
return Err(KclError::new_type(KclErrorDetails::new(
"getCommonEdge requires the faces to be in the same original sketch".to_string(),
vec![args.source_range],
)));
}
if let Some(ExtrudeSurface::Chamfer { .. } | ExtrudeSurface::Fillet { .. }) = first_tagged_path.surface {
exec_state
.flush_batch(ModelingCmdMeta::from_args(exec_state, &args), true)
.await?;
} else if let Some(ExtrudeSurface::Chamfer { .. } | ExtrudeSurface::Fillet { .. }) = second_tagged_path.surface {
exec_state
.flush_batch(ModelingCmdMeta::from_args(exec_state, &args), true)
.await?;
}
let resp = exec_state
.send_modeling_cmd(
ModelingCmdMeta::from_args_id(exec_state, &args, id),
ModelingCmd::from(
mcmd::Solid3dGetCommonEdge::builder()
.object_id(first_tagged_path.geometry.id())
.face_ids([first_face_id, second_face_id])
.build(),
),
)
.await?;
let OkWebSocketResponseData::Modeling {
modeling_response: OkModelingCmdResponse::Solid3dGetCommonEdge(common_edge),
} = &resp
else {
return Err(KclError::new_engine(KclErrorDetails::new(
format!("mcmd::Solid3dGetCommonEdge response was not as expected: {resp:?}"),
vec![args.source_range],
)));
};
common_edge.edge.ok_or_else(|| {
KclError::new_type(KclErrorDetails::new(
format!(
"No common edge was found between `{}` and `{}`",
face1.value, face2.value
),
vec![args.source_range],
))
})
}
pub async fn get_bounded_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let face = args.get_unlabeled_kw_arg("solid", &RuntimeType::solid(), exec_state)?;
let edge = args.get_kw_arg("edge", &RuntimeType::edge(), exec_state)?;
let lower_bound = args.get_kw_arg_opt("lowerBound", &RuntimeType::num_any(), exec_state)?;
let upper_bound = args.get_kw_arg_opt("upperBound", &RuntimeType::num_any(), exec_state)?;
let bounded_edge = inner_get_bounded_edge(face, edge, lower_bound, upper_bound, exec_state, args.clone()).await?;
Ok(KclValue::BoundedEdge {
value: bounded_edge,
meta: vec![args.source_range.into()],
})
}
pub async fn inner_get_bounded_edge(
face: Solid,
edge: EdgeReference,
lower_bound: Option<TyF64>,
upper_bound: Option<TyF64>,
exec_state: &mut ExecState,
args: Args,
) -> Result<BoundedEdge, KclError> {
let lower_bound = if let Some(lower_bound) = lower_bound {
let val = lower_bound.n as f32;
if !(0.0..=1.0).contains(&val) {
return Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"Invalid value: lowerBound must be between 0.0 and 1.0, provided {}",
val
),
vec![args.source_range],
)));
}
val
} else {
0.0_f32
};
let upper_bound = if let Some(upper_bound) = upper_bound {
let val = upper_bound.n as f32;
if !(0.0..=1.0).contains(&val) {
return Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"Invalid value: upperBound must be between 0.0 and 1.0, provided {}",
val
),
vec![args.source_range],
)));
}
val
} else {
1.0_f32
};
Ok(BoundedEdge {
face_id: face.id,
edge_id: edge.get_engine_id(exec_state, &args)?,
lower_bound,
upper_bound,
})
}