2024-09-25 16:12:18 -07:00
|
|
|
//! Standard library mirror.
|
|
|
|
|
|
|
|
use anyhow::Result;
|
|
|
|
use kcmc::{each_cmd as mcmd, ModelingCmd};
|
2025-04-04 12:55:21 -07:00
|
|
|
use kittycad_modeling_cmds::{
|
|
|
|
self as kcmc, length_unit::LengthUnit, ok_response::OkModelingCmdResponse, output::EntityGetAllChildUuids,
|
|
|
|
shared::Point3d, websocket::OkWebSocketResponseData,
|
|
|
|
};
|
2024-09-25 16:12:18 -07:00
|
|
|
|
|
|
|
use crate::{
|
2025-04-04 12:55:21 -07:00
|
|
|
errors::{KclError, KclErrorDetails},
|
2025-04-03 22:44:52 +13:00
|
|
|
execution::{
|
|
|
|
types::{PrimitiveType, RuntimeType},
|
|
|
|
ExecState, KclValue, Sketch,
|
|
|
|
},
|
2025-01-07 19:10:53 -08:00
|
|
|
std::{axis_or_reference::Axis2dOrEdgeReference, Args},
|
2024-09-25 16:12:18 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
/// Mirror a sketch.
|
|
|
|
pub async fn mirror_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
2025-06-06 10:45:58 +12:00
|
|
|
let sketches = args.get_unlabeled_kw_arg("sketches", &RuntimeType::sketches(), exec_state)?;
|
|
|
|
let axis = args.get_kw_arg(
|
2025-04-03 22:44:52 +13:00
|
|
|
"axis",
|
|
|
|
&RuntimeType::Union(vec![
|
|
|
|
RuntimeType::Primitive(PrimitiveType::Edge),
|
|
|
|
RuntimeType::Primitive(PrimitiveType::Axis2d),
|
|
|
|
]),
|
|
|
|
exec_state,
|
|
|
|
)?;
|
2024-09-25 16:12:18 -07:00
|
|
|
|
2025-04-03 22:44:52 +13:00
|
|
|
let sketches = inner_mirror_2d(sketches, axis, exec_state, args).await?;
|
2024-09-27 15:44:44 -07:00
|
|
|
Ok(sketches.into())
|
2024-09-25 16:12:18 -07:00
|
|
|
}
|
|
|
|
|
2025-04-04 12:55:21 -07:00
|
|
|
/// Mirror a sketch.
|
2024-09-25 16:12:18 -07:00
|
|
|
async fn inner_mirror_2d(
|
2025-03-17 17:57:26 +13:00
|
|
|
sketches: Vec<Sketch>,
|
2025-04-03 22:44:52 +13:00
|
|
|
axis: Axis2dOrEdgeReference,
|
2024-09-25 16:12:18 -07:00
|
|
|
exec_state: &mut ExecState,
|
|
|
|
args: Args,
|
2025-03-17 17:57:26 +13:00
|
|
|
) -> Result<Vec<Sketch>, KclError> {
|
2025-04-04 12:55:21 -07:00
|
|
|
let mut starting_sketches = sketches.clone();
|
|
|
|
|
|
|
|
// Update all to have a mirror.
|
|
|
|
starting_sketches.iter_mut().for_each(|sketch| {
|
|
|
|
sketch.mirror = Some(exec_state.next_uuid());
|
|
|
|
});
|
2024-09-25 16:12:18 -07:00
|
|
|
|
2025-02-18 13:50:13 -08:00
|
|
|
if args.ctx.no_engine_commands().await {
|
2024-09-27 15:44:44 -07:00
|
|
|
return Ok(starting_sketches);
|
2024-09-25 16:12:18 -07:00
|
|
|
}
|
|
|
|
|
2025-04-03 22:44:52 +13:00
|
|
|
match axis {
|
|
|
|
Axis2dOrEdgeReference::Axis { direction, origin } => {
|
2025-06-10 21:30:48 -04:00
|
|
|
exec_state
|
|
|
|
.batch_modeling_cmd(
|
|
|
|
(&args).into(),
|
|
|
|
ModelingCmd::from(mcmd::EntityMirror {
|
|
|
|
ids: starting_sketches.iter().map(|sketch| sketch.id).collect(),
|
|
|
|
axis: Point3d {
|
|
|
|
x: direction[0].to_mm(),
|
|
|
|
y: direction[1].to_mm(),
|
|
|
|
z: 0.0,
|
|
|
|
},
|
|
|
|
point: Point3d {
|
|
|
|
x: LengthUnit(origin[0].to_mm()),
|
|
|
|
y: LengthUnit(origin[1].to_mm()),
|
|
|
|
z: LengthUnit(0.0),
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
)
|
|
|
|
.await?;
|
2024-09-25 16:12:18 -07:00
|
|
|
}
|
2025-01-07 19:10:53 -08:00
|
|
|
Axis2dOrEdgeReference::Edge(edge) => {
|
2024-09-25 16:12:18 -07:00
|
|
|
let edge_id = edge.get_engine_id(exec_state, &args)?;
|
|
|
|
|
2025-06-10 21:30:48 -04:00
|
|
|
exec_state
|
|
|
|
.batch_modeling_cmd(
|
|
|
|
(&args).into(),
|
|
|
|
ModelingCmd::from(mcmd::EntityMirrorAcrossEdge {
|
|
|
|
ids: starting_sketches.iter().map(|sketch| sketch.id).collect(),
|
|
|
|
edge_id,
|
|
|
|
}),
|
|
|
|
)
|
|
|
|
.await?;
|
2024-09-25 16:12:18 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2025-04-04 12:55:21 -07:00
|
|
|
// After the mirror, get the first child uuid for the path.
|
|
|
|
// The "get extrusion face info" API call requires *any* edge on the sketch being extruded.
|
|
|
|
// But if you mirror2d a sketch these IDs might change so we need to get the children versus
|
|
|
|
// using the IDs we already have.
|
|
|
|
// We only do this with mirrors because otherwise it is a waste of a websocket call.
|
|
|
|
for sketch in &mut starting_sketches {
|
2025-06-10 21:30:48 -04:00
|
|
|
let response = exec_state
|
2025-04-04 12:55:21 -07:00
|
|
|
.send_modeling_cmd(
|
2025-06-10 21:30:48 -04:00
|
|
|
(&args).into(),
|
2025-04-04 12:55:21 -07:00
|
|
|
ModelingCmd::from(mcmd::EntityGetAllChildUuids { entity_id: sketch.id }),
|
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
let OkWebSocketResponseData::Modeling {
|
|
|
|
modeling_response:
|
|
|
|
OkModelingCmdResponse::EntityGetAllChildUuids(EntityGetAllChildUuids { entity_ids: child_ids }),
|
|
|
|
} = response
|
|
|
|
else {
|
2025-06-02 13:30:57 -05:00
|
|
|
return Err(KclError::new_internal(KclErrorDetails::new(
|
2025-05-19 14:13:10 -04:00
|
|
|
"Expected a successful response from EntityGetAllChildUuids".to_string(),
|
|
|
|
vec![args.source_range],
|
|
|
|
)));
|
2025-04-04 12:55:21 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
if child_ids.len() >= 2 {
|
|
|
|
// The first child is the original sketch, the second is the mirrored sketch.
|
|
|
|
let child_id = child_ids[1];
|
|
|
|
sketch.mirror = Some(child_id);
|
|
|
|
} else {
|
2025-06-02 13:30:57 -05:00
|
|
|
return Err(KclError::new_type(KclErrorDetails::new(
|
2025-05-19 14:13:10 -04:00
|
|
|
"Expected child uuids to be >= 2".to_string(),
|
|
|
|
vec![args.source_range],
|
|
|
|
)));
|
2025-04-04 12:55:21 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-27 15:44:44 -07:00
|
|
|
Ok(starting_sketches)
|
2024-09-25 16:12:18 -07:00
|
|
|
}
|