use super::*;
pub(super) fn resolve_frame(ctx: &FeatureContext) -> Result<(Frame, Vec<String>), String> {
if let Some(name) = common::first_reference_name(ctx.param("sketchPlane")) {
if let Some(frame) = ctx.scene.resolve_frame(&name) {
return Ok((frame, Vec::new()));
}
if let Some(face) = ctx.scene.resolve_face(&name) {
return Ok((common::face_frame(face)?, Vec::new()));
}
if let Some(frame) = persisted_basis_frame(ctx) {
return Ok((frame, Vec::new()));
}
return Ok((identity_frame(), vec![name]));
}
if let Some(frame) = persisted_basis_frame(ctx) {
return Ok((frame, Vec::new()));
}
Ok((identity_frame(), Vec::new()))
}
fn identity_frame() -> Frame {
Frame {
origin: Vec3::new(0.0, 0.0, 0.0),
x_axis: Vec3::new(1.0, 0.0, 0.0),
y_axis: Vec3::new(0.0, 1.0, 0.0),
z_axis: Vec3::new(0.0, 0.0, 1.0),
}
}
fn persisted_basis_frame(ctx: &FeatureContext) -> Option<Frame> {
let basis = ctx.persistent.get("basis")?;
let read = |key: &str, default: Vec3| read_vec3(basis.get(key), default);
Some(Frame {
origin: read("origin", Vec3::new(0.0, 0.0, 0.0)),
x_axis: read("x", Vec3::new(1.0, 0.0, 0.0)),
y_axis: read("y", Vec3::new(0.0, 1.0, 0.0)),
z_axis: read("z", Vec3::new(0.0, 0.0, 1.0)),
})
}
fn read_vec3(value: Option<&Value>, default: Vec3) -> Vec3 {
let Some(array) = value.and_then(|v| v.as_array()) else {
return default;
};
let component = |index: usize, fallback: f64| {
array.get(index).and_then(|v| v.as_f64()).unwrap_or(fallback)
};
Vec3::new(
component(0, default.x),
component(1, default.y),
component(2, default.z),
)
}
fn eval_expr(ctx: &FeatureContext, what: &str, value: &Value) -> Result<Option<f64>, String> {
let Value::String(source) = value else {
return Ok(None);
};
if source.trim().is_empty() {
return Ok(None);
}
let number = ctx
.env
.eval(source)
.map_err(|error| format!("sketch: {what} = `{source}`: {error}"))?;
if !number.is_finite() {
return Err(format!("sketch: {what} = `{source}` evaluated to {number}"));
}
Ok(Some(number))
}
pub(super) fn pre_evaluate_expressions(
ctx: &FeatureContext,
sketch: &mut Value,
) -> Result<(), String> {
if let Some(points) = sketch.get_mut("points").and_then(|p| p.as_array_mut()) {
for point in points {
let id = point
.get("id")
.map(|id| id.to_string())
.unwrap_or_else(|| "?".to_string());
for key in ["x", "y"] {
let Some(value) = point.get(key) else { continue };
let what = format!("point {id} `{key}`");
if let Some(number) = eval_expr(ctx, &what, &value.clone())? {
point[key] = Value::from(number);
}
}
}
}
if let Some(constraints) = sketch.get_mut("constraints").and_then(|c| c.as_array_mut()) {
for constraint in constraints {
let id = constraint
.get("id")
.map(|id| id.to_string())
.unwrap_or_else(|| "?".to_string());
let is_diameter = constraint.get("type").and_then(|v| v.as_str()) == Some("⟺")
&& constraint.get("displayStyle").and_then(|v| v.as_str()) == Some("diameter")
&& constraint.get("valueExprMode").and_then(|v| v.as_str()) == Some("diameter");
let expr = constraint
.get("valueExpr")
.cloned()
.map(|value| eval_expr(ctx, &format!("constraint {id} `valueExpr`"), &value))
.transpose()?
.flatten()
.map(|n| if is_diameter { n * 0.5 } else { n });
let evaluated = match expr {
Some(number) => Some(number),
None => constraint
.get("value")
.cloned()
.map(|value| eval_expr(ctx, &format!("constraint {id} `value`"), &value))
.transpose()?
.flatten(),
};
if let Some(number) = evaluated {
constraint["value"] = Value::from(number);
}
}
}
Ok(())
}