use std::collections::HashMap;
use std::sync::Arc;
use ahash::AHashSet;
use ezpz::Warning;
use indexmap::IndexMap;
use kcl_error::SourceRange;
use kittycad_modeling_cmds::units::UnitLength;
use uuid::Uuid;
use crate::ExecState;
use crate::KclError;
use crate::errors::KclErrorDetails;
use crate::exec::KclValue;
use crate::exec::NumericType;
use crate::exec::Sketch;
use crate::exec::UnitType;
use crate::execution::AbstractSegment;
use crate::execution::Metadata;
use crate::execution::Segment;
use crate::execution::SegmentKind;
use crate::execution::SegmentRepr;
use crate::execution::SketchSurface;
use crate::execution::UnsolvedExpr;
use crate::execution::UnsolvedSegment;
use crate::execution::UnsolvedSegmentKind;
use crate::execution::types::PrimitiveType;
use crate::execution::types::RuntimeType;
use crate::front::Freedom;
use crate::front::Object;
use crate::front::ObjectKind;
use crate::std::args::TyF64;
pub(super) struct FreedomAnalysis {
pub underconstrained: AHashSet<u32>,
pub num_constraints: usize,
}
impl FreedomAnalysis {
pub(super) fn from_ezpz_analysis(analysis: ezpz::FreedomAnalysis, num_constraints: usize) -> Self {
let underconstrained_vec: Vec<u32> = analysis.into_underconstrained();
let underconstrained = AHashSet::from_iter(underconstrained_vec.iter().copied());
FreedomAnalysis {
underconstrained,
num_constraints,
}
}
}
fn solver_unit(exec_state: &ExecState) -> UnitLength {
exec_state.length_unit()
}
pub(crate) fn solver_numeric_type(exec_state: &ExecState) -> NumericType {
NumericType::Known(UnitType::Length(solver_unit(exec_state)))
}
pub(crate) fn normalize_to_solver_distance_unit(
value: &KclValue,
source_range: SourceRange,
exec_state: &mut ExecState,
description: &str,
) -> Result<KclValue, KclError> {
let length_ty = RuntimeType::Primitive(PrimitiveType::Number(solver_numeric_type(exec_state)));
value.coerce(&length_ty, true, exec_state).map_err(|_| {
KclError::new_semantic(KclErrorDetails::new(
format!(
"{} must be a length coercible to the module length unit {}, but found {}",
description,
length_ty.human_friendly_type(),
value.human_friendly_type(),
),
vec![source_range],
))
})
}
pub(crate) fn normalize_to_solver_angle_unit(
value: &KclValue,
source_range: SourceRange,
exec_state: &mut ExecState,
description: &str,
) -> Result<KclValue, KclError> {
let angle_ty = RuntimeType::angle();
value.coerce(&angle_ty, true, exec_state).map_err(|_| {
KclError::new_semantic(KclErrorDetails::new(
format!(
"{} must be coercible to an angle unit {}, but found {}",
description,
angle_ty.human_friendly_type(),
value.human_friendly_type(),
),
vec![source_range],
))
})
}
pub(super) fn substitute_sketch_vars(
variables: IndexMap<String, KclValue>,
surface: &SketchSurface,
sketch_id: Uuid,
sketch: Option<&Sketch>,
solve_outcome: &Solved,
solution_ty: NumericType,
analysis: Option<&FreedomAnalysis>,
) -> Result<HashMap<String, KclValue>, KclError> {
let sketch = sketch.cloned().map(Arc::new);
let mut subbed = HashMap::with_capacity(variables.len());
for (name, value) in variables {
let subbed_value = substitute_sketch_var(
value,
surface,
sketch_id,
sketch.as_ref(),
solve_outcome,
solution_ty,
analysis,
)?;
subbed.insert(name, subbed_value);
}
Ok(subbed)
}
fn substitute_sketch_var(
value: KclValue,
surface: &SketchSurface,
sketch_id: Uuid,
sketch: Option<&Arc<Sketch>>,
solve_outcome: &Solved,
solution_ty: NumericType,
analysis: Option<&FreedomAnalysis>,
) -> Result<KclValue, KclError> {
match value {
KclValue::Uuid { .. } => Ok(value),
KclValue::Bool { .. } => Ok(value),
KclValue::Number { .. } => Ok(value),
KclValue::String { .. } => Ok(value),
KclValue::SketchVar { value: var } => {
let Some(solution) = solve_outcome.final_values.get(var.id.0) else {
let message = format!("No solution for sketch variable with id {}", var.id.0);
debug_assert!(false, "{}", &message);
return Err(KclError::new_internal(KclErrorDetails::new(
message,
var.meta.into_iter().map(|m| m.source_range).collect(),
)));
};
Ok(KclValue::Number {
value: *solution,
ty: solution_ty,
meta: var.meta.clone(),
})
}
KclValue::SketchConstraint { .. } => {
debug_assert!(false, "Sketch constraints should not appear in substituted values");
Ok(value)
}
KclValue::Tuple { value, meta } => {
let subbed = value
.into_iter()
.map(|v| substitute_sketch_var(v, surface, sketch_id, sketch, solve_outcome, solution_ty, analysis))
.collect::<Result<Vec<_>, KclError>>()?;
Ok(KclValue::Tuple { value: subbed, meta })
}
KclValue::HomArray { value, ty } => {
let subbed = value
.into_iter()
.map(|v| substitute_sketch_var(v, surface, sketch_id, sketch, solve_outcome, solution_ty, analysis))
.collect::<Result<Vec<_>, KclError>>()?;
Ok(KclValue::HomArray { value: subbed, ty })
}
KclValue::Object {
value,
constrainable,
meta,
} => {
let subbed = value
.into_iter()
.map(|(k, v)| {
substitute_sketch_var(v, surface, sketch_id, sketch, solve_outcome, solution_ty, analysis)
.map(|v| (k, v))
})
.collect::<Result<HashMap<_, _>, KclError>>()?;
Ok(KclValue::Object {
value: subbed,
constrainable,
meta,
})
}
KclValue::TagIdentifier(_) => Ok(value),
KclValue::TagDeclarator(_) => Ok(value),
KclValue::GdtAnnotation { .. } => Ok(value),
KclValue::Plane { .. } => Ok(value),
KclValue::Face { .. } => Ok(value),
KclValue::Segment {
value: abstract_segment,
} => match abstract_segment.repr {
SegmentRepr::Unsolved { segment } => {
let subbed = substitute_sketch_var_in_segment(
*segment,
surface,
sketch_id,
sketch.cloned(),
solve_outcome,
solution_ty,
analysis,
)?;
Ok(KclValue::Segment {
value: Box::new(AbstractSegment {
repr: SegmentRepr::Solved {
segment: Box::new(subbed),
},
meta: abstract_segment.meta,
}),
})
}
SegmentRepr::Solved { .. } => Ok(KclValue::Segment {
value: abstract_segment,
}),
},
KclValue::Sketch { .. } => Ok(value),
KclValue::Solid { .. } => Ok(value),
KclValue::Helix { .. } => Ok(value),
KclValue::ImportedGeometry(_) => Ok(value),
KclValue::Function { .. } => Ok(value),
KclValue::Module { .. } => Ok(value),
KclValue::Type { .. } => Ok(value),
KclValue::KclNone { .. } => Ok(value),
KclValue::BoundedEdge { .. } => Ok(value),
}
}
pub(super) fn substitute_sketch_var_in_segment(
segment: UnsolvedSegment,
surface: &SketchSurface,
sketch_id: Uuid,
sketch: Option<Arc<Sketch>>,
solve_outcome: &Solved,
solution_ty: NumericType,
analysis: Option<&FreedomAnalysis>,
) -> Result<Segment, KclError> {
let srs = segment.meta.iter().map(|m| m.source_range).collect::<Vec<_>>();
match &segment.kind {
UnsolvedSegmentKind::Point { position, ctor } => {
let (position_x, position_x_freedom) =
substitute_sketch_var_in_unsolved_expr(&position[0], solve_outcome, solution_ty, analysis, &srs)?;
let (position_y, position_y_freedom) =
substitute_sketch_var_in_unsolved_expr(&position[1], solve_outcome, solution_ty, analysis, &srs)?;
let position = [position_x, position_y];
Ok(Segment {
id: segment.id,
object_id: segment.object_id,
kind: SegmentKind::Point {
position,
ctor: ctor.clone(),
freedom: point_freedom(position_x_freedom, position_y_freedom),
},
surface: surface.clone(),
sketch_id,
sketch,
tag: segment.tag,
node_path: segment.node_path,
meta: segment.meta,
})
}
UnsolvedSegmentKind::Line {
start,
end,
ctor,
start_object_id,
end_object_id,
construction,
} => {
let (start_x, start_x_freedom) =
substitute_sketch_var_in_unsolved_expr(&start[0], solve_outcome, solution_ty, analysis, &srs)?;
let (start_y, start_y_freedom) =
substitute_sketch_var_in_unsolved_expr(&start[1], solve_outcome, solution_ty, analysis, &srs)?;
let (end_x, end_x_freedom) =
substitute_sketch_var_in_unsolved_expr(&end[0], solve_outcome, solution_ty, analysis, &srs)?;
let (end_y, end_y_freedom) =
substitute_sketch_var_in_unsolved_expr(&end[1], solve_outcome, solution_ty, analysis, &srs)?;
let start = [start_x, start_y];
let end = [end_x, end_y];
Ok(Segment {
id: segment.id,
object_id: segment.object_id,
kind: SegmentKind::Line {
start,
end,
ctor: ctor.clone(),
start_object_id: *start_object_id,
end_object_id: *end_object_id,
start_freedom: point_freedom(start_x_freedom, start_y_freedom),
end_freedom: point_freedom(end_x_freedom, end_y_freedom),
construction: *construction,
},
surface: surface.clone(),
sketch_id,
sketch,
tag: segment.tag,
node_path: segment.node_path,
meta: segment.meta,
})
}
UnsolvedSegmentKind::Arc {
start,
end,
center,
ctor,
start_object_id,
end_object_id,
center_object_id,
construction,
} => {
let (start_x, start_x_freedom) =
substitute_sketch_var_in_unsolved_expr(&start[0], solve_outcome, solution_ty, analysis, &srs)?;
let (start_y, start_y_freedom) =
substitute_sketch_var_in_unsolved_expr(&start[1], solve_outcome, solution_ty, analysis, &srs)?;
let (end_x, end_x_freedom) =
substitute_sketch_var_in_unsolved_expr(&end[0], solve_outcome, solution_ty, analysis, &srs)?;
let (end_y, end_y_freedom) =
substitute_sketch_var_in_unsolved_expr(&end[1], solve_outcome, solution_ty, analysis, &srs)?;
let (center_x, center_x_freedom) =
substitute_sketch_var_in_unsolved_expr(¢er[0], solve_outcome, solution_ty, analysis, &srs)?;
let (center_y, center_y_freedom) =
substitute_sketch_var_in_unsolved_expr(¢er[1], solve_outcome, solution_ty, analysis, &srs)?;
let start = [start_x, start_y];
let end = [end_x, end_y];
let center = [center_x, center_y];
Ok(Segment {
id: segment.id,
object_id: segment.object_id,
kind: SegmentKind::Arc {
start,
end,
center,
ctor: ctor.clone(),
start_object_id: *start_object_id,
end_object_id: *end_object_id,
center_object_id: *center_object_id,
start_freedom: point_freedom(start_x_freedom, start_y_freedom),
end_freedom: point_freedom(end_x_freedom, end_y_freedom),
center_freedom: point_freedom(center_x_freedom, center_y_freedom),
construction: *construction,
},
surface: surface.clone(),
sketch_id,
sketch,
tag: segment.tag,
node_path: segment.node_path,
meta: segment.meta,
})
}
UnsolvedSegmentKind::Circle {
start,
center,
ctor,
start_object_id,
center_object_id,
construction,
} => {
let (start_x, start_x_freedom) =
substitute_sketch_var_in_unsolved_expr(&start[0], solve_outcome, solution_ty, analysis, &srs)?;
let (start_y, start_y_freedom) =
substitute_sketch_var_in_unsolved_expr(&start[1], solve_outcome, solution_ty, analysis, &srs)?;
let (center_x, center_x_freedom) =
substitute_sketch_var_in_unsolved_expr(¢er[0], solve_outcome, solution_ty, analysis, &srs)?;
let (center_y, center_y_freedom) =
substitute_sketch_var_in_unsolved_expr(¢er[1], solve_outcome, solution_ty, analysis, &srs)?;
let start = [start_x, start_y];
let center = [center_x, center_y];
Ok(Segment {
id: segment.id,
object_id: segment.object_id,
kind: SegmentKind::Circle {
start,
center,
ctor: ctor.clone(),
start_object_id: *start_object_id,
center_object_id: *center_object_id,
start_freedom: point_freedom(start_x_freedom, start_y_freedom),
center_freedom: point_freedom(center_x_freedom, center_y_freedom),
construction: *construction,
},
surface: surface.clone(),
sketch_id,
sketch,
tag: segment.tag,
node_path: segment.node_path,
meta: segment.meta,
})
}
UnsolvedSegmentKind::ControlPointSpline {
controls,
ctor,
control_object_ids,
control_polygon_edge_object_ids,
degree,
construction,
} => {
let mut solved_controls = Vec::with_capacity(controls.len());
let mut control_freedoms = Vec::with_capacity(controls.len());
for control in controls {
let (x, x_freedom) =
substitute_sketch_var_in_unsolved_expr(&control[0], solve_outcome, solution_ty, analysis, &srs)?;
let (y, y_freedom) =
substitute_sketch_var_in_unsolved_expr(&control[1], solve_outcome, solution_ty, analysis, &srs)?;
solved_controls.push([x, y]);
control_freedoms.push(point_freedom(x_freedom, y_freedom));
}
Ok(Segment {
id: segment.id,
object_id: segment.object_id,
kind: SegmentKind::ControlPointSpline {
controls: solved_controls,
ctor: ctor.clone(),
control_object_ids: control_object_ids.clone(),
control_polygon_edge_object_ids: control_polygon_edge_object_ids.clone(),
control_freedoms,
degree: *degree,
construction: *construction,
},
surface: surface.clone(),
sketch_id,
sketch,
tag: segment.tag,
node_path: segment.node_path,
meta: segment.meta,
})
}
}
}
fn substitute_sketch_var_in_unsolved_expr(
unsolved_expr: &UnsolvedExpr,
solve_outcome: &Solved,
solution_ty: NumericType,
analysis: Option<&FreedomAnalysis>,
source_ranges: &[SourceRange],
) -> Result<(TyF64, Option<Freedom>), KclError> {
match unsolved_expr {
UnsolvedExpr::Known(n) => Ok((n.clone(), Some(Freedom::Fixed))),
UnsolvedExpr::Unknown(var_id) => {
let Some(solution) = solve_outcome.final_values.get(var_id.0) else {
let message = format!("No solution for sketch variable with id {}", var_id.0);
debug_assert!(false, "{}", &message);
return Err(KclError::new_internal(KclErrorDetails::new(
message,
source_ranges.to_vec(),
)));
};
let freedom = if solve_outcome.variables_in_conflicts.contains(&(var_id.0 as ezpz::Id)) {
Some(Freedom::Conflict)
} else if let Some(analysis) = analysis {
let solver_var_id = var_id.to_constraint_id(source_ranges.first().copied().unwrap_or_default())?;
let is_underconstrained = if analysis.num_constraints == 0 {
true
} else {
analysis.underconstrained.contains(&solver_var_id)
};
if is_underconstrained {
Some(Freedom::Free)
} else {
Some(Freedom::Fixed)
}
} else {
None
};
Ok((TyF64::new(*solution, solution_ty), freedom))
}
}
}
pub(crate) struct Solved {
pub(crate) final_values: Vec<f64>,
#[expect(dead_code, reason = "ezpz provides this info, but we aren't using it yet")]
pub(crate) iterations: usize,
pub(crate) warnings: Vec<Warning>,
#[expect(dead_code, reason = "ezpz provides this info, but we aren't using it yet")]
pub(crate) priority_solved: u32,
pub(crate) variables_in_conflicts: AHashSet<ezpz::Id>,
}
impl Solved {
pub(crate) fn from_ezpz_outcome(
value: ezpz::SolveOutcome,
constraints: &[ezpz::Constraint],
num_required_constraints: usize,
) -> Self {
let mut variables_in_conflicts = AHashSet::new();
for &constraint_idx in value.unsatisfied() {
if constraint_idx < num_required_constraints
&& let Some(constraint) = constraints.get(constraint_idx)
{
constraint.extend_associated_variable_ids(&mut variables_in_conflicts);
}
}
Self {
final_values: value.final_values().to_owned(),
iterations: value.iterations(),
warnings: value.warnings().to_owned(),
priority_solved: value.priority_solved(),
variables_in_conflicts,
}
}
}
fn point_freedom(x: Option<Freedom>, y: Option<Freedom>) -> Option<Freedom> {
match (x, y) {
(Some(x), Some(y)) => Some(x.merge(y)),
(Some(f), None) | (None, Some(f)) => match f {
Freedom::Fixed => None,
Freedom::Conflict | Freedom::Free => Some(f),
},
(None, None) => None,
}
}
pub(super) fn create_segment_scene_objects(
segments: &[Segment],
sketch_block_range: SourceRange,
exec_state: &mut ExecState,
) -> Result<Vec<Object>, KclError> {
let mut scene_objects = Vec::with_capacity(segments.len());
for segment in segments {
let source = Metadata::to_source_ref(&segment.meta, segment.node_path.clone());
match &segment.kind {
SegmentKind::Point {
position,
ctor,
freedom,
} => {
let final_freedom = freedom.unwrap_or(Freedom::Free);
let point2d = TyF64::to_point2d(position).map_err(|_| {
KclError::new_internal(KclErrorDetails::new(
format!("Error converting start point runtime type to API value: {:?}", position),
vec![sketch_block_range],
))
})?;
let artifact_id = exec_state.next_artifact_id();
let point_object = Object {
id: segment.object_id,
kind: ObjectKind::Segment {
segment: crate::front::Segment::Point(crate::front::Point {
position: point2d.clone(),
ctor: Some(crate::front::PointCtor {
position: ctor.position.clone(),
}),
owner: None,
freedom: final_freedom,
constraints: Vec::new(),
}),
},
label: Default::default(),
comments: Default::default(),
artifact_id,
source: source.clone(),
};
scene_objects.push(point_object);
}
SegmentKind::Line {
start,
end,
ctor,
start_object_id,
end_object_id,
start_freedom,
end_freedom,
construction,
} => {
let start_final_freedom = start_freedom.unwrap_or(Freedom::Free);
let end_final_freedom = end_freedom.unwrap_or(Freedom::Free);
let start_point2d = TyF64::to_point2d(start).map_err(|_| {
KclError::new_internal(KclErrorDetails::new(
format!("Error converting start point runtime type to API value: {:?}", start),
vec![sketch_block_range],
))
})?;
let start_artifact_id = exec_state.next_artifact_id();
let start_point_object = Object {
id: *start_object_id,
kind: ObjectKind::Segment {
segment: crate::front::Segment::Point(crate::front::Point {
position: start_point2d.clone(),
ctor: None,
owner: Some(segment.object_id),
freedom: start_final_freedom,
constraints: Vec::new(),
}),
},
label: Default::default(),
comments: Default::default(),
artifact_id: start_artifact_id,
source: source.clone(),
};
let start_point_object_id = start_point_object.id;
scene_objects.push(start_point_object);
let end_point2d = TyF64::to_point2d(end).map_err(|_| {
KclError::new_internal(KclErrorDetails::new(
format!("Error converting end point runtime type to API value: {:?}", end),
vec![sketch_block_range],
))
})?;
let end_artifact_id = exec_state.next_artifact_id();
let end_point_object = Object {
id: *end_object_id,
kind: ObjectKind::Segment {
segment: crate::front::Segment::Point(crate::front::Point {
position: end_point2d.clone(),
ctor: None,
owner: Some(segment.object_id),
freedom: end_final_freedom,
constraints: Vec::new(),
}),
},
label: Default::default(),
comments: Default::default(),
artifact_id: end_artifact_id,
source: source.clone(),
};
let end_point_object_id = end_point_object.id;
scene_objects.push(end_point_object);
let line_artifact_id = exec_state.next_artifact_id();
let segment_object = Object {
id: segment.object_id,
kind: ObjectKind::Segment {
segment: crate::front::Segment::Line(crate::front::Line {
start: start_point_object_id,
end: end_point_object_id,
owner: None,
ctor: crate::front::SegmentCtor::Line(ctor.as_ref().clone()),
ctor_applicable: true,
construction: *construction,
}),
},
label: Default::default(),
comments: Default::default(),
artifact_id: line_artifact_id,
source,
};
scene_objects.push(segment_object);
}
SegmentKind::Arc {
start,
end,
center,
ctor,
start_object_id,
end_object_id,
center_object_id,
start_freedom,
end_freedom,
center_freedom,
construction,
} => {
let start_final_freedom = start_freedom.unwrap_or(Freedom::Free);
let end_final_freedom = end_freedom.unwrap_or(Freedom::Free);
let center_final_freedom = center_freedom.unwrap_or(Freedom::Free);
let start_point2d = TyF64::to_point2d(start).map_err(|_| {
KclError::new_internal(KclErrorDetails::new(
format!("Error converting start point runtime type to API value: {:?}", start),
vec![sketch_block_range],
))
})?;
let start_artifact_id = exec_state.next_artifact_id();
let start_point_object = Object {
id: *start_object_id,
kind: ObjectKind::Segment {
segment: crate::front::Segment::Point(crate::front::Point {
position: start_point2d.clone(),
ctor: None,
owner: Some(segment.object_id),
freedom: start_final_freedom,
constraints: Vec::new(),
}),
},
label: Default::default(),
comments: Default::default(),
artifact_id: start_artifact_id,
source: source.clone(),
};
let start_point_object_id = start_point_object.id;
scene_objects.push(start_point_object);
let end_point2d = TyF64::to_point2d(end).map_err(|_| {
KclError::new_internal(KclErrorDetails::new(
format!("Error converting end point runtime type to API value: {:?}", end),
vec![sketch_block_range],
))
})?;
let end_artifact_id = exec_state.next_artifact_id();
let end_point_object = Object {
id: *end_object_id,
kind: ObjectKind::Segment {
segment: crate::front::Segment::Point(crate::front::Point {
position: end_point2d.clone(),
ctor: None,
owner: Some(segment.object_id),
freedom: end_final_freedom,
constraints: Vec::new(),
}),
},
label: Default::default(),
comments: Default::default(),
artifact_id: end_artifact_id,
source: source.clone(),
};
let end_point_object_id = end_point_object.id;
scene_objects.push(end_point_object);
let center_point2d = TyF64::to_point2d(center).map_err(|_| {
KclError::new_internal(KclErrorDetails::new(
format!("Error converting center point runtime type to API value: {:?}", center),
vec![sketch_block_range],
))
})?;
let center_artifact_id = exec_state.next_artifact_id();
let center_point_object = Object {
id: *center_object_id,
kind: ObjectKind::Segment {
segment: crate::front::Segment::Point(crate::front::Point {
position: center_point2d.clone(),
ctor: None,
owner: Some(segment.object_id),
freedom: center_final_freedom,
constraints: Vec::new(),
}),
},
label: Default::default(),
comments: Default::default(),
artifact_id: center_artifact_id,
source: source.clone(),
};
let center_point_object_id = center_point_object.id;
scene_objects.push(center_point_object);
let arc_artifact_id = exec_state.next_artifact_id();
let segment_object = Object {
id: segment.object_id,
kind: ObjectKind::Segment {
segment: crate::front::Segment::Arc(crate::front::Arc {
start: start_point_object_id,
end: end_point_object_id,
center: center_point_object_id,
ctor: crate::front::SegmentCtor::Arc(ctor.as_ref().clone()),
ctor_applicable: true,
construction: *construction,
}),
},
label: Default::default(),
comments: Default::default(),
artifact_id: arc_artifact_id,
source,
};
scene_objects.push(segment_object);
}
SegmentKind::Circle {
start,
center,
ctor,
start_object_id,
center_object_id,
start_freedom,
center_freedom,
construction,
} => {
let start_final_freedom = start_freedom.unwrap_or(Freedom::Free);
let center_final_freedom = center_freedom.unwrap_or(Freedom::Free);
let start_point2d = TyF64::to_point2d(start).map_err(|_| {
KclError::new_internal(KclErrorDetails::new(
format!("Error converting start point runtime type to API value: {:?}", start),
vec![sketch_block_range],
))
})?;
let start_artifact_id = exec_state.next_artifact_id();
let start_point_object = Object {
id: *start_object_id,
kind: ObjectKind::Segment {
segment: crate::front::Segment::Point(crate::front::Point {
position: start_point2d.clone(),
ctor: None,
owner: Some(segment.object_id),
freedom: start_final_freedom,
constraints: Vec::new(),
}),
},
label: Default::default(),
comments: Default::default(),
artifact_id: start_artifact_id,
source: source.clone(),
};
let start_point_object_id = start_point_object.id;
scene_objects.push(start_point_object);
let center_point2d = TyF64::to_point2d(center).map_err(|_| {
KclError::new_internal(KclErrorDetails::new(
format!("Error converting center point runtime type to API value: {:?}", center),
vec![sketch_block_range],
))
})?;
let center_artifact_id = exec_state.next_artifact_id();
let center_point_object = Object {
id: *center_object_id,
kind: ObjectKind::Segment {
segment: crate::front::Segment::Point(crate::front::Point {
position: center_point2d.clone(),
ctor: None,
owner: Some(segment.object_id),
freedom: center_final_freedom,
constraints: Vec::new(),
}),
},
label: Default::default(),
comments: Default::default(),
artifact_id: center_artifact_id,
source: source.clone(),
};
let center_point_object_id = center_point_object.id;
scene_objects.push(center_point_object);
let circle_artifact_id = exec_state.next_artifact_id();
let segment_object = Object {
id: segment.object_id,
kind: ObjectKind::Segment {
segment: crate::front::Segment::Circle(crate::front::Circle {
start: start_point_object_id,
center: center_point_object_id,
ctor: crate::front::SegmentCtor::Circle(ctor.as_ref().clone()),
ctor_applicable: true,
construction: *construction,
}),
},
label: Default::default(),
comments: Default::default(),
artifact_id: circle_artifact_id,
source,
};
scene_objects.push(segment_object);
}
SegmentKind::ControlPointSpline {
controls,
ctor,
control_object_ids,
control_polygon_edge_object_ids,
control_freedoms,
degree,
construction,
} => {
let mut control_point_object_ids = Vec::with_capacity(controls.len());
for ((control, control_object_id), control_freedom) in controls
.iter()
.zip(control_object_ids.iter())
.zip(control_freedoms.iter())
{
let control_point2d = TyF64::to_point2d(control).map_err(|_| {
KclError::new_internal(KclErrorDetails::new(
format!(
"Error converting control point runtime type to API value: {:?}",
control
),
vec![sketch_block_range],
))
})?;
let artifact_id = exec_state.next_artifact_id();
let point_object = Object {
id: *control_object_id,
kind: ObjectKind::Segment {
segment: crate::front::Segment::Point(crate::front::Point {
position: control_point2d,
ctor: None,
owner: Some(segment.object_id),
freedom: control_freedom.unwrap_or(Freedom::Free),
constraints: Vec::new(),
}),
},
label: Default::default(),
comments: Default::default(),
artifact_id,
source: source.clone(),
};
control_point_object_ids.push(point_object.id);
scene_objects.push(point_object);
}
for (index, edge_object_id) in control_polygon_edge_object_ids.iter().enumerate() {
let edge_artifact_id = exec_state.next_artifact_id();
let edge_object = Object {
id: *edge_object_id,
kind: ObjectKind::Segment {
segment: crate::front::Segment::Line(crate::front::Line {
start: control_point_object_ids[index],
end: control_point_object_ids[index + 1],
owner: Some(segment.object_id),
ctor: crate::front::SegmentCtor::Line(crate::front::LineCtor {
start: ctor.points[index].clone(),
end: ctor.points[index + 1].clone(),
construction: Some(*construction),
}),
ctor_applicable: false,
construction: *construction,
}),
},
label: Default::default(),
comments: Default::default(),
artifact_id: edge_artifact_id,
source: source.clone(),
};
scene_objects.push(edge_object);
}
let artifact_id = exec_state.next_artifact_id();
let segment_object = Object {
id: segment.object_id,
kind: ObjectKind::Segment {
segment: crate::front::Segment::ControlPointSpline(crate::front::ControlPointSpline {
controls: control_point_object_ids,
degree: *degree,
ctor: crate::front::SegmentCtor::ControlPointSpline(ctor.as_ref().clone()),
ctor_applicable: true,
construction: *construction,
}),
},
label: Default::default(),
comments: Default::default(),
artifact_id,
source,
};
scene_objects.push(segment_object);
}
}
}
Ok(scene_objects)
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use indexmap::IndexMap;
use kittycad_modeling_cmds::units::UnitLength;
use uuid::Uuid;
use super::*;
use crate::execution::ArtifactId;
use crate::execution::BasePath;
use crate::execution::GeoMeta;
use crate::execution::Plane;
use crate::execution::PlaneInfo;
use crate::execution::PlaneKind;
use crate::execution::ProfileClosed;
use crate::front::Expr;
use crate::front::LineCtor;
use crate::front::Number;
use crate::front::ObjectId;
use crate::front::Point2d;
use crate::std::sketch::PlaneData;
fn test_point(x: f64, y: f64) -> Point2d<Expr> {
Point2d {
x: Expr::Var(Number::from((x, UnitLength::Millimeters))),
y: Expr::Var(Number::from((y, UnitLength::Millimeters))),
}
}
fn test_surface() -> SketchSurface {
let id = Uuid::new_v4();
SketchSurface::Plane(Box::new(Plane {
id,
artifact_id: ArtifactId::new(id),
object_id: None,
kind: PlaneKind::XY,
info: PlaneInfo::try_from(PlaneData::XY).unwrap(),
meta: vec![],
}))
}
fn test_sketch(surface: SketchSurface) -> Sketch {
let id = Uuid::new_v4();
let base = BasePath {
from: [0.0, 0.0],
to: [0.0, 0.0],
units: UnitLength::Millimeters,
tag: None,
geo_meta: GeoMeta {
id,
metadata: Metadata {
source_range: SourceRange::default(),
},
},
};
Sketch {
id,
paths: vec![],
inner_paths: vec![],
on: surface,
start: base,
tags: Default::default(),
artifact_id: ArtifactId::new(id),
original_id: id,
origin_sketch_id: None,
mirror: None,
clone: None,
synthetic_jump_path_ids: vec![],
units: UnitLength::Millimeters,
meta: vec![],
is_closed: ProfileClosed::No,
}
}
fn test_line_value(object_id_seed: usize) -> KclValue {
let point = |x, y| {
[
UnsolvedExpr::Known(TyF64::new(x, NumericType::mm())),
UnsolvedExpr::Known(TyF64::new(y, NumericType::mm())),
]
};
let segment = UnsolvedSegment {
id: Uuid::new_v4(),
object_id: ObjectId(object_id_seed),
kind: UnsolvedSegmentKind::Line {
start: point(0.0, 0.0),
end: point(1.0, 0.0),
ctor: Box::new(LineCtor {
start: test_point(0.0, 0.0),
end: test_point(1.0, 0.0),
construction: None,
}),
start_object_id: ObjectId(object_id_seed + 1),
end_object_id: ObjectId(object_id_seed + 2),
construction: false,
},
tag: None,
node_path: None,
meta: vec![],
};
KclValue::Segment {
value: Box::new(AbstractSegment {
repr: SegmentRepr::Unsolved {
segment: Box::new(segment),
},
meta: vec![],
}),
}
}
fn segment_sketch(value: &KclValue) -> &Arc<Sketch> {
let KclValue::Segment { value } = value else {
panic!("expected segment");
};
let SegmentRepr::Solved { segment } = &value.repr else {
panic!("expected solved segment");
};
segment.sketch.as_ref().expect("expected segment sketch")
}
#[test]
fn substitute_sketch_vars_shares_one_sketch_across_segments() {
let surface = test_surface();
let sketch = test_sketch(surface.clone());
let mut variables = IndexMap::new();
variables.insert("line1".to_owned(), test_line_value(1));
variables.insert("line2".to_owned(), test_line_value(4));
let solved = Solved {
final_values: vec![],
iterations: 0,
warnings: vec![],
priority_solved: 0,
variables_in_conflicts: AHashSet::new(),
};
let substituted = substitute_sketch_vars(
variables,
&surface,
sketch.id,
Some(&sketch),
&solved,
NumericType::mm(),
None,
)
.unwrap();
let line1_sketch = segment_sketch(&substituted["line1"]);
let line2_sketch = segment_sketch(&substituted["line2"]);
assert!(Arc::ptr_eq(line1_sketch, line2_sketch));
assert_eq!(line1_sketch.id, sketch.id);
}
}