use crate::feature_pipeline::features::common;
use crate::feature_pipeline::features::datum::rotate_euler_xyz;
use crate::feature_pipeline::FeatureContext;
use crate::{transform_brep, AffineTransform, BrepSolid, Vec3};
pub fn apply(ctx: &FeatureContext, solid: BrepSolid) -> Result<BrepSolid, String> {
let Some(affine) = trs_transform(ctx)? else {
return Ok(solid);
};
let reflection = affine.determinant3() < 0.0;
transform_brep(&solid, affine, reflection)
.map_err(|error| format!("primitive transform bake: {error}"))
}
pub fn trs_transform(ctx: &FeatureContext) -> Result<Option<AffineTransform>, String> {
let Some(transform) = ctx.param("transform") else {
return Ok(None);
};
if let Some(name) = reference_name(transform.get("reference")) {
return Err(format!(
"primitive transform `reference` ('{name}') is not yet migrated to the Rust pipeline \
(the base frame could not be resolved)"
));
}
let vec3 = |key: &str, default: [f64; 3]| {
common::vec3_from_value(ctx.env, transform.get(key), &format!("transform.{key}"), default)
};
let position = vec3("position", [0.0, 0.0, 0.0])?;
let rotation_deg = vec3("rotationEuler", [0.0, 0.0, 0.0])?;
let scale = vec3("scale", [1.0, 1.0, 1.0])?;
if position == [0.0, 0.0, 0.0] && rotation_deg == [0.0, 0.0, 0.0] && scale == [1.0, 1.0, 1.0] {
return Ok(None);
}
let rotation = [
rotation_deg[0].to_radians(),
rotation_deg[1].to_radians(),
rotation_deg[2].to_radians(),
];
let col_x = rotate_euler_xyz(Vec3::new(1.0, 0.0, 0.0), rotation).scale(scale[0]);
let col_y = rotate_euler_xyz(Vec3::new(0.0, 1.0, 0.0), rotation).scale(scale[1]);
let col_z = rotate_euler_xyz(Vec3::new(0.0, 0.0, 1.0), rotation).scale(scale[2]);
let matrix = [
col_x.x, col_y.x, col_z.x, position[0], col_x.y, col_y.y, col_z.y, position[1], col_x.z, col_y.z, col_z.z, position[2], 0.0, 0.0, 0.0, 1.0,
];
AffineTransform::new(matrix)
.map(Some)
.map_err(|error| format!("primitive transform bake: {error}"))
}
fn reference_name(value: Option<&serde_json::Value>) -> Option<String> {
let raw = match value? {
serde_json::Value::String(text) => text.as_str(),
serde_json::Value::Object(map) => map
.get("name")
.and_then(|v| v.as_str())
.filter(|s| !s.trim().is_empty())
.or_else(|| map.get("id").and_then(|v| v.as_str()))?,
_ => return None,
};
let trimmed = raw.trim();
(!trimmed.is_empty()).then(|| trimmed.to_string())
}