use anyhow::Result;
use ezpz::Constraint as SolverConstraint;
use ezpz::datatypes::AngleKind;
use ezpz::datatypes::inputs::DatumCircle;
use ezpz::datatypes::inputs::DatumCircularArc;
use ezpz::datatypes::inputs::DatumDistance;
use ezpz::datatypes::inputs::DatumLineSegment;
use ezpz::datatypes::inputs::DatumPoint;
use kittycad_modeling_cmds as kcmc;
use crate::errors::KclError;
use crate::errors::KclErrorDetails;
use crate::execution::AbstractSegment;
#[cfg(feature = "artifact-graph")]
use crate::execution::Artifact;
#[cfg(feature = "artifact-graph")]
use crate::execution::CodeRef;
use crate::execution::ConstrainablePoint2d;
use crate::execution::ConstrainablePoint2dOrOrigin;
use crate::execution::ExecState;
use crate::execution::KclValue;
use crate::execution::SegmentRepr;
#[cfg(feature = "artifact-graph")]
use crate::execution::SketchBlockConstraint;
#[cfg(feature = "artifact-graph")]
use crate::execution::SketchBlockConstraintType;
use crate::execution::SketchConstraint;
use crate::execution::SketchConstraintKind;
use crate::execution::SketchVarId;
use crate::execution::UnsolvedExpr;
use crate::execution::UnsolvedSegment;
use crate::execution::UnsolvedSegmentKind;
use crate::execution::normalize_to_solver_distance_unit;
use crate::execution::solver_numeric_type;
use crate::execution::types::ArrayLen;
use crate::execution::types::PrimitiveType;
use crate::execution::types::RuntimeType;
use crate::front::ArcCtor;
use crate::front::CircleCtor;
#[cfg(feature = "artifact-graph")]
use crate::front::Coincident;
#[cfg(feature = "artifact-graph")]
use crate::front::Constraint;
#[cfg(feature = "artifact-graph")]
use crate::front::EqualRadius;
#[cfg(feature = "artifact-graph")]
use crate::front::Horizontal;
use crate::front::LineCtor;
#[cfg(feature = "artifact-graph")]
use crate::front::LinesEqualLength;
#[cfg(feature = "artifact-graph")]
use crate::front::Object;
use crate::front::ObjectId;
#[cfg(feature = "artifact-graph")]
use crate::front::ObjectKind;
#[cfg(feature = "artifact-graph")]
use crate::front::Parallel;
#[cfg(feature = "artifact-graph")]
use crate::front::Perpendicular;
use crate::front::Point2d;
use crate::front::PointCtor;
#[cfg(feature = "artifact-graph")]
use crate::front::SourceRef;
#[cfg(feature = "artifact-graph")]
use crate::front::Tangent;
#[cfg(feature = "artifact-graph")]
use crate::front::Vertical;
#[cfg(feature = "artifact-graph")]
use crate::frontend::sketch::ConstraintSegment;
use crate::std::Args;
use crate::std::args::FromKclValue;
use crate::std::args::TyF64;
fn point2d_is_origin(point2d: &KclValue) -> bool {
let Some([x, y]) = <[TyF64; 2]>::from_kcl_val(point2d) else {
return false;
};
if x.ty.as_length().is_none() || y.ty.as_length().is_none() {
return false;
}
x.n == 0.0 && y.n == 0.0
}
#[cfg(feature = "artifact-graph")]
fn coincident_segments_for_segment_and_point2d(
segment_id: ObjectId,
point2d: &KclValue,
segment_first: bool,
) -> Vec<ConstraintSegment> {
if !point2d_is_origin(point2d) {
return vec![segment_id.into()];
}
if segment_first {
vec![segment_id.into(), ConstraintSegment::ORIGIN]
} else {
vec![ConstraintSegment::ORIGIN, segment_id.into()]
}
}
pub async fn point(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let at: Vec<KclValue> = args.get_kw_arg("at", &RuntimeType::point2d(), exec_state)?;
let [at_x_value, at_y_value]: [KclValue; 2] = at.try_into().map_err(|_| {
KclError::new_semantic(KclErrorDetails::new(
"at must be a 2D point".to_owned(),
vec![args.source_range],
))
})?;
let Some(at_x) = at_x_value.as_unsolved_expr() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"at x must be a number or sketch var".to_owned(),
vec![args.source_range],
)));
};
let Some(at_y) = at_y_value.as_unsolved_expr() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"at y must be a number or sketch var".to_owned(),
vec![args.source_range],
)));
};
let ctor = PointCtor {
position: Point2d {
x: at_x_value.to_sketch_expr().ok_or_else(|| {
KclError::new_semantic(KclErrorDetails::new(
"unable to convert numeric type to suffix".to_owned(),
vec![args.source_range],
))
})?,
y: at_y_value.to_sketch_expr().ok_or_else(|| {
KclError::new_semantic(KclErrorDetails::new(
"unable to convert numeric type to suffix".to_owned(),
vec![args.source_range],
))
})?,
},
};
let segment = UnsolvedSegment {
id: exec_state.next_uuid(),
object_id: exec_state.next_object_id(),
kind: UnsolvedSegmentKind::Point {
position: [at_x, at_y],
ctor: Box::new(ctor),
},
tag: None,
node_path: args.node_path.clone(),
meta: vec![args.source_range.into()],
};
#[cfg(feature = "artifact-graph")]
let optional_constraints = {
let object_id = exec_state.add_placeholder_scene_object(segment.object_id, args.source_range, args.node_path);
let mut optional_constraints = Vec::new();
if exec_state.segment_ids_edited_contains(&object_id) {
if let Some(at_x_var) = at_x_value.as_sketch_var() {
let x_initial_value = at_x_var.initial_value_to_solver_units(
exec_state,
args.source_range,
"edited segment fixed constraint value",
)?;
optional_constraints.push(SolverConstraint::Fixed(
at_x_var.id.to_constraint_id(args.source_range)?,
x_initial_value.n,
));
}
if let Some(at_y_var) = at_y_value.as_sketch_var() {
let y_initial_value = at_y_var.initial_value_to_solver_units(
exec_state,
args.source_range,
"edited segment fixed constraint value",
)?;
optional_constraints.push(SolverConstraint::Fixed(
at_y_var.id.to_constraint_id(args.source_range)?,
y_initial_value.n,
));
}
}
optional_constraints
};
let Some(sketch_state) = exec_state.sketch_block_mut() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line() can only be used inside a sketch block".to_owned(),
vec![args.source_range],
)));
};
sketch_state.needed_by_engine.push(segment.clone());
#[cfg(feature = "artifact-graph")]
sketch_state.solver_optional_constraints.extend(optional_constraints);
let meta = segment.meta.clone();
let abstract_segment = AbstractSegment {
repr: SegmentRepr::Unsolved {
segment: Box::new(segment),
},
meta,
};
Ok(KclValue::Segment {
value: Box::new(abstract_segment),
})
}
pub async fn line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let start: Vec<KclValue> = args.get_kw_arg("start", &RuntimeType::point2d(), exec_state)?;
let end: Vec<KclValue> = args.get_kw_arg("end", &RuntimeType::point2d(), exec_state)?;
let construction_opt = args.get_kw_arg_opt("construction", &RuntimeType::bool(), exec_state)?;
let construction: bool = construction_opt.unwrap_or(false);
let construction_ctor = construction_opt;
let [start_x_value, start_y_value]: [KclValue; 2] = start.try_into().map_err(|_| {
KclError::new_semantic(KclErrorDetails::new(
"start must be a 2D point".to_owned(),
vec![args.source_range],
))
})?;
let [end_x_value, end_y_value]: [KclValue; 2] = end.try_into().map_err(|_| {
KclError::new_semantic(KclErrorDetails::new(
"end must be a 2D point".to_owned(),
vec![args.source_range],
))
})?;
let Some(start_x) = start_x_value.as_unsolved_expr() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"start x must be a number or sketch var".to_owned(),
vec![args.source_range],
)));
};
let Some(start_y) = start_y_value.as_unsolved_expr() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"start y must be a number or sketch var".to_owned(),
vec![args.source_range],
)));
};
let Some(end_x) = end_x_value.as_unsolved_expr() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"end x must be a number or sketch var".to_owned(),
vec![args.source_range],
)));
};
let Some(end_y) = end_y_value.as_unsolved_expr() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"end y must be a number or sketch var".to_owned(),
vec![args.source_range],
)));
};
let ctor = LineCtor {
start: Point2d {
x: start_x_value.to_sketch_expr().ok_or_else(|| {
KclError::new_semantic(KclErrorDetails::new(
"unable to convert numeric type to suffix".to_owned(),
vec![args.source_range],
))
})?,
y: start_y_value.to_sketch_expr().ok_or_else(|| {
KclError::new_semantic(KclErrorDetails::new(
"unable to convert numeric type to suffix".to_owned(),
vec![args.source_range],
))
})?,
},
end: Point2d {
x: end_x_value.to_sketch_expr().ok_or_else(|| {
KclError::new_semantic(KclErrorDetails::new(
"unable to convert numeric type to suffix".to_owned(),
vec![args.source_range],
))
})?,
y: end_y_value.to_sketch_expr().ok_or_else(|| {
KclError::new_semantic(KclErrorDetails::new(
"unable to convert numeric type to suffix".to_owned(),
vec![args.source_range],
))
})?,
},
construction: construction_ctor,
};
let start_object_id = exec_state.next_object_id();
let end_object_id = exec_state.next_object_id();
let line_object_id = exec_state.next_object_id();
let segment = UnsolvedSegment {
id: exec_state.next_uuid(),
object_id: line_object_id,
kind: UnsolvedSegmentKind::Line {
start: [start_x, start_y],
end: [end_x, end_y],
ctor: Box::new(ctor),
start_object_id,
end_object_id,
construction,
},
tag: None,
node_path: args.node_path.clone(),
meta: vec![args.source_range.into()],
};
#[cfg(feature = "artifact-graph")]
let optional_constraints = {
let start_object_id =
exec_state.add_placeholder_scene_object(start_object_id, args.source_range, args.node_path.clone());
let end_object_id =
exec_state.add_placeholder_scene_object(end_object_id, args.source_range, args.node_path.clone());
let line_object_id =
exec_state.add_placeholder_scene_object(line_object_id, args.source_range, args.node_path.clone());
let mut optional_constraints = Vec::new();
if exec_state.segment_ids_edited_contains(&start_object_id)
|| exec_state.segment_ids_edited_contains(&line_object_id)
{
if let Some(start_x_var) = start_x_value.as_sketch_var() {
let x_initial_value = start_x_var.initial_value_to_solver_units(
exec_state,
args.source_range,
"edited segment fixed constraint value",
)?;
optional_constraints.push(SolverConstraint::Fixed(
start_x_var.id.to_constraint_id(args.source_range)?,
x_initial_value.n,
));
}
if let Some(start_y_var) = start_y_value.as_sketch_var() {
let y_initial_value = start_y_var.initial_value_to_solver_units(
exec_state,
args.source_range,
"edited segment fixed constraint value",
)?;
optional_constraints.push(SolverConstraint::Fixed(
start_y_var.id.to_constraint_id(args.source_range)?,
y_initial_value.n,
));
}
}
if exec_state.segment_ids_edited_contains(&end_object_id)
|| exec_state.segment_ids_edited_contains(&line_object_id)
{
if let Some(end_x_var) = end_x_value.as_sketch_var() {
let x_initial_value = end_x_var.initial_value_to_solver_units(
exec_state,
args.source_range,
"edited segment fixed constraint value",
)?;
optional_constraints.push(SolverConstraint::Fixed(
end_x_var.id.to_constraint_id(args.source_range)?,
x_initial_value.n,
));
}
if let Some(end_y_var) = end_y_value.as_sketch_var() {
let y_initial_value = end_y_var.initial_value_to_solver_units(
exec_state,
args.source_range,
"edited segment fixed constraint value",
)?;
optional_constraints.push(SolverConstraint::Fixed(
end_y_var.id.to_constraint_id(args.source_range)?,
y_initial_value.n,
));
}
}
optional_constraints
};
let Some(sketch_state) = exec_state.sketch_block_mut() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line() can only be used inside a sketch block".to_owned(),
vec![args.source_range],
)));
};
sketch_state.needed_by_engine.push(segment.clone());
#[cfg(feature = "artifact-graph")]
sketch_state.solver_optional_constraints.extend(optional_constraints);
let meta = segment.meta.clone();
let abstract_segment = AbstractSegment {
repr: SegmentRepr::Unsolved {
segment: Box::new(segment),
},
meta,
};
Ok(KclValue::Segment {
value: Box::new(abstract_segment),
})
}
pub async fn arc(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let start: Vec<KclValue> = args.get_kw_arg("start", &RuntimeType::point2d(), exec_state)?;
let end: Vec<KclValue> = args.get_kw_arg("end", &RuntimeType::point2d(), exec_state)?;
let center: Vec<KclValue> = args.get_kw_arg("center", &RuntimeType::point2d(), exec_state)?;
let construction_opt = args.get_kw_arg_opt("construction", &RuntimeType::bool(), exec_state)?;
let construction: bool = construction_opt.unwrap_or(false);
let construction_ctor = construction_opt;
let [start_x_value, start_y_value]: [KclValue; 2] = start.try_into().map_err(|_| {
KclError::new_semantic(KclErrorDetails::new(
"start must be a 2D point".to_owned(),
vec![args.source_range],
))
})?;
let [end_x_value, end_y_value]: [KclValue; 2] = end.try_into().map_err(|_| {
KclError::new_semantic(KclErrorDetails::new(
"end must be a 2D point".to_owned(),
vec![args.source_range],
))
})?;
let [center_x_value, center_y_value]: [KclValue; 2] = center.try_into().map_err(|_| {
KclError::new_semantic(KclErrorDetails::new(
"center must be a 2D point".to_owned(),
vec![args.source_range],
))
})?;
let Some(UnsolvedExpr::Unknown(start_x)) = start_x_value.as_unsolved_expr() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"start x must be a sketch var".to_owned(),
vec![args.source_range],
)));
};
let Some(UnsolvedExpr::Unknown(start_y)) = start_y_value.as_unsolved_expr() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"start y must be a sketch var".to_owned(),
vec![args.source_range],
)));
};
let Some(UnsolvedExpr::Unknown(end_x)) = end_x_value.as_unsolved_expr() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"end x must be a sketch var".to_owned(),
vec![args.source_range],
)));
};
let Some(UnsolvedExpr::Unknown(end_y)) = end_y_value.as_unsolved_expr() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"end y must be a sketch var".to_owned(),
vec![args.source_range],
)));
};
let Some(UnsolvedExpr::Unknown(center_x)) = center_x_value.as_unsolved_expr() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"center x must be a sketch var".to_owned(),
vec![args.source_range],
)));
};
let Some(UnsolvedExpr::Unknown(center_y)) = center_y_value.as_unsolved_expr() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"center y must be a sketch var".to_owned(),
vec![args.source_range],
)));
};
let ctor = ArcCtor {
start: Point2d {
x: start_x_value.to_sketch_expr().ok_or_else(|| {
KclError::new_semantic(KclErrorDetails::new(
"unable to convert numeric type to suffix".to_owned(),
vec![args.source_range],
))
})?,
y: start_y_value.to_sketch_expr().ok_or_else(|| {
KclError::new_semantic(KclErrorDetails::new(
"unable to convert numeric type to suffix".to_owned(),
vec![args.source_range],
))
})?,
},
end: Point2d {
x: end_x_value.to_sketch_expr().ok_or_else(|| {
KclError::new_semantic(KclErrorDetails::new(
"unable to convert numeric type to suffix".to_owned(),
vec![args.source_range],
))
})?,
y: end_y_value.to_sketch_expr().ok_or_else(|| {
KclError::new_semantic(KclErrorDetails::new(
"unable to convert numeric type to suffix".to_owned(),
vec![args.source_range],
))
})?,
},
center: Point2d {
x: center_x_value.to_sketch_expr().ok_or_else(|| {
KclError::new_semantic(KclErrorDetails::new(
"unable to convert numeric type to suffix".to_owned(),
vec![args.source_range],
))
})?,
y: center_y_value.to_sketch_expr().ok_or_else(|| {
KclError::new_semantic(KclErrorDetails::new(
"unable to convert numeric type to suffix".to_owned(),
vec![args.source_range],
))
})?,
},
construction: construction_ctor,
};
let start_object_id = exec_state.next_object_id();
let end_object_id = exec_state.next_object_id();
let center_object_id = exec_state.next_object_id();
let arc_object_id = exec_state.next_object_id();
let segment = UnsolvedSegment {
id: exec_state.next_uuid(),
object_id: arc_object_id,
kind: UnsolvedSegmentKind::Arc {
start: [UnsolvedExpr::Unknown(start_x), UnsolvedExpr::Unknown(start_y)],
end: [UnsolvedExpr::Unknown(end_x), UnsolvedExpr::Unknown(end_y)],
center: [UnsolvedExpr::Unknown(center_x), UnsolvedExpr::Unknown(center_y)],
ctor: Box::new(ctor),
start_object_id,
end_object_id,
center_object_id,
construction,
},
tag: None,
node_path: args.node_path.clone(),
meta: vec![args.source_range.into()],
};
#[cfg(feature = "artifact-graph")]
let optional_constraints = {
let start_object_id =
exec_state.add_placeholder_scene_object(start_object_id, args.source_range, args.node_path.clone());
let end_object_id =
exec_state.add_placeholder_scene_object(end_object_id, args.source_range, args.node_path.clone());
let center_object_id =
exec_state.add_placeholder_scene_object(center_object_id, args.source_range, args.node_path.clone());
let arc_object_id =
exec_state.add_placeholder_scene_object(arc_object_id, args.source_range, args.node_path.clone());
let mut optional_constraints = Vec::new();
if exec_state.segment_ids_edited_contains(&start_object_id)
|| exec_state.segment_ids_edited_contains(&arc_object_id)
{
if let Some(start_x_var) = start_x_value.as_sketch_var() {
let x_initial_value = start_x_var.initial_value_to_solver_units(
exec_state,
args.source_range,
"edited segment fixed constraint value",
)?;
optional_constraints.push(ezpz::Constraint::Fixed(
start_x_var.id.to_constraint_id(args.source_range)?,
x_initial_value.n,
));
}
if let Some(start_y_var) = start_y_value.as_sketch_var() {
let y_initial_value = start_y_var.initial_value_to_solver_units(
exec_state,
args.source_range,
"edited segment fixed constraint value",
)?;
optional_constraints.push(ezpz::Constraint::Fixed(
start_y_var.id.to_constraint_id(args.source_range)?,
y_initial_value.n,
));
}
}
if exec_state.segment_ids_edited_contains(&end_object_id)
|| exec_state.segment_ids_edited_contains(&arc_object_id)
{
if let Some(end_x_var) = end_x_value.as_sketch_var() {
let x_initial_value = end_x_var.initial_value_to_solver_units(
exec_state,
args.source_range,
"edited segment fixed constraint value",
)?;
optional_constraints.push(ezpz::Constraint::Fixed(
end_x_var.id.to_constraint_id(args.source_range)?,
x_initial_value.n,
));
}
if let Some(end_y_var) = end_y_value.as_sketch_var() {
let y_initial_value = end_y_var.initial_value_to_solver_units(
exec_state,
args.source_range,
"edited segment fixed constraint value",
)?;
optional_constraints.push(ezpz::Constraint::Fixed(
end_y_var.id.to_constraint_id(args.source_range)?,
y_initial_value.n,
));
}
}
if exec_state.segment_ids_edited_contains(¢er_object_id)
|| exec_state.segment_ids_edited_contains(&arc_object_id)
{
if let Some(center_x_var) = center_x_value.as_sketch_var() {
let x_initial_value = center_x_var.initial_value_to_solver_units(
exec_state,
args.source_range,
"edited segment fixed constraint value",
)?;
optional_constraints.push(ezpz::Constraint::Fixed(
center_x_var.id.to_constraint_id(args.source_range)?,
x_initial_value.n,
));
}
if let Some(center_y_var) = center_y_value.as_sketch_var() {
let y_initial_value = center_y_var.initial_value_to_solver_units(
exec_state,
args.source_range,
"edited segment fixed constraint value",
)?;
optional_constraints.push(ezpz::Constraint::Fixed(
center_y_var.id.to_constraint_id(args.source_range)?,
y_initial_value.n,
));
}
}
optional_constraints
};
let range = args.source_range;
let constraint = ezpz::Constraint::Arc(ezpz::datatypes::inputs::DatumCircularArc {
center: ezpz::datatypes::inputs::DatumPoint::new_xy(
center_x.to_constraint_id(range)?,
center_y.to_constraint_id(range)?,
),
start: ezpz::datatypes::inputs::DatumPoint::new_xy(
start_x.to_constraint_id(range)?,
start_y.to_constraint_id(range)?,
),
end: ezpz::datatypes::inputs::DatumPoint::new_xy(
end_x.to_constraint_id(range)?,
end_y.to_constraint_id(range)?,
),
});
let Some(sketch_state) = exec_state.sketch_block_mut() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"arc() can only be used inside a sketch block".to_owned(),
vec![args.source_range],
)));
};
sketch_state.needed_by_engine.push(segment.clone());
sketch_state.solver_constraints.push(constraint);
#[cfg(feature = "artifact-graph")]
sketch_state.solver_optional_constraints.extend(optional_constraints);
let meta = segment.meta.clone();
let abstract_segment = AbstractSegment {
repr: SegmentRepr::Unsolved {
segment: Box::new(segment),
},
meta,
};
Ok(KclValue::Segment {
value: Box::new(abstract_segment),
})
}
pub async fn circle(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let start: Vec<KclValue> = args.get_kw_arg("start", &RuntimeType::point2d(), exec_state)?;
let center: Vec<KclValue> = args.get_kw_arg("center", &RuntimeType::point2d(), exec_state)?;
let construction_opt = args.get_kw_arg_opt("construction", &RuntimeType::bool(), exec_state)?;
let construction: bool = construction_opt.unwrap_or(false);
let construction_ctor = construction_opt;
let [start_x_value, start_y_value]: [KclValue; 2] = start.try_into().map_err(|_| {
KclError::new_semantic(KclErrorDetails::new(
"start must be a 2D point".to_owned(),
vec![args.source_range],
))
})?;
let [center_x_value, center_y_value]: [KclValue; 2] = center.try_into().map_err(|_| {
KclError::new_semantic(KclErrorDetails::new(
"center must be a 2D point".to_owned(),
vec![args.source_range],
))
})?;
let Some(UnsolvedExpr::Unknown(start_x)) = start_x_value.as_unsolved_expr() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"start x must be a sketch var".to_owned(),
vec![args.source_range],
)));
};
let Some(UnsolvedExpr::Unknown(start_y)) = start_y_value.as_unsolved_expr() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"start y must be a sketch var".to_owned(),
vec![args.source_range],
)));
};
let Some(UnsolvedExpr::Unknown(center_x)) = center_x_value.as_unsolved_expr() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"center x must be a sketch var".to_owned(),
vec![args.source_range],
)));
};
let Some(UnsolvedExpr::Unknown(center_y)) = center_y_value.as_unsolved_expr() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"center y must be a sketch var".to_owned(),
vec![args.source_range],
)));
};
let ctor = CircleCtor {
start: Point2d {
x: start_x_value.to_sketch_expr().ok_or_else(|| {
KclError::new_semantic(KclErrorDetails::new(
"unable to convert numeric type to suffix".to_owned(),
vec![args.source_range],
))
})?,
y: start_y_value.to_sketch_expr().ok_or_else(|| {
KclError::new_semantic(KclErrorDetails::new(
"unable to convert numeric type to suffix".to_owned(),
vec![args.source_range],
))
})?,
},
center: Point2d {
x: center_x_value.to_sketch_expr().ok_or_else(|| {
KclError::new_semantic(KclErrorDetails::new(
"unable to convert numeric type to suffix".to_owned(),
vec![args.source_range],
))
})?,
y: center_y_value.to_sketch_expr().ok_or_else(|| {
KclError::new_semantic(KclErrorDetails::new(
"unable to convert numeric type to suffix".to_owned(),
vec![args.source_range],
))
})?,
},
construction: construction_ctor,
};
let start_object_id = exec_state.next_object_id();
let center_object_id = exec_state.next_object_id();
let circle_object_id = exec_state.next_object_id();
let segment = UnsolvedSegment {
id: exec_state.next_uuid(),
object_id: circle_object_id,
kind: UnsolvedSegmentKind::Circle {
start: [UnsolvedExpr::Unknown(start_x), UnsolvedExpr::Unknown(start_y)],
center: [UnsolvedExpr::Unknown(center_x), UnsolvedExpr::Unknown(center_y)],
ctor: Box::new(ctor),
start_object_id,
center_object_id,
construction,
},
tag: None,
node_path: args.node_path.clone(),
meta: vec![args.source_range.into()],
};
#[cfg(feature = "artifact-graph")]
let optional_constraints = {
let start_object_id =
exec_state.add_placeholder_scene_object(start_object_id, args.source_range, args.node_path.clone());
let center_object_id =
exec_state.add_placeholder_scene_object(center_object_id, args.source_range, args.node_path.clone());
let circle_object_id =
exec_state.add_placeholder_scene_object(circle_object_id, args.source_range, args.node_path.clone());
let mut optional_constraints = Vec::new();
if exec_state.segment_ids_edited_contains(&start_object_id)
|| exec_state.segment_ids_edited_contains(&circle_object_id)
{
if let Some(start_x_var) = start_x_value.as_sketch_var() {
let x_initial_value = start_x_var.initial_value_to_solver_units(
exec_state,
args.source_range,
"edited segment fixed constraint value",
)?;
optional_constraints.push(ezpz::Constraint::Fixed(
start_x_var.id.to_constraint_id(args.source_range)?,
x_initial_value.n,
));
}
if let Some(start_y_var) = start_y_value.as_sketch_var() {
let y_initial_value = start_y_var.initial_value_to_solver_units(
exec_state,
args.source_range,
"edited segment fixed constraint value",
)?;
optional_constraints.push(ezpz::Constraint::Fixed(
start_y_var.id.to_constraint_id(args.source_range)?,
y_initial_value.n,
));
}
}
if exec_state.segment_ids_edited_contains(¢er_object_id)
|| exec_state.segment_ids_edited_contains(&circle_object_id)
{
if let Some(center_x_var) = center_x_value.as_sketch_var() {
let x_initial_value = center_x_var.initial_value_to_solver_units(
exec_state,
args.source_range,
"edited segment fixed constraint value",
)?;
optional_constraints.push(ezpz::Constraint::Fixed(
center_x_var.id.to_constraint_id(args.source_range)?,
x_initial_value.n,
));
}
if let Some(center_y_var) = center_y_value.as_sketch_var() {
let y_initial_value = center_y_var.initial_value_to_solver_units(
exec_state,
args.source_range,
"edited segment fixed constraint value",
)?;
optional_constraints.push(ezpz::Constraint::Fixed(
center_y_var.id.to_constraint_id(args.source_range)?,
y_initial_value.n,
));
}
}
optional_constraints
};
let Some(sketch_state) = exec_state.sketch_block_mut() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"circle() can only be used inside a sketch block".to_owned(),
vec![args.source_range],
)));
};
sketch_state.needed_by_engine.push(segment.clone());
#[cfg(feature = "artifact-graph")]
sketch_state.solver_optional_constraints.extend(optional_constraints);
let meta = segment.meta.clone();
let abstract_segment = AbstractSegment {
repr: SegmentRepr::Unsolved {
segment: Box::new(segment),
},
meta,
};
Ok(KclValue::Segment {
value: Box::new(abstract_segment),
})
}
pub async fn coincident(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
"points",
&RuntimeType::Array(
Box::new(RuntimeType::Union(vec![RuntimeType::segment(), RuntimeType::point2d()])),
ArrayLen::Known(2),
),
exec_state,
)?;
let [point0, point1]: [KclValue; 2] = points.try_into().map_err(|_| {
KclError::new_semantic(KclErrorDetails::new(
"must have two input points".to_owned(),
vec![args.source_range],
))
})?;
let range = args.source_range;
match (&point0, &point1) {
(KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"first point must be an unsolved segment".to_owned(),
vec![args.source_range],
)));
};
let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"second point must be an unsolved segment".to_owned(),
vec![args.source_range],
)));
};
match (&unsolved0.kind, &unsolved1.kind) {
(
UnsolvedSegmentKind::Point { position: pos0, .. },
UnsolvedSegmentKind::Point { position: pos1, .. },
) => {
let p0_x = &pos0[0];
let p0_y = &pos0[1];
match (p0_x, p0_y) {
(UnsolvedExpr::Unknown(p0_x), UnsolvedExpr::Unknown(p0_y)) => {
let p1_x = &pos1[0];
let p1_y = &pos1[1];
match (p1_x, p1_y) {
(UnsolvedExpr::Unknown(p1_x), UnsolvedExpr::Unknown(p1_y)) => {
let constraint = SolverConstraint::PointsCoincident(
ezpz::datatypes::inputs::DatumPoint::new_xy(
p0_x.to_constraint_id(range)?,
p0_y.to_constraint_id(range)?,
),
ezpz::datatypes::inputs::DatumPoint::new_xy(
p1_x.to_constraint_id(range)?,
p1_y.to_constraint_id(range)?,
),
);
#[cfg(feature = "artifact-graph")]
let constraint_id = exec_state.next_object_id();
let Some(sketch_state) = exec_state.sketch_block_mut() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"coincident() can only be used inside a sketch block".to_owned(),
vec![args.source_range],
)));
};
sketch_state.solver_constraints.push(constraint);
#[cfg(feature = "artifact-graph")]
{
let constraint = crate::front::Constraint::Coincident(Coincident {
segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
});
sketch_state.sketch_constraints.push(constraint_id);
track_constraint(constraint_id, constraint, exec_state, &args);
}
Ok(KclValue::none())
}
(UnsolvedExpr::Known(p1_x), UnsolvedExpr::Known(p1_y)) => {
let p1_x = KclValue::Number {
value: p1_x.n,
ty: p1_x.ty,
meta: vec![args.source_range.into()],
};
let p1_y = KclValue::Number {
value: p1_y.n,
ty: p1_y.ty,
meta: vec![args.source_range.into()],
};
let (constraint_x, constraint_y) =
coincident_constraints_fixed(*p0_x, *p0_y, &p1_x, &p1_y, exec_state, &args)?;
#[cfg(feature = "artifact-graph")]
let constraint_id = exec_state.next_object_id();
let Some(sketch_state) = exec_state.sketch_block_mut() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"coincident() can only be used inside a sketch block".to_owned(),
vec![args.source_range],
)));
};
sketch_state.solver_constraints.push(constraint_x);
sketch_state.solver_constraints.push(constraint_y);
#[cfg(feature = "artifact-graph")]
{
let constraint = crate::front::Constraint::Coincident(Coincident {
segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
});
sketch_state.sketch_constraints.push(constraint_id);
track_constraint(constraint_id, constraint, exec_state, &args);
}
Ok(KclValue::none())
}
(UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
| (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
Err(KclError::new_semantic(KclErrorDetails::new(
"Unimplemented: When given points, input point at index 0 must be a sketch var for both x and y coordinates to constrain as coincident".to_owned(),
vec![args.source_range],
)))
}
}
}
(UnsolvedExpr::Known(p0_x), UnsolvedExpr::Known(p0_y)) => {
let p1_x = &pos1[0];
let p1_y = &pos1[1];
match (p1_x, p1_y) {
(UnsolvedExpr::Unknown(p1_x), UnsolvedExpr::Unknown(p1_y)) => {
let p0_x = KclValue::Number {
value: p0_x.n,
ty: p0_x.ty,
meta: vec![args.source_range.into()],
};
let p0_y = KclValue::Number {
value: p0_y.n,
ty: p0_y.ty,
meta: vec![args.source_range.into()],
};
let (constraint_x, constraint_y) =
coincident_constraints_fixed(*p1_x, *p1_y, &p0_x, &p0_y, exec_state, &args)?;
#[cfg(feature = "artifact-graph")]
let constraint_id = exec_state.next_object_id();
let Some(sketch_state) = exec_state.sketch_block_mut() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"coincident() can only be used inside a sketch block".to_owned(),
vec![args.source_range],
)));
};
sketch_state.solver_constraints.push(constraint_x);
sketch_state.solver_constraints.push(constraint_y);
#[cfg(feature = "artifact-graph")]
{
let constraint = crate::front::Constraint::Coincident(Coincident {
segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
});
sketch_state.sketch_constraints.push(constraint_id);
track_constraint(constraint_id, constraint, exec_state, &args);
}
Ok(KclValue::none())
}
(UnsolvedExpr::Known(p1_x), UnsolvedExpr::Known(p1_y)) => {
if *p0_x != *p1_x || *p0_y != *p1_y {
return Err(KclError::new_semantic(KclErrorDetails::new(
"Coincident constraint between two fixed points failed since coordinates differ"
.to_owned(),
vec![args.source_range],
)));
}
Ok(KclValue::none())
}
(UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
| (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
Err(KclError::new_semantic(KclErrorDetails::new(
"Unimplemented: When given points, input point at index 0 must be a sketch var for both x and y coordinates to constrain as coincident".to_owned(),
vec![args.source_range],
)))
}
}
}
(UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
| (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
Err(KclError::new_semantic(KclErrorDetails::new(
"When given points, input point at index 0 must be a sketch var for both x and y coordinates to constrain as coincident".to_owned(),
vec![args.source_range],
)))
}
}
}
(
UnsolvedSegmentKind::Point {
position: point_pos, ..
},
UnsolvedSegmentKind::Line {
start: line_start,
end: line_end,
..
},
)
| (
UnsolvedSegmentKind::Line {
start: line_start,
end: line_end,
..
},
UnsolvedSegmentKind::Point {
position: point_pos, ..
},
) => {
let point_x = &point_pos[0];
let point_y = &point_pos[1];
match (point_x, point_y) {
(UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
let (start_x, start_y) = (&line_start[0], &line_start[1]);
let (end_x, end_y) = (&line_end[0], &line_end[1]);
match (start_x, start_y, end_x, end_y) {
(
UnsolvedExpr::Unknown(sx), UnsolvedExpr::Unknown(sy),
UnsolvedExpr::Unknown(ex), UnsolvedExpr::Unknown(ey),
) => {
let point = DatumPoint::new_xy(
point_x.to_constraint_id(range)?,
point_y.to_constraint_id(range)?,
);
let line_segment = DatumLineSegment::new(
DatumPoint::new_xy(sx.to_constraint_id(range)?, sy.to_constraint_id(range)?),
DatumPoint::new_xy(ex.to_constraint_id(range)?, ey.to_constraint_id(range)?),
);
let constraint = SolverConstraint::PointLineDistance(point, line_segment, 0.0);
#[cfg(feature = "artifact-graph")]
let constraint_id = exec_state.next_object_id();
let Some(sketch_state) = exec_state.sketch_block_mut() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"coincident() can only be used inside a sketch block".to_owned(),
vec![args.source_range],
)));
};
sketch_state.solver_constraints.push(constraint);
#[cfg(feature = "artifact-graph")]
{
let constraint = crate::front::Constraint::Coincident(Coincident {
segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
});
sketch_state.sketch_constraints.push(constraint_id);
track_constraint(constraint_id, constraint, exec_state, &args);
}
Ok(KclValue::none())
}
_ => Err(KclError::new_semantic(KclErrorDetails::new(
"Line segment endpoints must be sketch variables for point-segment coincident constraint".to_owned(),
vec![args.source_range],
))),
}
}
_ => Err(KclError::new_semantic(KclErrorDetails::new(
"Point coordinates must be sketch variables for point-segment coincident constraint"
.to_owned(),
vec![args.source_range],
))),
}
}
(
UnsolvedSegmentKind::Point {
position: point_pos, ..
},
UnsolvedSegmentKind::Arc {
start: arc_start,
end: arc_end,
center: arc_center,
..
},
)
| (
UnsolvedSegmentKind::Arc {
start: arc_start,
end: arc_end,
center: arc_center,
..
},
UnsolvedSegmentKind::Point {
position: point_pos, ..
},
) => {
let point_x = &point_pos[0];
let point_y = &point_pos[1];
match (point_x, point_y) {
(UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
let (center_x, center_y) = (&arc_center[0], &arc_center[1]);
let (start_x, start_y) = (&arc_start[0], &arc_start[1]);
let (end_x, end_y) = (&arc_end[0], &arc_end[1]);
match (center_x, center_y, start_x, start_y, end_x, end_y) {
(
UnsolvedExpr::Unknown(cx), UnsolvedExpr::Unknown(cy),
UnsolvedExpr::Unknown(sx), UnsolvedExpr::Unknown(sy),
UnsolvedExpr::Unknown(ex), UnsolvedExpr::Unknown(ey),
) => {
let point = DatumPoint::new_xy(
point_x.to_constraint_id(range)?,
point_y.to_constraint_id(range)?,
);
let circular_arc = DatumCircularArc {
center: DatumPoint::new_xy(
cx.to_constraint_id(range)?,
cy.to_constraint_id(range)?,
),
start: DatumPoint::new_xy(
sx.to_constraint_id(range)?,
sy.to_constraint_id(range)?,
),
end: DatumPoint::new_xy(
ex.to_constraint_id(range)?,
ey.to_constraint_id(range)?,
),
};
let constraint = SolverConstraint::PointArcCoincident(circular_arc, point);
#[cfg(feature = "artifact-graph")]
let constraint_id = exec_state.next_object_id();
let Some(sketch_state) = exec_state.sketch_block_mut() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"coincident() can only be used inside a sketch block".to_owned(),
vec![args.source_range],
)));
};
sketch_state.solver_constraints.push(constraint);
#[cfg(feature = "artifact-graph")]
{
let constraint = crate::front::Constraint::Coincident(Coincident {
segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
});
sketch_state.sketch_constraints.push(constraint_id);
track_constraint(constraint_id, constraint, exec_state, &args);
}
Ok(KclValue::none())
}
_ => Err(KclError::new_semantic(KclErrorDetails::new(
"Arc center, start, and end points must be sketch variables for point-arc coincident constraint".to_owned(),
vec![args.source_range],
))),
}
}
_ => Err(KclError::new_semantic(KclErrorDetails::new(
"Point coordinates must be sketch variables for point-arc coincident constraint".to_owned(),
vec![args.source_range],
))),
}
}
(
UnsolvedSegmentKind::Point {
position: point_pos, ..
},
UnsolvedSegmentKind::Circle {
start: circle_start,
center: circle_center,
..
},
)
| (
UnsolvedSegmentKind::Circle {
start: circle_start,
center: circle_center,
..
},
UnsolvedSegmentKind::Point {
position: point_pos, ..
},
) => {
let point_x = &point_pos[0];
let point_y = &point_pos[1];
match (point_x, point_y) {
(UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
let (center_x, center_y) = (&circle_center[0], &circle_center[1]);
let (start_x, start_y) = (&circle_start[0], &circle_start[1]);
match (center_x, center_y, start_x, start_y) {
(
UnsolvedExpr::Unknown(cx),
UnsolvedExpr::Unknown(cy),
UnsolvedExpr::Unknown(sx),
UnsolvedExpr::Unknown(sy),
) => {
let point_radius_line = DatumLineSegment::new(
DatumPoint::new_xy(
cx.to_constraint_id(range)?,
cy.to_constraint_id(range)?,
),
DatumPoint::new_xy(
point_x.to_constraint_id(range)?,
point_y.to_constraint_id(range)?,
),
);
let circle_radius_line = DatumLineSegment::new(
DatumPoint::new_xy(
cx.to_constraint_id(range)?,
cy.to_constraint_id(range)?,
),
DatumPoint::new_xy(
sx.to_constraint_id(range)?,
sy.to_constraint_id(range)?,
),
);
let constraint =
SolverConstraint::LinesEqualLength(point_radius_line, circle_radius_line);
#[cfg(feature = "artifact-graph")]
let constraint_id = exec_state.next_object_id();
let Some(sketch_state) = exec_state.sketch_block_mut() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"coincident() can only be used inside a sketch block".to_owned(),
vec![args.source_range],
)));
};
sketch_state.solver_constraints.push(constraint);
#[cfg(feature = "artifact-graph")]
{
let constraint = crate::front::Constraint::Coincident(Coincident {
segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
});
sketch_state.sketch_constraints.push(constraint_id);
track_constraint(constraint_id, constraint, exec_state, &args);
}
Ok(KclValue::none())
}
_ => Err(KclError::new_semantic(KclErrorDetails::new(
"Circle start and center points must be sketch variables for point-circle coincident constraint".to_owned(),
vec![args.source_range],
))),
}
}
_ => Err(KclError::new_semantic(KclErrorDetails::new(
"Point coordinates must be sketch variables for point-circle coincident constraint"
.to_owned(),
vec![args.source_range],
))),
}
}
(
UnsolvedSegmentKind::Line {
start: line0_start,
end: line0_end,
..
},
UnsolvedSegmentKind::Line {
start: line1_start,
end: line1_end,
..
},
) => {
let (line0_start_x, line0_start_y) = (&line0_start[0], &line0_start[1]);
let (line0_end_x, line0_end_y) = (&line0_end[0], &line0_end[1]);
let (line1_start_x, line1_start_y) = (&line1_start[0], &line1_start[1]);
let (line1_end_x, line1_end_y) = (&line1_end[0], &line1_end[1]);
match (
line0_start_x,
line0_start_y,
line0_end_x,
line0_end_y,
line1_start_x,
line1_start_y,
line1_end_x,
line1_end_y,
) {
(
UnsolvedExpr::Unknown(l0_sx),
UnsolvedExpr::Unknown(l0_sy),
UnsolvedExpr::Unknown(l0_ex),
UnsolvedExpr::Unknown(l0_ey),
UnsolvedExpr::Unknown(l1_sx),
UnsolvedExpr::Unknown(l1_sy),
UnsolvedExpr::Unknown(l1_ex),
UnsolvedExpr::Unknown(l1_ey),
) => {
let line0_segment = DatumLineSegment::new(
DatumPoint::new_xy(l0_sx.to_constraint_id(range)?, l0_sy.to_constraint_id(range)?),
DatumPoint::new_xy(l0_ex.to_constraint_id(range)?, l0_ey.to_constraint_id(range)?),
);
let line1_segment = DatumLineSegment::new(
DatumPoint::new_xy(l1_sx.to_constraint_id(range)?, l1_sy.to_constraint_id(range)?),
DatumPoint::new_xy(l1_ex.to_constraint_id(range)?, l1_ey.to_constraint_id(range)?),
);
let parallel_constraint =
SolverConstraint::LinesAtAngle(line0_segment, line1_segment, AngleKind::Parallel);
let point_on_line1 =
DatumPoint::new_xy(l1_sx.to_constraint_id(range)?, l1_sy.to_constraint_id(range)?);
let distance_constraint =
SolverConstraint::PointLineDistance(point_on_line1, line0_segment, 0.0);
#[cfg(feature = "artifact-graph")]
let constraint_id = exec_state.next_object_id();
let Some(sketch_state) = exec_state.sketch_block_mut() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"coincident() can only be used inside a sketch block".to_owned(),
vec![args.source_range],
)));
};
sketch_state.solver_constraints.push(parallel_constraint);
sketch_state.solver_constraints.push(distance_constraint);
#[cfg(feature = "artifact-graph")]
{
let constraint = crate::front::Constraint::Coincident(Coincident {
segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
});
sketch_state.sketch_constraints.push(constraint_id);
track_constraint(constraint_id, constraint, exec_state, &args);
}
Ok(KclValue::none())
}
_ => Err(KclError::new_semantic(KclErrorDetails::new(
"Line segment endpoints must be sketch variables for line-line coincident constraint"
.to_owned(),
vec![args.source_range],
))),
}
}
_ => Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"coincident supports point-point, point-segment, or segment-segment; found {:?} and {:?}",
&unsolved0.kind, &unsolved1.kind
),
vec![args.source_range],
))),
}
}
(KclValue::Segment { value: seg }, point2d) | (point2d, KclValue::Segment { value: seg }) => {
let Some(pt) = <[TyF64; 2]>::from_kcl_val(point2d) else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"Expected a Segment or Point2d (e.g. [1mm, 2mm])".to_owned(),
vec![args.source_range],
)));
};
let SegmentRepr::Unsolved { segment: unsolved } = &seg.repr else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"segment must be an unsolved segment".to_owned(),
vec![args.source_range],
)));
};
match &unsolved.kind {
UnsolvedSegmentKind::Point { position, .. } => {
let p_x = &position[0];
let p_y = &position[1];
match (p_x, p_y) {
(UnsolvedExpr::Unknown(p_x), UnsolvedExpr::Unknown(p_y)) => {
let pt_x = KclValue::Number {
value: pt[0].n,
ty: pt[0].ty,
meta: vec![args.source_range.into()],
};
let pt_y = KclValue::Number {
value: pt[1].n,
ty: pt[1].ty,
meta: vec![args.source_range.into()],
};
let (constraint_x, constraint_y) =
coincident_constraints_fixed(*p_x, *p_y, &pt_x, &pt_y, exec_state, &args)?;
#[cfg(feature = "artifact-graph")]
let constraint_id = exec_state.next_object_id();
#[cfg(feature = "artifact-graph")]
let coincident_segments = coincident_segments_for_segment_and_point2d(
unsolved.object_id,
point2d,
matches!((&point0, &point1), (KclValue::Segment { .. }, _)),
);
let Some(sketch_state) = exec_state.sketch_block_mut() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"coincident() can only be used inside a sketch block".to_owned(),
vec![args.source_range],
)));
};
sketch_state.solver_constraints.push(constraint_x);
sketch_state.solver_constraints.push(constraint_y);
#[cfg(feature = "artifact-graph")]
{
let constraint = crate::front::Constraint::Coincident(Coincident {
segments: coincident_segments,
});
sketch_state.sketch_constraints.push(constraint_id);
track_constraint(constraint_id, constraint, exec_state, &args);
}
Ok(KclValue::none())
}
(UnsolvedExpr::Known(known_x), UnsolvedExpr::Known(known_y)) => {
let pt_x_val = normalize_to_solver_distance_unit(
&KclValue::Number {
value: pt[0].n,
ty: pt[0].ty,
meta: vec![args.source_range.into()],
},
args.source_range,
exec_state,
"coincident constraint value",
)?;
let pt_y_val = normalize_to_solver_distance_unit(
&KclValue::Number {
value: pt[1].n,
ty: pt[1].ty,
meta: vec![args.source_range.into()],
},
args.source_range,
exec_state,
"coincident constraint value",
)?;
let Some(pt_x) = pt_x_val.as_ty_f64() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"Expected number for Point2d x coordinate".to_owned(),
vec![args.source_range],
)));
};
let Some(pt_y) = pt_y_val.as_ty_f64() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"Expected number for Point2d y coordinate".to_owned(),
vec![args.source_range],
)));
};
let known_x_val = normalize_to_solver_distance_unit(
&KclValue::Number {
value: known_x.n,
ty: known_x.ty,
meta: vec![args.source_range.into()],
},
args.source_range,
exec_state,
"coincident constraint value",
)?;
let Some(known_x_f) = known_x_val.as_ty_f64() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"Expected number for known x coordinate".to_owned(),
vec![args.source_range],
)));
};
let known_y_val = normalize_to_solver_distance_unit(
&KclValue::Number {
value: known_y.n,
ty: known_y.ty,
meta: vec![args.source_range.into()],
},
args.source_range,
exec_state,
"coincident constraint value",
)?;
let Some(known_y_f) = known_y_val.as_ty_f64() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"Expected number for known y coordinate".to_owned(),
vec![args.source_range],
)));
};
if known_x_f.n != pt_x.n || known_y_f.n != pt_y.n {
return Err(KclError::new_semantic(KclErrorDetails::new(
"Coincident constraint between two fixed points failed since coordinates differ"
.to_owned(),
vec![args.source_range],
)));
}
Ok(KclValue::none())
}
_ => Err(KclError::new_semantic(KclErrorDetails::new(
"Point coordinates must have consistent known/unknown status for coincident constraint"
.to_owned(),
vec![args.source_range],
))),
}
}
_ => Err(KclError::new_semantic(KclErrorDetails::new(
"A Point2d can only be constrained coincident with a point segment, not a line or arc".to_owned(),
vec![args.source_range],
))),
}
}
_ => {
let pt0 = <[TyF64; 2]>::from_kcl_val(&point0);
let pt1 = <[TyF64; 2]>::from_kcl_val(&point1);
match (pt0, pt1) {
(Some(a), Some(b)) => {
let a_x = normalize_to_solver_distance_unit(
&KclValue::Number {
value: a[0].n,
ty: a[0].ty,
meta: vec![args.source_range.into()],
},
args.source_range,
exec_state,
"coincident constraint value",
)?;
let a_y = normalize_to_solver_distance_unit(
&KclValue::Number {
value: a[1].n,
ty: a[1].ty,
meta: vec![args.source_range.into()],
},
args.source_range,
exec_state,
"coincident constraint value",
)?;
let b_x = normalize_to_solver_distance_unit(
&KclValue::Number {
value: b[0].n,
ty: b[0].ty,
meta: vec![args.source_range.into()],
},
args.source_range,
exec_state,
"coincident constraint value",
)?;
let b_y = normalize_to_solver_distance_unit(
&KclValue::Number {
value: b[1].n,
ty: b[1].ty,
meta: vec![args.source_range.into()],
},
args.source_range,
exec_state,
"coincident constraint value",
)?;
if a_x.as_ty_f64().map(|v| v.n) != b_x.as_ty_f64().map(|v| v.n)
|| a_y.as_ty_f64().map(|v| v.n) != b_y.as_ty_f64().map(|v| v.n)
{
return Err(KclError::new_semantic(KclErrorDetails::new(
"Coincident constraint between two fixed points failed since coordinates differ".to_owned(),
vec![args.source_range],
)));
}
Ok(KclValue::none())
}
_ => Err(KclError::new_semantic(KclErrorDetails::new(
"All inputs must be Segments or Point2d values".to_owned(),
vec![args.source_range],
))),
}
}
}
}
#[cfg(feature = "artifact-graph")]
fn track_constraint(constraint_id: ObjectId, constraint: Constraint, exec_state: &mut ExecState, args: &Args) {
let sketch_id = {
let Some(sketch_state) = exec_state.sketch_block_mut() else {
debug_assert!(false, "Constraint created outside a sketch block");
return;
};
sketch_state.sketch_id
};
let Some(sketch_id) = sketch_id else {
debug_assert!(false, "Constraint created without a sketch id");
return;
};
let artifact_id = exec_state.next_artifact_id();
exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
id: artifact_id,
sketch_id,
constraint_id,
constraint_type: SketchBlockConstraintType::from(&constraint),
code_ref: CodeRef::placeholder(args.source_range),
}));
exec_state.add_scene_object(
Object {
id: constraint_id,
kind: ObjectKind::Constraint { constraint },
label: Default::default(),
comments: Default::default(),
artifact_id,
source: SourceRef::new(args.source_range, args.node_path.clone()),
},
args.source_range,
);
}
fn coincident_constraints_fixed(
p0_x: SketchVarId,
p0_y: SketchVarId,
p1_x: &KclValue,
p1_y: &KclValue,
exec_state: &mut ExecState,
args: &Args,
) -> Result<(ezpz::Constraint, ezpz::Constraint), KclError> {
let p1_x_number_value =
normalize_to_solver_distance_unit(p1_x, p1_x.into(), exec_state, "coincident constraint value")?;
let p1_y_number_value =
normalize_to_solver_distance_unit(p1_y, p1_y.into(), exec_state, "coincident constraint value")?;
let Some(p1_x) = p1_x_number_value.as_ty_f64() else {
let message = format!(
"Expected number after coercion, but found {}",
p1_x_number_value.human_friendly_type()
);
debug_assert!(false, "{}", &message);
return Err(KclError::new_internal(KclErrorDetails::new(
message,
vec![args.source_range],
)));
};
let Some(p1_y) = p1_y_number_value.as_ty_f64() else {
let message = format!(
"Expected number after coercion, but found {}",
p1_y_number_value.human_friendly_type()
);
debug_assert!(false, "{}", &message);
return Err(KclError::new_internal(KclErrorDetails::new(
message,
vec![args.source_range],
)));
};
let constraint_x = SolverConstraint::Fixed(p0_x.to_constraint_id(args.source_range)?, p1_x.n);
let constraint_y = SolverConstraint::Fixed(p0_y.to_constraint_id(args.source_range)?, p1_y.n);
Ok((constraint_x, constraint_y))
}
pub async fn distance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
"points",
&RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
exec_state,
)?;
let [point0, point1]: [KclValue; 2] = points.try_into().map_err(|_| {
KclError::new_semantic(KclErrorDetails::new(
"must have two input points".to_owned(),
vec![args.source_range],
))
})?;
match (&point0, &point1) {
(KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"first point must be an unsolved segment".to_owned(),
vec![args.source_range],
)));
};
let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"second point must be an unsolved segment".to_owned(),
vec![args.source_range],
)));
};
match (&unsolved0.kind, &unsolved1.kind) {
(
UnsolvedSegmentKind::Point { position: pos0, .. },
UnsolvedSegmentKind::Point { position: pos1, .. },
) => {
match (&pos0[0], &pos0[1], &pos1[0], &pos1[1]) {
(
UnsolvedExpr::Unknown(p0_x),
UnsolvedExpr::Unknown(p0_y),
UnsolvedExpr::Unknown(p1_x),
UnsolvedExpr::Unknown(p1_y),
) => {
let sketch_constraint = SketchConstraint {
kind: SketchConstraintKind::Distance {
points: [
ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
vars: crate::front::Point2d { x: *p0_x, y: *p0_y },
object_id: unsolved0.object_id,
}),
ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
vars: crate::front::Point2d { x: *p1_x, y: *p1_y },
object_id: unsolved1.object_id,
}),
],
},
meta: vec![args.source_range.into()],
};
Ok(KclValue::SketchConstraint {
value: Box::new(sketch_constraint),
})
}
_ => Err(KclError::new_semantic(KclErrorDetails::new(
"unimplemented: distance() arguments must be all sketch vars in all coordinates".to_owned(),
vec![args.source_range],
))),
}
}
_ => Err(KclError::new_semantic(KclErrorDetails::new(
"distance() arguments must be unsolved points".to_owned(),
vec![args.source_range],
))),
}
}
(KclValue::Segment { value: seg }, point2d) | (point2d, KclValue::Segment { value: seg }) => {
if !point2d_is_origin(point2d) {
return Err(KclError::new_semantic(KclErrorDetails::new(
"distance() Point2d arguments must be ORIGIN".to_owned(),
vec![args.source_range],
)));
}
let SegmentRepr::Unsolved { segment: unsolved } = &seg.repr else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"segment must be an unsolved segment".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedSegmentKind::Point { position, .. } = &unsolved.kind else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"distance() arguments must be unsolved points or ORIGIN".to_owned(),
vec![args.source_range],
)));
};
match (&position[0], &position[1]) {
(UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
let point = ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
vars: crate::front::Point2d {
x: *point_x,
y: *point_y,
},
object_id: unsolved.object_id,
});
let points = if matches!((&point0, &point1), (KclValue::Segment { .. }, _)) {
[point, ConstrainablePoint2dOrOrigin::Origin]
} else {
[ConstrainablePoint2dOrOrigin::Origin, point]
};
Ok(KclValue::SketchConstraint {
value: Box::new(SketchConstraint {
kind: SketchConstraintKind::Distance { points },
meta: vec![args.source_range.into()],
}),
})
}
_ => Err(KclError::new_semantic(KclErrorDetails::new(
"unimplemented: distance() point arguments must be sketch vars in all coordinates".to_owned(),
vec![args.source_range],
))),
}
}
_ => Err(KclError::new_semantic(KclErrorDetails::new(
"distance() arguments must be point segments or ORIGIN".to_owned(),
vec![args.source_range],
))),
}
}
fn create_circular_radius_constraint(
segment: KclValue,
constraint_kind: fn([ConstrainablePoint2d; 2]) -> SketchConstraintKind,
source_range: crate::SourceRange,
) -> Result<SketchConstraint, KclError> {
let dummy_constraint = constraint_kind([
ConstrainablePoint2d {
vars: crate::front::Point2d {
x: SketchVarId(0),
y: SketchVarId(0),
},
object_id: ObjectId(0),
},
ConstrainablePoint2d {
vars: crate::front::Point2d {
x: SketchVarId(0),
y: SketchVarId(0),
},
object_id: ObjectId(0),
},
]);
let function_name = dummy_constraint.name();
let KclValue::Segment { value: seg } = segment else {
return Err(KclError::new_semantic(KclErrorDetails::new(
format!("{}() argument must be a segment", function_name),
vec![source_range],
)));
};
let SegmentRepr::Unsolved { segment: unsolved } = &seg.repr else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"segment must be unsolved".to_owned(),
vec![source_range],
)));
};
match &unsolved.kind {
UnsolvedSegmentKind::Arc {
center,
start,
center_object_id,
start_object_id,
..
}
| UnsolvedSegmentKind::Circle {
center,
start,
center_object_id,
start_object_id,
..
} => {
match (¢er[0], ¢er[1], &start[0], &start[1]) {
(
UnsolvedExpr::Unknown(center_x),
UnsolvedExpr::Unknown(center_y),
UnsolvedExpr::Unknown(start_x),
UnsolvedExpr::Unknown(start_y),
) => {
let sketch_constraint = SketchConstraint {
kind: constraint_kind([
ConstrainablePoint2d {
vars: crate::front::Point2d {
x: *center_x,
y: *center_y,
},
object_id: *center_object_id,
},
ConstrainablePoint2d {
vars: crate::front::Point2d {
x: *start_x,
y: *start_y,
},
object_id: *start_object_id,
},
]),
meta: vec![source_range.into()],
};
Ok(sketch_constraint)
}
_ => Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"unimplemented: {}() arc or circle segment must have all sketch vars in all coordinates",
function_name
),
vec![source_range],
))),
}
}
_ => Err(KclError::new_semantic(KclErrorDetails::new(
format!("{}() argument must be an arc or circle segment", function_name),
vec![source_range],
))),
}
}
pub async fn radius(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let segment: KclValue =
args.get_unlabeled_kw_arg("points", &RuntimeType::Primitive(PrimitiveType::Any), exec_state)?;
create_circular_radius_constraint(
segment,
|points| SketchConstraintKind::Radius { points },
args.source_range,
)
.map(|constraint| KclValue::SketchConstraint {
value: Box::new(constraint),
})
}
pub async fn diameter(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let segment: KclValue =
args.get_unlabeled_kw_arg("points", &RuntimeType::Primitive(PrimitiveType::Any), exec_state)?;
create_circular_radius_constraint(
segment,
|points| SketchConstraintKind::Diameter { points },
args.source_range,
)
.map(|constraint| KclValue::SketchConstraint {
value: Box::new(constraint),
})
}
pub async fn horizontal_distance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
"points",
&RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
exec_state,
)?;
let [p1, p2] = points.as_slice() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"must have two input points".to_owned(),
vec![args.source_range],
)));
};
match (p1, p2) {
(KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"first point must be an unsolved segment".to_owned(),
vec![args.source_range],
)));
};
let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"second point must be an unsolved segment".to_owned(),
vec![args.source_range],
)));
};
match (&unsolved0.kind, &unsolved1.kind) {
(
UnsolvedSegmentKind::Point { position: pos0, .. },
UnsolvedSegmentKind::Point { position: pos1, .. },
) => {
match (&pos0[0], &pos0[1], &pos1[0], &pos1[1]) {
(
UnsolvedExpr::Unknown(p0_x),
UnsolvedExpr::Unknown(p0_y),
UnsolvedExpr::Unknown(p1_x),
UnsolvedExpr::Unknown(p1_y),
) => {
let sketch_constraint = SketchConstraint {
kind: SketchConstraintKind::HorizontalDistance {
points: [
ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
vars: crate::front::Point2d { x: *p0_x, y: *p0_y },
object_id: unsolved0.object_id,
}),
ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
vars: crate::front::Point2d { x: *p1_x, y: *p1_y },
object_id: unsolved1.object_id,
}),
],
},
meta: vec![args.source_range.into()],
};
Ok(KclValue::SketchConstraint {
value: Box::new(sketch_constraint),
})
}
_ => Err(KclError::new_semantic(KclErrorDetails::new(
"unimplemented: horizontalDistance() arguments must be all sketch vars in all coordinates"
.to_owned(),
vec![args.source_range],
))),
}
}
_ => Err(KclError::new_semantic(KclErrorDetails::new(
"horizontalDistance() arguments must be unsolved points".to_owned(),
vec![args.source_range],
))),
}
}
(KclValue::Segment { value: seg }, point2d) | (point2d, KclValue::Segment { value: seg }) => {
if !point2d_is_origin(point2d) {
return Err(KclError::new_semantic(KclErrorDetails::new(
"horizontalDistance() Point2d arguments must be ORIGIN".to_owned(),
vec![args.source_range],
)));
}
let SegmentRepr::Unsolved { segment: unsolved } = &seg.repr else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"segment must be an unsolved segment".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedSegmentKind::Point { position, .. } = &unsolved.kind else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"horizontalDistance() arguments must be unsolved points or ORIGIN".to_owned(),
vec![args.source_range],
)));
};
match (&position[0], &position[1]) {
(UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
let point = ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
vars: crate::front::Point2d {
x: *point_x,
y: *point_y,
},
object_id: unsolved.object_id,
});
let points = if matches!((p1, p2), (KclValue::Segment { .. }, _)) {
[point, ConstrainablePoint2dOrOrigin::Origin]
} else {
[ConstrainablePoint2dOrOrigin::Origin, point]
};
Ok(KclValue::SketchConstraint {
value: Box::new(SketchConstraint {
kind: SketchConstraintKind::HorizontalDistance { points },
meta: vec![args.source_range.into()],
}),
})
}
_ => Err(KclError::new_semantic(KclErrorDetails::new(
"unimplemented: horizontalDistance() point arguments must be sketch vars in all coordinates"
.to_owned(),
vec![args.source_range],
))),
}
}
_ => Err(KclError::new_semantic(KclErrorDetails::new(
"horizontalDistance() arguments must be point segments or ORIGIN".to_owned(),
vec![args.source_range],
))),
}
}
pub async fn vertical_distance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
"points",
&RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
exec_state,
)?;
let [p1, p2] = points.as_slice() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"must have two input points".to_owned(),
vec![args.source_range],
)));
};
match (p1, p2) {
(KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"first point must be an unsolved segment".to_owned(),
vec![args.source_range],
)));
};
let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"second point must be an unsolved segment".to_owned(),
vec![args.source_range],
)));
};
match (&unsolved0.kind, &unsolved1.kind) {
(
UnsolvedSegmentKind::Point { position: pos0, .. },
UnsolvedSegmentKind::Point { position: pos1, .. },
) => {
match (&pos0[0], &pos0[1], &pos1[0], &pos1[1]) {
(
UnsolvedExpr::Unknown(p0_x),
UnsolvedExpr::Unknown(p0_y),
UnsolvedExpr::Unknown(p1_x),
UnsolvedExpr::Unknown(p1_y),
) => {
let sketch_constraint = SketchConstraint {
kind: SketchConstraintKind::VerticalDistance {
points: [
ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
vars: crate::front::Point2d { x: *p0_x, y: *p0_y },
object_id: unsolved0.object_id,
}),
ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
vars: crate::front::Point2d { x: *p1_x, y: *p1_y },
object_id: unsolved1.object_id,
}),
],
},
meta: vec![args.source_range.into()],
};
Ok(KclValue::SketchConstraint {
value: Box::new(sketch_constraint),
})
}
_ => Err(KclError::new_semantic(KclErrorDetails::new(
"unimplemented: verticalDistance() arguments must be all sketch vars in all coordinates"
.to_owned(),
vec![args.source_range],
))),
}
}
_ => Err(KclError::new_semantic(KclErrorDetails::new(
"verticalDistance() arguments must be unsolved points".to_owned(),
vec![args.source_range],
))),
}
}
(KclValue::Segment { value: seg }, point2d) | (point2d, KclValue::Segment { value: seg }) => {
if !point2d_is_origin(point2d) {
return Err(KclError::new_semantic(KclErrorDetails::new(
"verticalDistance() Point2d arguments must be ORIGIN".to_owned(),
vec![args.source_range],
)));
}
let SegmentRepr::Unsolved { segment: unsolved } = &seg.repr else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"segment must be an unsolved segment".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedSegmentKind::Point { position, .. } = &unsolved.kind else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"verticalDistance() arguments must be unsolved points or ORIGIN".to_owned(),
vec![args.source_range],
)));
};
match (&position[0], &position[1]) {
(UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
let point = ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
vars: crate::front::Point2d {
x: *point_x,
y: *point_y,
},
object_id: unsolved.object_id,
});
let points = if matches!((p1, p2), (KclValue::Segment { .. }, _)) {
[point, ConstrainablePoint2dOrOrigin::Origin]
} else {
[ConstrainablePoint2dOrOrigin::Origin, point]
};
Ok(KclValue::SketchConstraint {
value: Box::new(SketchConstraint {
kind: SketchConstraintKind::VerticalDistance { points },
meta: vec![args.source_range.into()],
}),
})
}
_ => Err(KclError::new_semantic(KclErrorDetails::new(
"unimplemented: verticalDistance() point arguments must be sketch vars in all coordinates"
.to_owned(),
vec![args.source_range],
))),
}
}
_ => Err(KclError::new_semantic(KclErrorDetails::new(
"verticalDistance() arguments must be point segments or ORIGIN".to_owned(),
vec![args.source_range],
))),
}
}
pub async fn equal_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
#[derive(Clone, Copy)]
struct ConstrainableLine {
solver_line: DatumLineSegment,
#[cfg(feature = "artifact-graph")]
object_id: ObjectId,
}
let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
"lines",
&RuntimeType::Array(
Box::new(RuntimeType::Primitive(PrimitiveType::Any)),
ArrayLen::Minimum(2),
),
exec_state,
)?;
let range = args.source_range;
let constrainable_lines: Vec<ConstrainableLine> = lines
.iter()
.map(|line| {
let KclValue::Segment { value: segment } = line else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line argument must be a Segment".to_owned(),
vec![args.source_range],
)));
};
let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
return Err(KclError::new_internal(KclErrorDetails::new(
"line must be an unsolved Segment".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line argument must be a line, no other type of Segment".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedExpr::Unknown(line_p0_x) = &start[0] else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line's start x coordinate must be a var".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedExpr::Unknown(line_p0_y) = &start[1] else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line's start y coordinate must be a var".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedExpr::Unknown(line_p1_x) = &end[0] else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line's end x coordinate must be a var".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedExpr::Unknown(line_p1_y) = &end[1] else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line's end y coordinate must be a var".to_owned(),
vec![args.source_range],
)));
};
let solver_line_p0 =
DatumPoint::new_xy(line_p0_x.to_constraint_id(range)?, line_p0_y.to_constraint_id(range)?);
let solver_line_p1 =
DatumPoint::new_xy(line_p1_x.to_constraint_id(range)?, line_p1_y.to_constraint_id(range)?);
Ok(ConstrainableLine {
solver_line: DatumLineSegment::new(solver_line_p0, solver_line_p1),
#[cfg(feature = "artifact-graph")]
object_id: unsolved.object_id,
})
})
.collect::<Result<_, _>>()?;
#[cfg(feature = "artifact-graph")]
let constraint_id = exec_state.next_object_id();
let Some(sketch_state) = exec_state.sketch_block_mut() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"equalLength() can only be used inside a sketch block".to_owned(),
vec![args.source_range],
)));
};
let first_line = constrainable_lines[0];
for line in constrainable_lines.iter().skip(1) {
sketch_state.solver_constraints.push(SolverConstraint::LinesEqualLength(
first_line.solver_line,
line.solver_line,
));
}
#[cfg(feature = "artifact-graph")]
{
let constraint = crate::front::Constraint::LinesEqualLength(LinesEqualLength {
lines: constrainable_lines.iter().map(|line| line.object_id).collect(),
});
sketch_state.sketch_constraints.push(constraint_id);
track_constraint(constraint_id, constraint, exec_state, &args);
}
Ok(KclValue::none())
}
fn datum_point(coords: [SketchVarId; 2], range: crate::SourceRange) -> Result<DatumPoint, KclError> {
Ok(DatumPoint::new_xy(
coords[0].to_constraint_id(range)?,
coords[1].to_constraint_id(range)?,
))
}
fn sketch_var_initial_value(
sketch_vars: &[KclValue],
id: SketchVarId,
exec_state: &mut ExecState,
range: crate::SourceRange,
) -> Result<f64, KclError> {
sketch_vars
.get(id.0)
.and_then(KclValue::as_sketch_var)
.map(|sketch_var| {
sketch_var
.initial_value_to_solver_units(exec_state, range, "equalRadius() hidden shared radius initial value")
.map(|value| value.n)
})
.transpose()?
.ok_or_else(|| {
KclError::new_internal(KclErrorDetails::new(
format!("Missing sketch variable initial value for id {}", id.0),
vec![range],
))
})
}
fn radius_guess(
sketch_vars: &[KclValue],
center: [SketchVarId; 2],
point: [SketchVarId; 2],
exec_state: &mut ExecState,
range: crate::SourceRange,
) -> Result<f64, KclError> {
let dx = sketch_var_initial_value(sketch_vars, point[0], exec_state, range)?
- sketch_var_initial_value(sketch_vars, center[0], exec_state, range)?;
let dy = sketch_var_initial_value(sketch_vars, point[1], exec_state, range)?
- sketch_var_initial_value(sketch_vars, center[1], exec_state, range)?;
Ok(libm::hypot(dx, dy))
}
pub async fn equal_radius(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
#[derive(Debug, Clone, Copy)]
struct RadiusInputVars {
center: [SketchVarId; 2],
start: [SketchVarId; 2],
end: Option<[SketchVarId; 2]>,
}
#[derive(Debug, Clone, Copy)]
enum EqualRadiusInput {
Radius(RadiusInputVars),
}
fn extract_equal_radius_input(
segment_value: &KclValue,
range: crate::SourceRange,
) -> Result<(EqualRadiusInput, ObjectId), KclError> {
let KclValue::Segment { value: segment } = segment_value else {
return Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"equalRadius() arguments must be segments but found {}",
segment_value.human_friendly_type()
),
vec![range],
)));
};
let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"equalRadius() arguments must be unsolved segments".to_owned(),
vec![range],
)));
};
match &unsolved.kind {
UnsolvedSegmentKind::Arc { center, start, end, .. } => {
let (
UnsolvedExpr::Unknown(center_x),
UnsolvedExpr::Unknown(center_y),
UnsolvedExpr::Unknown(start_x),
UnsolvedExpr::Unknown(start_y),
UnsolvedExpr::Unknown(end_x),
UnsolvedExpr::Unknown(end_y),
) = (¢er[0], ¢er[1], &start[0], &start[1], &end[0], &end[1])
else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"arc center/start/end coordinates must be sketch vars for equalRadius()".to_owned(),
vec![range],
)));
};
Ok((
EqualRadiusInput::Radius(RadiusInputVars {
center: [*center_x, *center_y],
start: [*start_x, *start_y],
end: Some([*end_x, *end_y]),
}),
unsolved.object_id,
))
}
UnsolvedSegmentKind::Circle { center, start, .. } => {
let (
UnsolvedExpr::Unknown(center_x),
UnsolvedExpr::Unknown(center_y),
UnsolvedExpr::Unknown(start_x),
UnsolvedExpr::Unknown(start_y),
) = (¢er[0], ¢er[1], &start[0], &start[1])
else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"circle center/start coordinates must be sketch vars for equalRadius()".to_owned(),
vec![range],
)));
};
Ok((
EqualRadiusInput::Radius(RadiusInputVars {
center: [*center_x, *center_y],
start: [*start_x, *start_y],
end: None,
}),
unsolved.object_id,
))
}
other => Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"equalRadius() currently supports only arc and circle segments, you provided {}",
other.human_friendly_kind_with_article()
),
vec![range],
))),
}
}
let input: Vec<KclValue> = args.get_unlabeled_kw_arg(
"input",
&RuntimeType::Array(
Box::new(RuntimeType::Primitive(PrimitiveType::Any)),
ArrayLen::Minimum(2),
),
exec_state,
)?;
let range = args.source_range;
let extracted_input = input
.iter()
.map(|segment_value| extract_equal_radius_input(segment_value, range))
.collect::<Result<Vec<_>, _>>()?;
let radius_inputs: Vec<RadiusInputVars> = extracted_input
.iter()
.map(|(equal_radius_input, _)| match equal_radius_input {
EqualRadiusInput::Radius(radius_input) => *radius_input,
})
.collect();
#[cfg(feature = "artifact-graph")]
let input_object_ids: Vec<ObjectId> = extracted_input.iter().map(|(_, object_id)| *object_id).collect();
let sketch_var_ty = solver_numeric_type(exec_state);
#[cfg(feature = "artifact-graph")]
let constraint_id = exec_state.next_object_id();
let sketch_vars = {
let Some(sketch_state) = exec_state.sketch_block_mut() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"equalRadius() can only be used inside a sketch block".to_owned(),
vec![range],
)));
};
sketch_state.sketch_vars.clone()
};
let radius_initial_value = radius_guess(
&sketch_vars,
radius_inputs[0].center,
radius_inputs[0].start,
exec_state,
range,
)?;
let Some(sketch_state) = exec_state.sketch_block_mut() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"equalRadius() can only be used inside a sketch block".to_owned(),
vec![range],
)));
};
let radius_id = sketch_state.next_sketch_var_id();
sketch_state.sketch_vars.push(KclValue::SketchVar {
value: Box::new(crate::execution::SketchVar {
id: radius_id,
initial_value: radius_initial_value,
ty: sketch_var_ty,
meta: vec![],
}),
});
let radius = DatumDistance::new(radius_id.to_constraint_id(range)?);
for radius_input in radius_inputs {
let center = datum_point(radius_input.center, range)?;
let start = datum_point(radius_input.start, range)?;
sketch_state
.solver_constraints
.push(SolverConstraint::DistanceVar(start, center, radius));
if let Some(end) = radius_input.end {
let end = datum_point(end, range)?;
sketch_state
.solver_constraints
.push(SolverConstraint::DistanceVar(end, center, radius));
}
}
#[cfg(feature = "artifact-graph")]
{
let constraint = crate::front::Constraint::EqualRadius(EqualRadius {
input: input_object_ids,
});
sketch_state.sketch_constraints.push(constraint_id);
track_constraint(constraint_id, constraint, exec_state, &args);
}
Ok(KclValue::none())
}
pub async fn tangent(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
#[derive(Debug, Clone, Copy)]
struct ConstrainableLineVars {
start: [SketchVarId; 2],
end: [SketchVarId; 2],
}
#[derive(Debug, Clone, Copy)]
struct ConstrainableCircularVars {
center: [SketchVarId; 2],
start: [SketchVarId; 2],
end: Option<[SketchVarId; 2]>,
}
#[derive(Debug, Clone, Copy)]
enum TangentInput {
Line(ConstrainableLineVars),
Circular(ConstrainableCircularVars),
}
fn extract_tangent_input(
segment_value: &KclValue,
range: crate::SourceRange,
) -> Result<(TangentInput, ObjectId), KclError> {
let KclValue::Segment { value: segment } = segment_value else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"tangent() arguments must be segments".to_owned(),
vec![range],
)));
};
let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"tangent() arguments must be unsolved segments".to_owned(),
vec![range],
)));
};
match &unsolved.kind {
UnsolvedSegmentKind::Line { start, end, .. } => {
let (
UnsolvedExpr::Unknown(start_x),
UnsolvedExpr::Unknown(start_y),
UnsolvedExpr::Unknown(end_x),
UnsolvedExpr::Unknown(end_y),
) = (&start[0], &start[1], &end[0], &end[1])
else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line coordinates must be sketch vars for tangent()".to_owned(),
vec![range],
)));
};
Ok((
TangentInput::Line(ConstrainableLineVars {
start: [*start_x, *start_y],
end: [*end_x, *end_y],
}),
unsolved.object_id,
))
}
UnsolvedSegmentKind::Arc { center, start, end, .. } => {
let (
UnsolvedExpr::Unknown(center_x),
UnsolvedExpr::Unknown(center_y),
UnsolvedExpr::Unknown(start_x),
UnsolvedExpr::Unknown(start_y),
UnsolvedExpr::Unknown(end_x),
UnsolvedExpr::Unknown(end_y),
) = (¢er[0], ¢er[1], &start[0], &start[1], &end[0], &end[1])
else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"arc center/start/end coordinates must be sketch vars for tangent()".to_owned(),
vec![range],
)));
};
Ok((
TangentInput::Circular(ConstrainableCircularVars {
center: [*center_x, *center_y],
start: [*start_x, *start_y],
end: Some([*end_x, *end_y]),
}),
unsolved.object_id,
))
}
UnsolvedSegmentKind::Circle { center, start, .. } => {
let (
UnsolvedExpr::Unknown(center_x),
UnsolvedExpr::Unknown(center_y),
UnsolvedExpr::Unknown(start_x),
UnsolvedExpr::Unknown(start_y),
) = (¢er[0], ¢er[1], &start[0], &start[1])
else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"circle center/start coordinates must be sketch vars for tangent()".to_owned(),
vec![range],
)));
};
Ok((
TangentInput::Circular(ConstrainableCircularVars {
center: [*center_x, *center_y],
start: [*start_x, *start_y],
end: None,
}),
unsolved.object_id,
))
}
_ => Err(KclError::new_semantic(KclErrorDetails::new(
"tangent() supports only line, arc, and circle segments".to_owned(),
vec![range],
))),
}
}
fn point_initial_position(
sketch_vars: &[KclValue],
point: [SketchVarId; 2],
exec_state: &mut ExecState,
range: crate::SourceRange,
) -> Result<[f64; 2], KclError> {
Ok([
sketch_var_initial_value(sketch_vars, point[0], exec_state, range)?,
sketch_var_initial_value(sketch_vars, point[1], exec_state, range)?,
])
}
fn canonicalize_line_for_tangent(
sketch_vars: &[KclValue],
line: ConstrainableLineVars,
arc_center: [SketchVarId; 2],
exec_state: &mut ExecState,
range: crate::SourceRange,
) -> Result<ConstrainableLineVars, KclError> {
let [sx, sy] = point_initial_position(sketch_vars, line.start, exec_state, range)?;
let [ex, ey] = point_initial_position(sketch_vars, line.end, exec_state, range)?;
let [cx, cy] = point_initial_position(sketch_vars, arc_center, exec_state, range)?;
let signed_side = (ex - sx) * (cy - sy) - (ey - sy) * (cx - sx);
if signed_side < -1e-9 {
Ok(ConstrainableLineVars {
start: line.end,
end: line.start,
})
} else {
Ok(line)
}
}
let input: Vec<KclValue> = args.get_unlabeled_kw_arg(
"input",
&RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
exec_state,
)?;
let [item0, item1]: [KclValue; 2] = input.try_into().map_err(|_| {
KclError::new_semantic(KclErrorDetails::new(
"tangent() requires exactly 2 input segments".to_owned(),
vec![args.source_range],
))
})?;
let range = args.source_range;
let (input0, input0_object_id) = extract_tangent_input(&item0, range)?;
let (input1, input1_object_id) = extract_tangent_input(&item1, range)?;
#[cfg(not(feature = "artifact-graph"))]
let _ = (input0_object_id, input1_object_id);
enum TangentCase {
LineCircular(ConstrainableLineVars, ConstrainableCircularVars),
CircularCircular(ConstrainableCircularVars, ConstrainableCircularVars),
}
let tangent_case = match (input0, input1) {
(TangentInput::Line(line), TangentInput::Circular(circular))
| (TangentInput::Circular(circular), TangentInput::Line(line)) => TangentCase::LineCircular(line, circular),
(TangentInput::Circular(circular0), TangentInput::Circular(circular1)) => {
TangentCase::CircularCircular(circular0, circular1)
}
(TangentInput::Line(_), TangentInput::Line(_)) => {
return Err(KclError::new_semantic(KclErrorDetails::new(
"tangent() does not support Line/Line. Tangency requires at least one circular segment.".to_owned(),
vec![range],
)));
}
};
let sketch_var_ty = solver_numeric_type(exec_state);
#[cfg(feature = "artifact-graph")]
let constraint_id = exec_state.next_object_id();
let sketch_vars = {
let Some(sketch_state) = exec_state.sketch_block_mut() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"tangent() can only be used inside a sketch block".to_owned(),
vec![range],
)));
};
sketch_state.sketch_vars.clone()
};
match tangent_case {
TangentCase::LineCircular(line, circular) => {
let canonical_line = canonicalize_line_for_tangent(&sketch_vars, line, circular.center, exec_state, range)?;
let line_p0 = datum_point(canonical_line.start, range)?;
let line_p1 = datum_point(canonical_line.end, range)?;
let line_datum = DatumLineSegment::new(line_p0, line_p1);
let center = datum_point(circular.center, range)?;
let circular_start = datum_point(circular.start, range)?;
let circular_end = circular.end.map(|end| datum_point(end, range)).transpose()?;
let radius_initial_value = radius_guess(&sketch_vars, circular.center, circular.start, exec_state, range)?;
let Some(sketch_state) = exec_state.sketch_block_mut() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"tangent() can only be used inside a sketch block".to_owned(),
vec![range],
)));
};
let radius_id = sketch_state.next_sketch_var_id();
sketch_state.sketch_vars.push(KclValue::SketchVar {
value: Box::new(crate::execution::SketchVar {
id: radius_id,
initial_value: radius_initial_value,
ty: sketch_var_ty,
meta: vec![],
}),
});
let radius = DatumDistance::new(radius_id.to_constraint_id(range)?);
let circle = DatumCircle { center, radius };
sketch_state
.solver_constraints
.push(SolverConstraint::DistanceVar(circular_start, center, radius));
if let Some(circular_end) = circular_end {
sketch_state
.solver_constraints
.push(SolverConstraint::DistanceVar(circular_end, center, radius));
}
sketch_state
.solver_constraints
.push(SolverConstraint::LineTangentToCircle(line_datum, circle));
}
TangentCase::CircularCircular(circular0, circular1) => {
let center0 = datum_point(circular0.center, range)?;
let start0 = datum_point(circular0.start, range)?;
let end0 = circular0.end.map(|end| datum_point(end, range)).transpose()?;
let radius0_initial_value =
radius_guess(&sketch_vars, circular0.center, circular0.start, exec_state, range)?;
let center1 = datum_point(circular1.center, range)?;
let start1 = datum_point(circular1.start, range)?;
let end1 = circular1.end.map(|end| datum_point(end, range)).transpose()?;
let radius1_initial_value =
radius_guess(&sketch_vars, circular1.center, circular1.start, exec_state, range)?;
let Some(sketch_state) = exec_state.sketch_block_mut() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"tangent() can only be used inside a sketch block".to_owned(),
vec![range],
)));
};
let radius0_id = sketch_state.next_sketch_var_id();
sketch_state.sketch_vars.push(KclValue::SketchVar {
value: Box::new(crate::execution::SketchVar {
id: radius0_id,
initial_value: radius0_initial_value,
ty: sketch_var_ty,
meta: vec![],
}),
});
let radius0 = DatumDistance::new(radius0_id.to_constraint_id(range)?);
let circle0 = DatumCircle {
center: center0,
radius: radius0,
};
let radius1_id = sketch_state.next_sketch_var_id();
sketch_state.sketch_vars.push(KclValue::SketchVar {
value: Box::new(crate::execution::SketchVar {
id: radius1_id,
initial_value: radius1_initial_value,
ty: sketch_var_ty,
meta: vec![],
}),
});
let radius1 = DatumDistance::new(radius1_id.to_constraint_id(range)?);
let circle1 = DatumCircle {
center: center1,
radius: radius1,
};
sketch_state
.solver_constraints
.push(SolverConstraint::DistanceVar(start0, center0, radius0));
if let Some(end0) = end0 {
sketch_state
.solver_constraints
.push(SolverConstraint::DistanceVar(end0, center0, radius0));
}
sketch_state
.solver_constraints
.push(SolverConstraint::DistanceVar(start1, center1, radius1));
if let Some(end1) = end1 {
sketch_state
.solver_constraints
.push(SolverConstraint::DistanceVar(end1, center1, radius1));
}
sketch_state
.solver_constraints
.push(SolverConstraint::CircleTangentToCircle(circle0, circle1));
}
}
#[cfg(feature = "artifact-graph")]
{
let constraint = crate::front::Constraint::Tangent(Tangent {
input: vec![input0_object_id, input1_object_id],
});
let Some(sketch_state) = exec_state.sketch_block_mut() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"tangent() can only be used inside a sketch block".to_owned(),
vec![range],
)));
};
sketch_state.sketch_constraints.push(constraint_id);
track_constraint(constraint_id, constraint, exec_state, &args);
}
Ok(KclValue::none())
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum LinesAtAngleKind {
Parallel,
Perpendicular,
}
impl LinesAtAngleKind {
pub fn to_function_name(self) -> &'static str {
match self {
LinesAtAngleKind::Parallel => "parallel",
LinesAtAngleKind::Perpendicular => "perpendicular",
}
}
fn to_solver_angle(self) -> ezpz::datatypes::AngleKind {
match self {
LinesAtAngleKind::Parallel => ezpz::datatypes::AngleKind::Parallel,
LinesAtAngleKind::Perpendicular => ezpz::datatypes::AngleKind::Perpendicular,
}
}
#[cfg(feature = "artifact-graph")]
fn constraint(&self, lines: Vec<ObjectId>) -> Constraint {
match self {
LinesAtAngleKind::Parallel => Constraint::Parallel(Parallel { lines }),
LinesAtAngleKind::Perpendicular => Constraint::Perpendicular(Perpendicular { lines }),
}
}
}
#[expect(unused)]
fn into_kcmc_angle(angle: ezpz::datatypes::Angle) -> kcmc::shared::Angle {
kcmc::shared::Angle::from_degrees(angle.to_degrees())
}
#[expect(unused)]
fn into_ezpz_angle(angle: kcmc::shared::Angle) -> ezpz::datatypes::Angle {
ezpz::datatypes::Angle::from_degrees(angle.to_degrees())
}
pub async fn parallel(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
lines_at_angle(LinesAtAngleKind::Parallel, exec_state, args).await
}
pub async fn perpendicular(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
lines_at_angle(LinesAtAngleKind::Perpendicular, exec_state, args).await
}
pub async fn angle(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
"lines",
&RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
exec_state,
)?;
let [line0, line1]: [KclValue; 2] = lines.try_into().map_err(|_| {
KclError::new_semantic(KclErrorDetails::new(
"must have two input lines".to_owned(),
vec![args.source_range],
))
})?;
let KclValue::Segment { value: segment0 } = &line0 else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line argument must be a Segment".to_owned(),
vec![args.source_range],
)));
};
let SegmentRepr::Unsolved { segment: unsolved0 } = &segment0.repr else {
return Err(KclError::new_internal(KclErrorDetails::new(
"line must be an unsolved Segment".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedSegmentKind::Line {
start: start0,
end: end0,
..
} = &unsolved0.kind
else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line argument must be a line, no other type of Segment".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedExpr::Unknown(line0_p0_x) = &start0[0] else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line's start x coordinate must be a var".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedExpr::Unknown(line0_p0_y) = &start0[1] else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line's start y coordinate must be a var".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedExpr::Unknown(line0_p1_x) = &end0[0] else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line's end x coordinate must be a var".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedExpr::Unknown(line0_p1_y) = &end0[1] else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line's end y coordinate must be a var".to_owned(),
vec![args.source_range],
)));
};
let KclValue::Segment { value: segment1 } = &line1 else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line argument must be a Segment".to_owned(),
vec![args.source_range],
)));
};
let SegmentRepr::Unsolved { segment: unsolved1 } = &segment1.repr else {
return Err(KclError::new_internal(KclErrorDetails::new(
"line must be an unsolved Segment".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedSegmentKind::Line {
start: start1,
end: end1,
..
} = &unsolved1.kind
else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line argument must be a line, no other type of Segment".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedExpr::Unknown(line1_p0_x) = &start1[0] else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line's start x coordinate must be a var".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedExpr::Unknown(line1_p0_y) = &start1[1] else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line's start y coordinate must be a var".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedExpr::Unknown(line1_p1_x) = &end1[0] else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line's end x coordinate must be a var".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedExpr::Unknown(line1_p1_y) = &end1[1] else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line's end y coordinate must be a var".to_owned(),
vec![args.source_range],
)));
};
let sketch_constraint = SketchConstraint {
kind: SketchConstraintKind::Angle {
line0: crate::execution::ConstrainableLine2d {
object_id: unsolved0.object_id,
vars: [
crate::front::Point2d {
x: *line0_p0_x,
y: *line0_p0_y,
},
crate::front::Point2d {
x: *line0_p1_x,
y: *line0_p1_y,
},
],
},
line1: crate::execution::ConstrainableLine2d {
object_id: unsolved1.object_id,
vars: [
crate::front::Point2d {
x: *line1_p0_x,
y: *line1_p0_y,
},
crate::front::Point2d {
x: *line1_p1_x,
y: *line1_p1_y,
},
],
},
},
meta: vec![args.source_range.into()],
};
Ok(KclValue::SketchConstraint {
value: Box::new(sketch_constraint),
})
}
async fn lines_at_angle(
angle_kind: LinesAtAngleKind,
exec_state: &mut ExecState,
args: Args,
) -> Result<KclValue, KclError> {
let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
"lines",
&RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
exec_state,
)?;
let [line0, line1]: [KclValue; 2] = lines.try_into().map_err(|_| {
KclError::new_semantic(KclErrorDetails::new(
"must have two input lines".to_owned(),
vec![args.source_range],
))
})?;
let KclValue::Segment { value: segment0 } = &line0 else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line argument must be a Segment".to_owned(),
vec![args.source_range],
)));
};
let SegmentRepr::Unsolved { segment: unsolved0 } = &segment0.repr else {
return Err(KclError::new_internal(KclErrorDetails::new(
"line must be an unsolved Segment".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedSegmentKind::Line {
start: start0,
end: end0,
..
} = &unsolved0.kind
else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line argument must be a line, no other type of Segment".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedExpr::Unknown(line0_p0_x) = &start0[0] else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line's start x coordinate must be a var".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedExpr::Unknown(line0_p0_y) = &start0[1] else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line's start y coordinate must be a var".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedExpr::Unknown(line0_p1_x) = &end0[0] else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line's end x coordinate must be a var".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedExpr::Unknown(line0_p1_y) = &end0[1] else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line's end y coordinate must be a var".to_owned(),
vec![args.source_range],
)));
};
let KclValue::Segment { value: segment1 } = &line1 else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line argument must be a Segment".to_owned(),
vec![args.source_range],
)));
};
let SegmentRepr::Unsolved { segment: unsolved1 } = &segment1.repr else {
return Err(KclError::new_internal(KclErrorDetails::new(
"line must be an unsolved Segment".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedSegmentKind::Line {
start: start1,
end: end1,
..
} = &unsolved1.kind
else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line argument must be a line, no other type of Segment".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedExpr::Unknown(line1_p0_x) = &start1[0] else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line's start x coordinate must be a var".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedExpr::Unknown(line1_p0_y) = &start1[1] else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line's start y coordinate must be a var".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedExpr::Unknown(line1_p1_x) = &end1[0] else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line's end x coordinate must be a var".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedExpr::Unknown(line1_p1_y) = &end1[1] else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line's end y coordinate must be a var".to_owned(),
vec![args.source_range],
)));
};
let range = args.source_range;
let solver_line0_p0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
line0_p0_x.to_constraint_id(range)?,
line0_p0_y.to_constraint_id(range)?,
);
let solver_line0_p1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
line0_p1_x.to_constraint_id(range)?,
line0_p1_y.to_constraint_id(range)?,
);
let solver_line0 = ezpz::datatypes::inputs::DatumLineSegment::new(solver_line0_p0, solver_line0_p1);
let solver_line1_p0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
line1_p0_x.to_constraint_id(range)?,
line1_p0_y.to_constraint_id(range)?,
);
let solver_line1_p1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
line1_p1_x.to_constraint_id(range)?,
line1_p1_y.to_constraint_id(range)?,
);
let solver_line1 = ezpz::datatypes::inputs::DatumLineSegment::new(solver_line1_p0, solver_line1_p1);
let constraint = SolverConstraint::LinesAtAngle(solver_line0, solver_line1, angle_kind.to_solver_angle());
#[cfg(feature = "artifact-graph")]
let constraint_id = exec_state.next_object_id();
let Some(sketch_state) = exec_state.sketch_block_mut() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"{}() can only be used inside a sketch block",
angle_kind.to_function_name()
),
vec![args.source_range],
)));
};
sketch_state.solver_constraints.push(constraint);
#[cfg(feature = "artifact-graph")]
{
let constraint = angle_kind.constraint(vec![unsolved0.object_id, unsolved1.object_id]);
sketch_state.sketch_constraints.push(constraint_id);
track_constraint(constraint_id, constraint, exec_state, &args);
}
Ok(KclValue::none())
}
pub async fn horizontal(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let line: KclValue = args.get_unlabeled_kw_arg("line", &RuntimeType::segment(), exec_state)?;
let KclValue::Segment { value: segment } = line else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line argument must be a Segment".to_owned(),
vec![args.source_range],
)));
};
let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
return Err(KclError::new_internal(KclErrorDetails::new(
"line must be an unsolved Segment".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line argument must be a line, no other type of Segment".to_owned(),
vec![args.source_range],
)));
};
let p0_x = &start[0];
let p0_y = &start[1];
let p1_x = &end[0];
let p1_y = &end[1];
match (p0_x, p0_y, p1_x, p1_y) {
(
UnsolvedExpr::Unknown(p0_x),
UnsolvedExpr::Unknown(p0_y),
UnsolvedExpr::Unknown(p1_x),
UnsolvedExpr::Unknown(p1_y),
) => {
let range = args.source_range;
let solver_p0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
p0_x.to_constraint_id(range)?,
p0_y.to_constraint_id(range)?,
);
let solver_p1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
p1_x.to_constraint_id(range)?,
p1_y.to_constraint_id(range)?,
);
let solver_line = ezpz::datatypes::inputs::DatumLineSegment::new(solver_p0, solver_p1);
let constraint = ezpz::Constraint::Horizontal(solver_line);
#[cfg(feature = "artifact-graph")]
let constraint_id = exec_state.next_object_id();
let Some(sketch_state) = exec_state.sketch_block_mut() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"horizontal() can only be used inside a sketch block".to_owned(),
vec![args.source_range],
)));
};
sketch_state.solver_constraints.push(constraint);
#[cfg(feature = "artifact-graph")]
{
let constraint = crate::front::Constraint::Horizontal(Horizontal {
line: unsolved.object_id,
});
sketch_state.sketch_constraints.push(constraint_id);
track_constraint(constraint_id, constraint, exec_state, &args);
}
Ok(KclValue::none())
}
_ => Err(KclError::new_semantic(KclErrorDetails::new(
"line's x and y coordinates of both start and end must be vars".to_owned(),
vec![args.source_range],
))),
}
}
pub async fn vertical(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let line: KclValue = args.get_unlabeled_kw_arg("line", &RuntimeType::segment(), exec_state)?;
let KclValue::Segment { value: segment } = line else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line argument must be a Segment".to_owned(),
vec![args.source_range],
)));
};
let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
return Err(KclError::new_internal(KclErrorDetails::new(
"line must be an unsolved Segment".to_owned(),
vec![args.source_range],
)));
};
let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"line argument must be a line, no other type of Segment".to_owned(),
vec![args.source_range],
)));
};
let p0_x = &start[0];
let p0_y = &start[1];
let p1_x = &end[0];
let p1_y = &end[1];
match (p0_x, p0_y, p1_x, p1_y) {
(
UnsolvedExpr::Unknown(p0_x),
UnsolvedExpr::Unknown(p0_y),
UnsolvedExpr::Unknown(p1_x),
UnsolvedExpr::Unknown(p1_y),
) => {
let range = args.source_range;
let solver_p0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
p0_x.to_constraint_id(range)?,
p0_y.to_constraint_id(range)?,
);
let solver_p1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
p1_x.to_constraint_id(range)?,
p1_y.to_constraint_id(range)?,
);
let solver_line = ezpz::datatypes::inputs::DatumLineSegment::new(solver_p0, solver_p1);
let constraint = ezpz::Constraint::Vertical(solver_line);
#[cfg(feature = "artifact-graph")]
let constraint_id = exec_state.next_object_id();
let Some(sketch_state) = exec_state.sketch_block_mut() else {
return Err(KclError::new_semantic(KclErrorDetails::new(
"vertical() can only be used inside a sketch block".to_owned(),
vec![args.source_range],
)));
};
sketch_state.solver_constraints.push(constraint);
#[cfg(feature = "artifact-graph")]
{
let constraint = crate::front::Constraint::Vertical(Vertical {
line: unsolved.object_id,
});
sketch_state.sketch_constraints.push(constraint_id);
track_constraint(constraint_id, constraint, exec_state, &args);
}
Ok(KclValue::none())
}
_ => Err(KclError::new_semantic(KclErrorDetails::new(
"line's x and y coordinates of both start and end must be vars".to_owned(),
vec![args.source_range],
))),
}
}