use crate::feature_pipeline::features::{common, transform_bake};
use crate::feature_pipeline::{FeatureContext, FeatureResult};
use crate::{make_arc, make_torus_brep, revolve_profile_brep_named, Vec3};
pub fn execute(ctx: &FeatureContext) -> FeatureResult {
match build(ctx) {
Ok(result) => result,
Err(error) => ctx.fail(error),
}
}
fn build(ctx: &FeatureContext) -> Result<FeatureResult, String> {
let major_radius = ctx.number("majorRadius")?.abs();
let tube_radius = ctx.number("tubeRadius")?.abs();
let raw = match ctx.param("arc") {
None | Some(serde_json::Value::Null) => 360.0,
Some(_) => ctx.number("arc")?,
};
let raw = if !raw.is_finite() || raw == 0.0 { 360.0 } else { raw };
let sweep = raw.abs().min(360.0).max(1e-6);
let full = (sweep - 360.0).abs() <= 1e-9;
let reverse = raw < 0.0 && !full;
let solid = if full {
let mut solid = make_torus_brep(
Vec3::new(0.0, 0.0, 0.0),
Vec3::new(0.0, 1.0, 0.0),
major_radius,
tube_radius,
)?;
let face = solid
.shells
.get_mut(0)
.and_then(|shell| shell.faces.get_mut(0))
.ok_or("torus builder produced no face")?;
face.name = Some(format!("{}_Side", ctx.id));
solid
} else {
let tube_center = Vec3::new(major_radius, 0.0, 0.0);
let x_axis = Vec3::new(1.0, 0.0, 0.0);
let y_axis = Vec3::new(0.0, 1.0, 0.0);
let quarter = std::f64::consts::FRAC_PI_2;
let profile = [
make_arc(tube_center, x_axis, y_axis, tube_radius, 0.0, quarter)?,
make_arc(tube_center, x_axis, y_axis, tube_radius, quarter, 2.0 * quarter)?,
make_arc(tube_center, x_axis, y_axis, tube_radius, 2.0 * quarter, 3.0 * quarter)?,
make_arc(tube_center, x_axis, y_axis, tube_radius, 3.0 * quarter, 4.0 * quarter)?,
];
let side_names = vec![Some(format!("{}_Side", ctx.id)); 4];
let cap_names = [
Some(format!("{}_Cap0", ctx.id)),
Some(format!("{}_Cap1", ctx.id)),
];
revolve_profile_brep_named(
&profile,
Vec3::new(0.0, 0.0, 0.0),
Vec3::new(0.0, 1.0, 0.0),
sweep.to_radians(),
&side_names,
&cap_names,
)?
};
let solid = if reverse {
common::rotate_half_turn_about_x(solid)?
} else {
solid
};
let solid = transform_bake::apply(ctx, solid)?;
Ok(common::finalize_solid(ctx, solid, &ctx.id))
}
pub fn context_applicable(_probe: &crate::feature_pipeline::SelectionProbe) -> bool {
false
}
pub fn schema() -> serde_json::Value {
serde_json::json!({
"type": "P.T",
"shortName": "P.T",
"longName": "Primitive Torus",
"displayBuilder": false,
"inputParamsSchema": {
"id": {
"type": "string",
"default_value": null,
"hint": "Unique identifier for the feature"
},
"majorRadius": {
"type": "number",
"default_value": 10,
"hint": "Distance from center to the centerline of the tube (R)"
},
"tubeRadius": {
"type": "number",
"default_value": 2,
"hint": "Radius of the tube (r)"
},
"arc": {
"type": "number",
"default_value": 360,
"hint": "Sweep angle of the torus in degrees [-360, 360]; a negative angle reverses the sweep direction"
},
"transform": common::primitive_transform_schema(),
"boolean": common::optional_boolean_schema()
}
})
}