1use anyhow::Result;
4use kcmc::{
5 each_cmd as mcmd,
6 length_unit::LengthUnit,
7 shared::{Angle, Opposite},
8 ModelingCmd,
9};
10use kittycad_modeling_cmds::{self as kcmc, shared::Point3d};
11
12use super::{args::TyF64, DEFAULT_TOLERANCE};
13use crate::{
14 errors::{KclError, KclErrorDetails},
15 execution::{
16 types::{NumericType, PrimitiveType, RuntimeType},
17 ExecState, KclValue, Sketch, Solid,
18 },
19 parsing::ast::types::TagNode,
20 std::{axis_or_reference::Axis2dOrEdgeReference, extrude::do_post_extrude, Args},
21};
22
23extern crate nalgebra_glm as glm;
24
25pub async fn revolve(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
27 let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?;
28 let axis = args.get_kw_arg_typed(
29 "axis",
30 &RuntimeType::Union(vec![
31 RuntimeType::Primitive(PrimitiveType::Edge),
32 RuntimeType::Primitive(PrimitiveType::Axis2d),
33 ]),
34 exec_state,
35 )?;
36 let angle: Option<TyF64> = args.get_kw_arg_opt_typed("angle", &RuntimeType::degrees(), exec_state)?;
37 let tolerance: Option<TyF64> = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::length(), exec_state)?;
38 let tag_start = args.get_kw_arg_opt("tagStart")?;
39 let tag_end = args.get_kw_arg_opt("tagEnd")?;
40 let symmetric = args.get_kw_arg_opt("symmetric")?;
41 let bidirectional_angle: Option<TyF64> =
42 args.get_kw_arg_opt_typed("bidirectionalAngle", &RuntimeType::angle(), exec_state)?;
43
44 let value = inner_revolve(
45 sketches,
46 axis,
47 angle.map(|t| t.n),
48 tolerance,
49 tag_start,
50 tag_end,
51 symmetric,
52 bidirectional_angle.map(|t| t.n),
53 exec_state,
54 args,
55 )
56 .await?;
57 Ok(value.into())
58}
59
60#[allow(clippy::too_many_arguments)]
61async fn inner_revolve(
62 sketches: Vec<Sketch>,
63 axis: Axis2dOrEdgeReference,
64 angle: Option<f64>,
65 tolerance: Option<TyF64>,
66 tag_start: Option<TagNode>,
67 tag_end: Option<TagNode>,
68 symmetric: Option<bool>,
69 bidirectional_angle: Option<f64>,
70 exec_state: &mut ExecState,
71 args: Args,
72) -> Result<Vec<Solid>, KclError> {
73 if let Some(angle) = angle {
74 if !(-360.0..=360.0).contains(&angle) || angle == 0.0 {
78 return Err(KclError::Semantic(KclErrorDetails::new(
79 format!("Expected angle to be between -360 and 360 and not 0, found `{}`", angle),
80 vec![args.source_range],
81 )));
82 }
83 }
84
85 if let Some(bidirectional_angle) = bidirectional_angle {
86 if !(-360.0..=360.0).contains(&bidirectional_angle) || bidirectional_angle == 0.0 {
90 return Err(KclError::Semantic(KclErrorDetails::new(
91 format!(
92 "Expected bidirectional angle to be between -360 and 360 and not 0, found `{}`",
93 bidirectional_angle
94 ),
95 vec![args.source_range],
96 )));
97 }
98
99 if let Some(angle) = angle {
100 let ang = angle.signum() * bidirectional_angle + angle;
101 if !(-360.0..=360.0).contains(&ang) {
102 return Err(KclError::Semantic(KclErrorDetails::new(
103 format!(
104 "Combined angle and bidirectional must be between -360 and 360, found '{}'",
105 ang
106 ),
107 vec![args.source_range],
108 )));
109 }
110 }
111 }
112
113 if symmetric.unwrap_or(false) && bidirectional_angle.is_some() {
114 return Err(KclError::Semantic(KclErrorDetails::new(
115 "You cannot give both `symmetric` and `bidirectional` params, you have to choose one or the other"
116 .to_owned(),
117 vec![args.source_range],
118 )));
119 }
120
121 let angle = Angle::from_degrees(angle.unwrap_or(360.0));
122
123 let bidirectional_angle = bidirectional_angle.map(Angle::from_degrees);
124
125 let opposite = match (symmetric, bidirectional_angle) {
126 (Some(true), _) => Opposite::Symmetric,
127 (None, None) => Opposite::None,
128 (Some(false), None) => Opposite::None,
129 (None, Some(angle)) => Opposite::Other(angle),
130 (Some(false), Some(angle)) => Opposite::Other(angle),
131 };
132
133 let mut solids = Vec::new();
134 for sketch in &sketches {
135 let id = exec_state.next_uuid();
136 let tolerance = tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE);
137
138 let direction = match &axis {
139 Axis2dOrEdgeReference::Axis { direction, origin } => {
140 args.batch_modeling_cmd(
141 id,
142 ModelingCmd::from(mcmd::Revolve {
143 angle,
144 target: sketch.id.into(),
145 axis: Point3d {
146 x: direction[0].to_mm(),
147 y: direction[1].to_mm(),
148 z: 0.0,
149 },
150 origin: Point3d {
151 x: LengthUnit(origin[0].to_mm()),
152 y: LengthUnit(origin[1].to_mm()),
153 z: LengthUnit(0.0),
154 },
155 tolerance: LengthUnit(tolerance),
156 axis_is_2d: true,
157 opposite: opposite.clone(),
158 }),
159 )
160 .await?;
161 glm::DVec2::new(direction[0].to_mm(), direction[1].to_mm())
162 }
163 Axis2dOrEdgeReference::Edge(edge) => {
164 let edge_id = edge.get_engine_id(exec_state, &args)?;
165 args.batch_modeling_cmd(
166 id,
167 ModelingCmd::from(mcmd::RevolveAboutEdge {
168 angle,
169 target: sketch.id.into(),
170 edge_id,
171 tolerance: LengthUnit(tolerance),
172 opposite: opposite.clone(),
173 }),
174 )
175 .await?;
176 glm::DVec2::new(0.0, 1.0)
178 }
179 };
180
181 let mut edge_id = None;
182 for path in sketch.paths.clone() {
185 let from = path.get_from();
186 let to = path.get_to();
187
188 let dir = glm::DVec2::new(to[0].n - from[0].n, to[1].n - from[1].n);
189 if glm::are_collinear2d(&dir, &direction, tolerance) {
190 continue;
191 }
192 edge_id = Some(path.get_id());
193 break;
194 }
195
196 solids.push(
197 do_post_extrude(
198 sketch,
199 id.into(),
200 TyF64::new(0.0, NumericType::mm()),
201 false,
202 &super::extrude::NamedCapTags {
203 start: tag_start.as_ref(),
204 end: tag_end.as_ref(),
205 },
206 exec_state,
207 &args,
208 edge_id,
209 )
210 .await?,
211 );
212 }
213
214 Ok(solids)
215}