Merge branch 'main' into jess/cleaned-imports
This commit is contained in:
@ -444,12 +444,11 @@ impl FnData {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
pub(super) fn to_autocomplete_snippet(&self) -> String {
|
||||
if self.name == "loft" {
|
||||
return "loft([${0:sketch000}, ${1:sketch001}])${}".to_owned();
|
||||
return "loft([${0:sketch000}, ${1:sketch001}])".to_owned();
|
||||
} else if self.name == "hole" {
|
||||
return "hole(${0:holeSketch}, ${1:%})${}".to_owned();
|
||||
return "hole(${0:holeSketch}, ${1:%})".to_owned();
|
||||
}
|
||||
let mut args = Vec::new();
|
||||
let mut index = 0;
|
||||
@ -459,9 +458,7 @@ impl FnData {
|
||||
args.push(arg_str);
|
||||
}
|
||||
}
|
||||
// We end with ${} so you can jump to the end of the snippet.
|
||||
// After the last argument.
|
||||
format!("{}({})${{}}", self.preferred_name, args.join(", "))
|
||||
format!("{}({})", self.preferred_name, args.join(", "))
|
||||
}
|
||||
|
||||
fn to_signature_help(&self) -> SignatureHelp {
|
||||
|
@ -498,12 +498,17 @@ pub trait StdLibFn: std::fmt::Debug + Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn to_autocomplete_snippet(&self) -> Result<String> {
|
||||
if self.name() == "loft" {
|
||||
return Ok("loft([${0:sketch000}, ${1:sketch001}])${}".to_string());
|
||||
return Ok("loft([${0:sketch000}, ${1:sketch001}])".to_string());
|
||||
} else if self.name() == "union" {
|
||||
return Ok("union([${0:extrude001}, ${1:extrude002}])".to_string());
|
||||
} else if self.name() == "subtract" {
|
||||
return Ok("subtract([${0:extrude001}], tools = [${1:extrude002}])".to_string());
|
||||
} else if self.name() == "intersect" {
|
||||
return Ok("intersect([${0:extrude001}, ${1:extrude002}])".to_string());
|
||||
} else if self.name() == "hole" {
|
||||
return Ok("hole(${0:holeSketch}, ${1:%})${}".to_string());
|
||||
return Ok("hole(${0:holeSketch}, ${1:%})".to_string());
|
||||
}
|
||||
let in_keyword_fn = self.keyword_arguments();
|
||||
let mut args = Vec::new();
|
||||
@ -514,9 +519,7 @@ pub trait StdLibFn: std::fmt::Debug + Send + Sync {
|
||||
args.push(arg_str);
|
||||
}
|
||||
}
|
||||
// We end with ${} so you can jump to the end of the snippet.
|
||||
// After the last argument.
|
||||
Ok(format!("{}({})${{}}", self.name(), args.join(", ")))
|
||||
Ok(format!("{}({})", self.name(), args.join(", ")))
|
||||
}
|
||||
|
||||
fn to_signature_help(&self) -> SignatureHelp {
|
||||
@ -890,29 +893,26 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_line() {
|
||||
let line_fn: Box<dyn StdLibFn> = Box::new(crate::std::sketch::Line);
|
||||
let snippet = line_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(snippet, r#"line(${0:%}, end = [${1:3.14}, ${2:3.14}])${}"#);
|
||||
assert_eq!(snippet, r#"line(${0:%}, end = [${1:3.14}, ${2:3.14}])"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_extrude() {
|
||||
let extrude_fn: Box<dyn StdLibFn> = Box::new(crate::std::extrude::Extrude);
|
||||
let snippet = extrude_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(snippet, r#"extrude(${0:%}, length = ${1:3.14})${}"#);
|
||||
assert_eq!(snippet, r#"extrude(${0:%}, length = ${1:3.14})"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_fillet() {
|
||||
let fillet_fn: Box<dyn StdLibFn> = Box::new(crate::std::fillet::Fillet);
|
||||
let snippet = fillet_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(
|
||||
snippet,
|
||||
r#"fillet(${0:%}, radius = ${1:3.14}, tags = [${2:"tag_or_edge_fn"}])${}"#
|
||||
r#"fillet(${0:%}, radius = ${1:3.14}, tags = [${2:"tag_or_edge_fn"}])"#
|
||||
);
|
||||
}
|
||||
|
||||
@ -920,18 +920,17 @@ mod tests {
|
||||
fn get_autocomplete_snippet_start_sketch_on() {
|
||||
let start_sketch_on_fn: Box<dyn StdLibFn> = Box::new(crate::std::sketch::StartSketchOn);
|
||||
let snippet = start_sketch_on_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(snippet, r#"startSketchOn(${0:"XY"})${}"#);
|
||||
assert_eq!(snippet, r#"startSketchOn(${0:"XY"})"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_pattern_circular_3d() {
|
||||
// We test this one specifically because it has ints and floats and strings.
|
||||
let pattern_fn: Box<dyn StdLibFn> = Box::new(crate::std::patterns::PatternCircular3D);
|
||||
let snippet = pattern_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(
|
||||
snippet,
|
||||
r#"patternCircular3d(${0:%}, instances = ${1:10}, axis = [${2:3.14}, ${3:3.14}, ${4:3.14}], center = [${5:3.14}, ${6:3.14}, ${7:3.14}], arcDegrees = ${8:3.14}, rotateDuplicates = ${9:false})${}"#
|
||||
r#"patternCircular3d(${0:%}, instances = ${1:10}, axis = [${2:3.14}, ${3:3.14}, ${4:3.14}], center = [${5:3.14}, ${6:3.14}, ${7:3.14}], arcDegrees = ${8:3.14}, rotateDuplicates = ${9:false})"#
|
||||
);
|
||||
}
|
||||
|
||||
@ -942,11 +941,10 @@ mod tests {
|
||||
panic!();
|
||||
};
|
||||
let snippet = revolve_fn.to_autocomplete_snippet();
|
||||
assert_eq!(snippet, r#"revolve(axis = ${0:X})${}"#);
|
||||
assert_eq!(snippet, r#"revolve(axis = ${0:X})"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_circle() {
|
||||
let data = kcl_doc::walk_prelude();
|
||||
let DocData::Fn(circle_fn) = data.into_iter().find(|d| d.name() == "circle").unwrap() else {
|
||||
@ -955,12 +953,11 @@ mod tests {
|
||||
let snippet = circle_fn.to_autocomplete_snippet();
|
||||
assert_eq!(
|
||||
snippet,
|
||||
r#"circle(center = [${0:3.14}, ${1:3.14}], radius = ${2:3.14})${}"#
|
||||
r#"circle(center = [${0:3.14}, ${1:3.14}], radius = ${2:3.14})"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_arc() {
|
||||
let arc_fn: Box<dyn StdLibFn> = Box::new(crate::std::sketch::Arc);
|
||||
let snippet = arc_fn.to_autocomplete_snippet().unwrap();
|
||||
@ -970,7 +967,7 @@ mod tests {
|
||||
angleStart = ${0:3.14},
|
||||
angleEnd = ${1:3.14},
|
||||
radius = ${2:3.14},
|
||||
}, ${3:%})${}"#
|
||||
}, ${3:%})"#
|
||||
);
|
||||
}
|
||||
|
||||
@ -978,17 +975,16 @@ mod tests {
|
||||
fn get_autocomplete_snippet_map() {
|
||||
let map_fn: Box<dyn StdLibFn> = Box::new(crate::std::array::Map);
|
||||
let snippet = map_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(snippet, r#"map(${0:[0..9]})${}"#);
|
||||
assert_eq!(snippet, r#"map(${0:[0..9]})"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_pattern_linear_2d() {
|
||||
let pattern_fn: Box<dyn StdLibFn> = Box::new(crate::std::patterns::PatternLinear2D);
|
||||
let snippet = pattern_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(
|
||||
snippet,
|
||||
r#"patternLinear2d(${0:%}, instances = ${1:10}, distance = ${2:3.14}, axis = [${3:3.14}, ${4:3.14}])${}"#
|
||||
r#"patternLinear2d(${0:%}, instances = ${1:10}, distance = ${2:3.14}, axis = [${3:3.14}, ${4:3.14}])"#
|
||||
);
|
||||
}
|
||||
|
||||
@ -998,36 +994,32 @@ mod tests {
|
||||
let snippet = appearance_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(
|
||||
snippet,
|
||||
r#"appearance(${0:%}, color = ${1:"#.to_owned() + "\"#" + r#"ff0000"})${}"#
|
||||
r#"appearance(${0:%}, color = ${1:"#.to_owned() + "\"#" + r#"ff0000"})"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_loft() {
|
||||
let loft_fn: Box<dyn StdLibFn> = Box::new(crate::std::loft::Loft);
|
||||
let snippet = loft_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(snippet, r#"loft([${0:sketch000}, ${1:sketch001}])${}"#);
|
||||
assert_eq!(snippet, r#"loft([${0:sketch000}, ${1:sketch001}])"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_sweep() {
|
||||
let sweep_fn: Box<dyn StdLibFn> = Box::new(crate::std::sweep::Sweep);
|
||||
let snippet = sweep_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(snippet, r#"sweep(${0:%}, path = ${1:sketch000})${}"#);
|
||||
assert_eq!(snippet, r#"sweep(${0:%}, path = ${1:sketch000})"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_hole() {
|
||||
let hole_fn: Box<dyn StdLibFn> = Box::new(crate::std::sketch::Hole);
|
||||
let snippet = hole_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(snippet, r#"hole(${0:holeSketch}, ${1:%})${}"#);
|
||||
assert_eq!(snippet, r#"hole(${0:holeSketch}, ${1:%})"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_helix() {
|
||||
let data = kcl_doc::walk_prelude();
|
||||
let DocData::Fn(helix_fn) = data.into_iter().find(|d| d.name() == "helix").unwrap() else {
|
||||
@ -1036,36 +1028,32 @@ mod tests {
|
||||
let snippet = helix_fn.to_autocomplete_snippet();
|
||||
assert_eq!(
|
||||
snippet,
|
||||
r#"helix(revolutions = ${0:3.14}, angleStart = ${1:3.14}, radius = ${2:3.14}, axis = ${3:X}, length = ${4:3.14})${}"#
|
||||
r#"helix(revolutions = ${0:3.14}, angleStart = ${1:3.14}, radius = ${2:3.14}, axis = ${3:X}, length = ${4:3.14})"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_union() {
|
||||
let union_fn: Box<dyn StdLibFn> = Box::new(crate::std::csg::Union);
|
||||
let snippet = union_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(snippet, r#"union(${0:%})${}"#);
|
||||
assert_eq!(snippet, r#"union([${0:extrude001}, ${1:extrude002}])"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_subtract() {
|
||||
let subtract_fn: Box<dyn StdLibFn> = Box::new(crate::std::csg::Subtract);
|
||||
let snippet = subtract_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(snippet, r#"subtract(${0:%}, tools = ${1:%})${}"#);
|
||||
assert_eq!(snippet, r#"subtract([${0:extrude001}], tools = [${1:extrude002}])"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_intersect() {
|
||||
let intersect_fn: Box<dyn StdLibFn> = Box::new(crate::std::csg::Intersect);
|
||||
let snippet = intersect_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(snippet, r#"intersect(${0:%})${}"#);
|
||||
assert_eq!(snippet, r#"intersect([${0:extrude001}, ${1:extrude002}])"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_get_common_edge() {
|
||||
let get_common_edge_fn: Box<dyn StdLibFn> = Box::new(crate::std::edge::GetCommonEdge);
|
||||
let snippet = get_common_edge_fn.to_autocomplete_snippet().unwrap();
|
||||
@ -1073,40 +1061,34 @@ mod tests {
|
||||
snippet,
|
||||
r#"getCommonEdge(faces = [{
|
||||
value = ${0:"string"},
|
||||
}])${}"#
|
||||
}])"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_scale() {
|
||||
let scale_fn: Box<dyn StdLibFn> = Box::new(crate::std::transform::Scale);
|
||||
let snippet = scale_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(
|
||||
snippet,
|
||||
r#"scale(${0:%}, x = ${1:3.14}, y = ${2:3.14}, z = ${3:3.14})${}"#
|
||||
);
|
||||
assert_eq!(snippet, r#"scale(${0:%}, x = ${1:3.14}, y = ${2:3.14}, z = ${3:3.14})"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_translate() {
|
||||
let translate_fn: Box<dyn StdLibFn> = Box::new(crate::std::transform::Translate);
|
||||
let snippet = translate_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(
|
||||
snippet,
|
||||
r#"translate(${0:%}, x = ${1:3.14}, y = ${2:3.14}, z = ${3:3.14})${}"#
|
||||
r#"translate(${0:%}, x = ${1:3.14}, y = ${2:3.14}, z = ${3:3.14})"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_rotate() {
|
||||
let rotate_fn: Box<dyn StdLibFn> = Box::new(crate::std::transform::Rotate);
|
||||
let snippet = rotate_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(
|
||||
snippet,
|
||||
r#"rotate(${0:%}, roll = ${1:3.14}, pitch = ${2:3.14}, yaw = ${3:3.14})${}"#
|
||||
r#"rotate(${0:%}, roll = ${1:3.14}, pitch = ${2:3.14}, yaw = ${3:3.14})"#
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,6 @@ pub(crate) const SETTINGS_UNIT_ANGLE: &str = "defaultAngleUnit";
|
||||
pub(super) const NO_PRELUDE: &str = "no_std";
|
||||
|
||||
pub(super) const IMPORT_FORMAT: &str = "format";
|
||||
pub(super) const IMPORT_FORMAT_VALUES: [&str; 9] = ["fbx", "gltf", "glb", "obj", "ply", "sldprt", "stp", "step", "stl"];
|
||||
pub(super) const IMPORT_COORDS: &str = "coords";
|
||||
pub(super) const IMPORT_COORDS_VALUES: [(&str, &System); 3] =
|
||||
[("zoo", KITTYCAD), ("opengl", OPENGL), ("vulkan", VULKAN)];
|
||||
|
@ -115,6 +115,30 @@ impl CodeRef {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export_to = "Artifact.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompositeSolid {
|
||||
pub id: ArtifactId,
|
||||
pub sub_type: CompositeSolidSubType,
|
||||
/// Constituent solids of the composite solid.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub solid_ids: Vec<ArtifactId>,
|
||||
/// Tool solids used for asymmetric operations like subtract.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub tool_ids: Vec<ArtifactId>,
|
||||
pub code_ref: CodeRef,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
|
||||
#[ts(export_to = "Artifact.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum CompositeSolidSubType {
|
||||
Intersect,
|
||||
Subtract,
|
||||
Union,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export_to = "Artifact.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@ -318,6 +342,7 @@ pub struct Helix {
|
||||
#[ts(export_to = "Artifact.ts")]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub enum Artifact {
|
||||
CompositeSolid(CompositeSolid),
|
||||
Plane(Plane),
|
||||
Path(Path),
|
||||
Segment(Segment),
|
||||
@ -336,6 +361,7 @@ pub enum Artifact {
|
||||
impl Artifact {
|
||||
pub(crate) fn id(&self) -> ArtifactId {
|
||||
match self {
|
||||
Artifact::CompositeSolid(a) => a.id,
|
||||
Artifact::Plane(a) => a.id,
|
||||
Artifact::Path(a) => a.id,
|
||||
Artifact::Segment(a) => a.id,
|
||||
@ -355,6 +381,7 @@ impl Artifact {
|
||||
#[expect(dead_code)]
|
||||
pub(crate) fn code_ref(&self) -> Option<&CodeRef> {
|
||||
match self {
|
||||
Artifact::CompositeSolid(a) => Some(&a.code_ref),
|
||||
Artifact::Plane(a) => Some(&a.code_ref),
|
||||
Artifact::Path(a) => Some(&a.code_ref),
|
||||
Artifact::Segment(a) => Some(&a.code_ref),
|
||||
@ -375,6 +402,7 @@ impl Artifact {
|
||||
/// type, return the new artifact which should be used as a replacement.
|
||||
fn merge(&mut self, new: Artifact) -> Option<Artifact> {
|
||||
match self {
|
||||
Artifact::CompositeSolid(a) => a.merge(new),
|
||||
Artifact::Plane(a) => a.merge(new),
|
||||
Artifact::Path(a) => a.merge(new),
|
||||
Artifact::Segment(a) => a.merge(new),
|
||||
@ -392,6 +420,18 @@ impl Artifact {
|
||||
}
|
||||
}
|
||||
|
||||
impl CompositeSolid {
|
||||
fn merge(&mut self, new: Artifact) -> Option<Artifact> {
|
||||
let Artifact::CompositeSolid(new) = new else {
|
||||
return Some(new);
|
||||
};
|
||||
merge_ids(&mut self.solid_ids, new.solid_ids);
|
||||
merge_ids(&mut self.tool_ids, new.tool_ids);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Plane {
|
||||
fn merge(&mut self, new: Artifact) -> Option<Artifact> {
|
||||
let Artifact::Plane(new) = new else {
|
||||
@ -1047,6 +1087,85 @@ fn artifacts_to_update(
|
||||
// the helix here, but it's not useful right now.
|
||||
return Ok(return_arr);
|
||||
}
|
||||
ModelingCmd::BooleanIntersection(_) | ModelingCmd::BooleanSubtract(_) | ModelingCmd::BooleanUnion(_) => {
|
||||
let (sub_type, solid_ids, tool_ids) = match cmd {
|
||||
ModelingCmd::BooleanIntersection(intersection) => {
|
||||
let solid_ids = intersection
|
||||
.solid_ids
|
||||
.iter()
|
||||
.copied()
|
||||
.map(ArtifactId::new)
|
||||
.collect::<Vec<_>>();
|
||||
(CompositeSolidSubType::Intersect, solid_ids, Vec::new())
|
||||
}
|
||||
ModelingCmd::BooleanSubtract(subtract) => {
|
||||
let solid_ids = subtract
|
||||
.target_ids
|
||||
.iter()
|
||||
.copied()
|
||||
.map(ArtifactId::new)
|
||||
.collect::<Vec<_>>();
|
||||
let tool_ids = subtract
|
||||
.tool_ids
|
||||
.iter()
|
||||
.copied()
|
||||
.map(ArtifactId::new)
|
||||
.collect::<Vec<_>>();
|
||||
(CompositeSolidSubType::Subtract, solid_ids, tool_ids)
|
||||
}
|
||||
ModelingCmd::BooleanUnion(union) => {
|
||||
let solid_ids = union.solid_ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>();
|
||||
(CompositeSolidSubType::Union, solid_ids, Vec::new())
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let mut new_solid_ids = vec![id];
|
||||
|
||||
match response {
|
||||
OkModelingCmdResponse::BooleanIntersection(intersection) => intersection
|
||||
.extra_solid_ids
|
||||
.iter()
|
||||
.copied()
|
||||
.map(ArtifactId::new)
|
||||
.for_each(|id| new_solid_ids.push(id)),
|
||||
OkModelingCmdResponse::BooleanSubtract(subtract) => subtract
|
||||
.extra_solid_ids
|
||||
.iter()
|
||||
.copied()
|
||||
.map(ArtifactId::new)
|
||||
.for_each(|id| new_solid_ids.push(id)),
|
||||
OkModelingCmdResponse::BooleanUnion(union) => union
|
||||
.extra_solid_ids
|
||||
.iter()
|
||||
.copied()
|
||||
.map(ArtifactId::new)
|
||||
.for_each(|id| new_solid_ids.push(id)),
|
||||
_ => {}
|
||||
}
|
||||
let return_arr = new_solid_ids
|
||||
.into_iter()
|
||||
// Extra solid IDs may include the command's ID. Make sure we
|
||||
// don't create a duplicate.
|
||||
.filter(|solid_id| *solid_id != id)
|
||||
.map(|solid_id| {
|
||||
Artifact::CompositeSolid(CompositeSolid {
|
||||
id: solid_id,
|
||||
sub_type,
|
||||
solid_ids: solid_ids.clone(),
|
||||
tool_ids: tool_ids.clone(),
|
||||
code_ref: CodeRef {
|
||||
range,
|
||||
path_to_node: path_to_node.clone(),
|
||||
},
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// TODO: Should we add the reverse graph edges?
|
||||
|
||||
return Ok(return_arr);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
@ -67,6 +67,11 @@ impl Artifact {
|
||||
/// the graph. This should be disjoint with `child_ids`.
|
||||
pub(crate) fn back_edges(&self) -> Vec<ArtifactId> {
|
||||
match self {
|
||||
Artifact::CompositeSolid(a) => {
|
||||
let mut ids = a.solid_ids.clone();
|
||||
ids.extend(a.tool_ids.iter());
|
||||
ids
|
||||
}
|
||||
Artifact::Plane(_) => Vec::new(),
|
||||
Artifact::Path(a) => vec![a.plane_id],
|
||||
Artifact::Segment(a) => vec![a.path_id],
|
||||
@ -87,6 +92,11 @@ impl Artifact {
|
||||
/// the graph.
|
||||
pub(crate) fn child_ids(&self) -> Vec<ArtifactId> {
|
||||
match self {
|
||||
Artifact::CompositeSolid(_) => {
|
||||
// Note: Don't include these since they're parents: solid_ids,
|
||||
// tool_ids.
|
||||
Vec::new()
|
||||
}
|
||||
Artifact::Plane(a) => a.path_ids.clone(),
|
||||
Artifact::Path(a) => {
|
||||
// Note: Don't include these since they're parents: plane_id.
|
||||
@ -213,6 +223,7 @@ impl ArtifactGraph {
|
||||
let id = artifact.id();
|
||||
|
||||
let grouped = match artifact {
|
||||
Artifact::CompositeSolid(_) => false,
|
||||
Artifact::Plane(_) => false,
|
||||
Artifact::Path(_) => {
|
||||
groups.entry(id).or_insert_with(Vec::new).push(id);
|
||||
@ -278,6 +289,15 @@ impl ArtifactGraph {
|
||||
}
|
||||
|
||||
match artifact {
|
||||
Artifact::CompositeSolid(composite_solid) => {
|
||||
writeln!(
|
||||
output,
|
||||
"{prefix}{}[\"CompositeSolid {:?}<br>{:?}\"]",
|
||||
id,
|
||||
composite_solid.sub_type,
|
||||
code_ref_display(&composite_solid.code_ref)
|
||||
)?;
|
||||
}
|
||||
Artifact::Plane(plane) => {
|
||||
writeln!(
|
||||
output,
|
||||
|
@ -918,25 +918,40 @@ impl Node<BinaryExpression> {
|
||||
if self.operator == BinaryOperator::Add || self.operator == BinaryOperator::Or {
|
||||
if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
|
||||
let args = crate::std::Args::new(Default::default(), self.into(), ctx.clone(), None);
|
||||
let result =
|
||||
crate::std::csg::inner_union(vec![*left.clone(), *right.clone()], exec_state, args).await?;
|
||||
let result = crate::std::csg::inner_union(
|
||||
vec![*left.clone(), *right.clone()],
|
||||
Default::default(),
|
||||
exec_state,
|
||||
args,
|
||||
)
|
||||
.await?;
|
||||
return Ok(result.into());
|
||||
}
|
||||
} else if self.operator == BinaryOperator::Sub {
|
||||
// Check if we have solids.
|
||||
if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
|
||||
let args = crate::std::Args::new(Default::default(), self.into(), ctx.clone(), None);
|
||||
let result =
|
||||
crate::std::csg::inner_subtract(vec![*left.clone()], vec![*right.clone()], exec_state, args)
|
||||
.await?;
|
||||
let result = crate::std::csg::inner_subtract(
|
||||
vec![*left.clone()],
|
||||
vec![*right.clone()],
|
||||
Default::default(),
|
||||
exec_state,
|
||||
args,
|
||||
)
|
||||
.await?;
|
||||
return Ok(result.into());
|
||||
}
|
||||
} else if self.operator == BinaryOperator::And {
|
||||
// Check if we have solids.
|
||||
if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
|
||||
let args = crate::std::Args::new(Default::default(), self.into(), ctx.clone(), None);
|
||||
let result =
|
||||
crate::std::csg::inner_intersect(vec![*left.clone(), *right.clone()], exec_state, args).await?;
|
||||
let result = crate::std::csg::inner_intersect(
|
||||
vec![*left.clone(), *right.clone()],
|
||||
Default::default(),
|
||||
exec_state,
|
||||
args,
|
||||
)
|
||||
.await?;
|
||||
return Ok(result.into());
|
||||
}
|
||||
}
|
||||
|
@ -173,7 +173,7 @@ pub(super) fn format_from_annotations(
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"Unknown format for import, expected one of: {}",
|
||||
annotations::IMPORT_FORMAT_VALUES.join(", ")
|
||||
crate::IMPORT_FILE_EXTENSIONS.join(", ")
|
||||
),
|
||||
source_ranges: vec![p.as_source_range()],
|
||||
})
|
||||
|
@ -11,9 +11,7 @@ pub use cache::{bust_cache, clear_mem_cache};
|
||||
pub use cad_op::Operation;
|
||||
pub use geometry::*;
|
||||
pub use id_generator::IdGenerator;
|
||||
pub(crate) use import::{
|
||||
import_foreign, send_to_engine as send_import_to_engine, PreImportedGeometry, ZOO_COORD_SYSTEM,
|
||||
};
|
||||
pub(crate) use import::PreImportedGeometry;
|
||||
use indexmap::IndexMap;
|
||||
pub use kcl_value::{KclObjectFields, KclValue};
|
||||
use kcmc::{
|
||||
|
@ -131,11 +131,36 @@ pub mod pretty {
|
||||
pub use crate::{parsing::token::NumericSuffix, unparser::format_number};
|
||||
}
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
use clap::ValueEnum;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use crate::log::{log, logln};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
|
||||
pub static ref IMPORT_FILE_EXTENSIONS: Vec<String> = {
|
||||
let mut import_file_extensions = vec!["stp".to_string(), "glb".to_string(), "fbxb".to_string()];
|
||||
#[cfg(feature = "cli")]
|
||||
let named_extensions = kittycad::types::FileImportFormat::value_variants()
|
||||
.iter()
|
||||
.map(|x| format!("{}", x))
|
||||
.collect::<Vec<String>>();
|
||||
#[cfg(not(feature = "cli"))]
|
||||
let named_extensions = vec![]; // We don't really need this outside of the CLI.
|
||||
// Add all the default import formats.
|
||||
import_file_extensions.extend_from_slice(&named_extensions);
|
||||
import_file_extensions
|
||||
};
|
||||
|
||||
pub static ref RELEVANT_FILE_EXTENSIONS: Vec<String> = {
|
||||
let mut relevant_extensions = IMPORT_FILE_EXTENSIONS.clone();
|
||||
relevant_extensions.push("kcl".to_string());
|
||||
relevant_extensions
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Program {
|
||||
#[serde(flatten)]
|
||||
|
@ -3418,3 +3418,148 @@ async fn kcl_test_kcl_lsp_multi_file_error() {
|
||||
|
||||
server.executor_ctx().await.clone().unwrap().close().await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_kcl_lsp_on_hover_untitled_file_scheme() {
|
||||
let server = kcl_lsp_server(true).await.unwrap();
|
||||
|
||||
// Send open file.
|
||||
server
|
||||
.did_open(tower_lsp::lsp_types::DidOpenTextDocumentParams {
|
||||
text_document: tower_lsp::lsp_types::TextDocumentItem {
|
||||
uri: "untitled:Untitled-1".try_into().unwrap(),
|
||||
language_id: "kcl".to_string(),
|
||||
version: 1,
|
||||
text: r#"startSketchOn(XY)
|
||||
foo = 42
|
||||
foo
|
||||
|
||||
fn bar(x: string): string {
|
||||
return x
|
||||
}
|
||||
|
||||
bar("an arg")
|
||||
|
||||
startSketchOn(XY)
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line(end = [10, 0])
|
||||
|> line(end = [0, 10])
|
||||
"#
|
||||
.to_string(),
|
||||
},
|
||||
})
|
||||
.await;
|
||||
|
||||
// Std lib call
|
||||
let hover = server
|
||||
.hover(tower_lsp::lsp_types::HoverParams {
|
||||
text_document_position_params: tower_lsp::lsp_types::TextDocumentPositionParams {
|
||||
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
|
||||
uri: "untitled:Untitled-1".try_into().unwrap(),
|
||||
},
|
||||
position: tower_lsp::lsp_types::Position { line: 0, character: 2 },
|
||||
},
|
||||
work_done_progress_params: Default::default(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
match hover.unwrap().contents {
|
||||
tower_lsp::lsp_types::HoverContents::Markup(tower_lsp::lsp_types::MarkupContent { value, .. }) => {
|
||||
assert!(value.contains("startSketchOn"));
|
||||
assert!(value.contains(": SketchSurface"));
|
||||
assert!(value.contains("Start a new 2-dimensional sketch on a specific"));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
// Variable use
|
||||
let hover = server
|
||||
.hover(tower_lsp::lsp_types::HoverParams {
|
||||
text_document_position_params: tower_lsp::lsp_types::TextDocumentPositionParams {
|
||||
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
|
||||
uri: "untitled:Untitled-1".try_into().unwrap(),
|
||||
},
|
||||
position: tower_lsp::lsp_types::Position { line: 2, character: 1 },
|
||||
},
|
||||
work_done_progress_params: Default::default(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
match hover.unwrap().contents {
|
||||
tower_lsp::lsp_types::HoverContents::Markup(tower_lsp::lsp_types::MarkupContent { value, .. }) => {
|
||||
assert!(value.contains("foo: number = 42"));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
// User-defined function call.
|
||||
let hover = server
|
||||
.hover(tower_lsp::lsp_types::HoverParams {
|
||||
text_document_position_params: tower_lsp::lsp_types::TextDocumentPositionParams {
|
||||
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
|
||||
uri: "untitled:Untitled-1".try_into().unwrap(),
|
||||
},
|
||||
position: tower_lsp::lsp_types::Position { line: 8, character: 1 },
|
||||
},
|
||||
work_done_progress_params: Default::default(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
match hover.unwrap().contents {
|
||||
tower_lsp::lsp_types::HoverContents::Markup(tower_lsp::lsp_types::MarkupContent { value, .. }) => {
|
||||
assert!(value.contains("bar(x: string): string"));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
// Variable inside a function
|
||||
let hover = server
|
||||
.hover(tower_lsp::lsp_types::HoverParams {
|
||||
text_document_position_params: tower_lsp::lsp_types::TextDocumentPositionParams {
|
||||
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
|
||||
uri: "untitled:Untitled-1".try_into().unwrap(),
|
||||
},
|
||||
position: tower_lsp::lsp_types::Position { line: 5, character: 9 },
|
||||
},
|
||||
work_done_progress_params: Default::default(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
match hover.unwrap().contents {
|
||||
tower_lsp::lsp_types::HoverContents::Markup(tower_lsp::lsp_types::MarkupContent { value, .. }) => {
|
||||
assert!(value.contains("x: string"));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
// std function KwArg
|
||||
let hover = server
|
||||
.hover(tower_lsp::lsp_types::HoverParams {
|
||||
text_document_position_params: tower_lsp::lsp_types::TextDocumentPositionParams {
|
||||
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
|
||||
uri: "untitled:Untitled-1".try_into().unwrap(),
|
||||
},
|
||||
position: tower_lsp::lsp_types::Position {
|
||||
line: 12,
|
||||
character: 11,
|
||||
},
|
||||
},
|
||||
work_done_progress_params: Default::default(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
match hover.unwrap().contents {
|
||||
tower_lsp::lsp_types::HoverContents::Markup(tower_lsp::lsp_types::MarkupContent { value, .. }) => {
|
||||
assert!(value.contains("end?: [number]"));
|
||||
assert!(value.contains("How far away (along the X and Y axes) should this line go?"));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
server.executor_ctx().await.clone().unwrap().close().await;
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ use crate::{
|
||||
token::{Token, TokenSlice, TokenType},
|
||||
PIPE_OPERATOR, PIPE_SUBSTITUTION_OPERATOR,
|
||||
},
|
||||
SourceRange,
|
||||
SourceRange, IMPORT_FILE_EXTENSIONS,
|
||||
};
|
||||
|
||||
thread_local! {
|
||||
@ -1803,11 +1803,6 @@ fn import_stmt(i: &mut TokenSlice) -> PResult<BoxNode<ImportStatement>> {
|
||||
end = alias.end;
|
||||
*selector_alias = Some(alias);
|
||||
}
|
||||
|
||||
ParseContext::warn(CompilationError::err(
|
||||
SourceRange::new(start, path.end, path.module_id),
|
||||
"Importing a whole module is experimental, likely to be buggy, and likely to change",
|
||||
));
|
||||
}
|
||||
|
||||
let path_string = match path.inner.value {
|
||||
@ -1843,8 +1838,6 @@ fn import_stmt(i: &mut TokenSlice) -> PResult<BoxNode<ImportStatement>> {
|
||||
))
|
||||
}
|
||||
|
||||
const FOREIGN_IMPORT_EXTENSIONS: [&str; 8] = ["fbx", "gltf", "glb", "obj", "ply", "sldprt", "step", "stl"];
|
||||
|
||||
/// Validates the path string in an `import` statement.
|
||||
///
|
||||
/// `var_name` is `true` if the path will be used as a variable name.
|
||||
@ -1909,12 +1902,11 @@ fn validate_path_string(path_string: String, var_name: bool, path_range: SourceR
|
||||
|
||||
ImportPath::Std { path: segments }
|
||||
} else if path_string.contains('.') {
|
||||
// TODO should allow other extensions if there is a format attribute.
|
||||
let extn = &path_string[path_string.rfind('.').unwrap() + 1..];
|
||||
if !FOREIGN_IMPORT_EXTENSIONS.contains(&extn) {
|
||||
let extn = std::path::Path::new(&path_string).extension().unwrap_or_default();
|
||||
if !IMPORT_FILE_EXTENSIONS.contains(&extn.to_string_lossy().to_string()) {
|
||||
ParseContext::warn(CompilationError::err(
|
||||
path_range,
|
||||
format!("unsupported import path format. KCL files can be imported from the current project, CAD files with the following formats are supported: {}", FOREIGN_IMPORT_EXTENSIONS.join(", ")),
|
||||
format!("unsupported import path format. KCL files can be imported from the current project, CAD files with the following formats are supported: {}", IMPORT_FILE_EXTENSIONS.join(", ")),
|
||||
))
|
||||
}
|
||||
ImportPath::Foreign { path: path_string }
|
||||
@ -1922,7 +1914,7 @@ fn validate_path_string(path_string: String, var_name: bool, path_range: SourceR
|
||||
return Err(ErrMode::Cut(
|
||||
CompilationError::fatal(
|
||||
path_range,
|
||||
format!("unsupported import path format. KCL files can be imported from the current project, CAD files with the following formats are supported: {}", FOREIGN_IMPORT_EXTENSIONS.join(", ")),
|
||||
format!("unsupported import path format. KCL files can be imported from the current project, CAD files with the following formats are supported: {}", IMPORT_FILE_EXTENSIONS.join(", ")),
|
||||
)
|
||||
.into(),
|
||||
));
|
||||
@ -4498,21 +4490,9 @@ export fn cos(num: number(rad)): number(_) {}"#;
|
||||
|
||||
#[test]
|
||||
fn warn_import() {
|
||||
let some_program_string = r#"import "foo.kcl""#;
|
||||
let (_, errs) = assert_no_err(some_program_string);
|
||||
assert_eq!(errs.len(), 1, "{errs:#?}");
|
||||
|
||||
let some_program_string = r#"import "foo.obj""#;
|
||||
let (_, errs) = assert_no_err(some_program_string);
|
||||
assert_eq!(errs.len(), 1, "{errs:#?}");
|
||||
|
||||
let some_program_string = r#"import "foo.sldprt""#;
|
||||
let (_, errs) = assert_no_err(some_program_string);
|
||||
assert_eq!(errs.len(), 1, "{errs:#?}");
|
||||
|
||||
let some_program_string = r#"import "foo.bad""#;
|
||||
let (_, errs) = assert_no_err(some_program_string);
|
||||
assert_eq!(errs.len(), 2, "{errs:#?}");
|
||||
assert_eq!(errs.len(), 1, "{errs:#?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -664,10 +664,6 @@ impl Args {
|
||||
FromArgs::from_args(self, 0)
|
||||
}
|
||||
|
||||
pub(crate) fn get_import_data(&self) -> Result<(String, Option<crate::std::import::ImportFormat>), KclError> {
|
||||
FromArgs::from_args(self, 0)
|
||||
}
|
||||
|
||||
pub(crate) fn get_sketch_data_and_optional_tag(
|
||||
&self,
|
||||
) -> Result<(super::sketch::SketchData, Option<FaceTag>), KclError> {
|
||||
@ -1077,35 +1073,6 @@ macro_rules! let_field_of {
|
||||
};
|
||||
}
|
||||
|
||||
impl<'a> FromKclValue<'a> for crate::std::import::ImportFormat {
|
||||
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
||||
let obj = arg.as_object()?;
|
||||
let_field_of!(obj, typ "format");
|
||||
match typ {
|
||||
"fbx" => Some(Self::Fbx {}),
|
||||
"gltf" => Some(Self::Gltf {}),
|
||||
"sldprt" => Some(Self::Sldprt {}),
|
||||
"step" => Some(Self::Step {}),
|
||||
"stl" => {
|
||||
let_field_of!(obj, coords?);
|
||||
let_field_of!(obj, units);
|
||||
Some(Self::Stl { coords, units })
|
||||
}
|
||||
"obj" => {
|
||||
let_field_of!(obj, coords?);
|
||||
let_field_of!(obj, units);
|
||||
Some(Self::Obj { coords, units })
|
||||
}
|
||||
"ply" => {
|
||||
let_field_of!(obj, coords?);
|
||||
let_field_of!(obj, units);
|
||||
Some(Self::Ply { coords, units })
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromKclValue<'a> for super::sketch::AngledLineThatIntersectsData {
|
||||
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
||||
let obj = arg.as_object()?;
|
||||
|
@ -2,6 +2,13 @@
|
||||
|
||||
use anyhow::Result;
|
||||
use kcl_derive_docs::stdlib;
|
||||
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, ModelingCmd};
|
||||
use kittycad_modeling_cmds::{
|
||||
self as kcmc,
|
||||
ok_response::OkModelingCmdResponse,
|
||||
output::{BooleanIntersection, BooleanSubtract, BooleanUnion},
|
||||
websocket::OkWebSocketResponseData,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
@ -9,10 +16,13 @@ use crate::{
|
||||
std::Args,
|
||||
};
|
||||
|
||||
use super::DEFAULT_TOLERANCE;
|
||||
|
||||
/// Union two or more solids into a single solid.
|
||||
pub async fn union(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let solids: Vec<Solid> =
|
||||
args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::Union(vec![RuntimeType::solids()]), exec_state)?;
|
||||
let tolerance = args.get_kw_arg_opt("tolerance")?;
|
||||
|
||||
if solids.len() < 2 {
|
||||
return Err(KclError::UndefinedValue(KclErrorDetails {
|
||||
@ -21,7 +31,7 @@ pub async fn union(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
}));
|
||||
}
|
||||
|
||||
let solids = inner_union(solids, exec_state, args).await?;
|
||||
let solids = inner_union(solids, tolerance, exec_state, args).await?;
|
||||
Ok(solids.into())
|
||||
}
|
||||
|
||||
@ -30,18 +40,19 @@ pub async fn union(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
/// ```no_run
|
||||
/// // Union two cubes using the stdlib functions.
|
||||
///
|
||||
/// fn cube(center) {
|
||||
/// fn cube(center, size) {
|
||||
/// return startSketchOn('XY')
|
||||
/// |> startProfileAt([center[0] - 10, center[1] - 10], %)
|
||||
/// |> line(endAbsolute = [center[0] + 10, center[1] - 10])
|
||||
/// |> line(endAbsolute = [center[0] + 10, center[1] + 10])
|
||||
/// |> line(endAbsolute = [center[0] - 10, center[1] + 10])
|
||||
/// |> startProfileAt([center[0] - size, center[1] - size], %)
|
||||
/// |> line(endAbsolute = [center[0] + size, center[1] - size])
|
||||
/// |> line(endAbsolute = [center[0] + size, center[1] + size])
|
||||
/// |> line(endAbsolute = [center[0] - size, center[1] + size])
|
||||
/// |> close()
|
||||
/// |> extrude(length = 10)
|
||||
/// }
|
||||
///
|
||||
/// part001 = cube([0, 0])
|
||||
/// part002 = cube([20, 10])
|
||||
/// part001 = cube([0, 0], 10)
|
||||
/// part002 = cube([7, 3], 5)
|
||||
/// |> translate(z = 1)
|
||||
///
|
||||
/// unionedPart = union([part001, part002])
|
||||
/// ```
|
||||
@ -51,18 +62,19 @@ pub async fn union(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
/// // NOTE: This will not work when using codemods through the UI.
|
||||
/// // Codemods will generate the stdlib function call instead.
|
||||
///
|
||||
/// fn cube(center) {
|
||||
/// fn cube(center, size) {
|
||||
/// return startSketchOn('XY')
|
||||
/// |> startProfileAt([center[0] - 10, center[1] - 10], %)
|
||||
/// |> line(endAbsolute = [center[0] + 10, center[1] - 10])
|
||||
/// |> line(endAbsolute = [center[0] + 10, center[1] + 10])
|
||||
/// |> line(endAbsolute = [center[0] - 10, center[1] + 10])
|
||||
/// |> startProfileAt([center[0] - size, center[1] - size], %)
|
||||
/// |> line(endAbsolute = [center[0] + size, center[1] - size])
|
||||
/// |> line(endAbsolute = [center[0] + size, center[1] + size])
|
||||
/// |> line(endAbsolute = [center[0] - size, center[1] + size])
|
||||
/// |> close()
|
||||
/// |> extrude(length = 10)
|
||||
/// }
|
||||
///
|
||||
/// part001 = cube([0, 0])
|
||||
/// part002 = cube([20, 10])
|
||||
/// part001 = cube([0, 0], 10)
|
||||
/// part002 = cube([7, 3], 5)
|
||||
/// |> translate(z = 1)
|
||||
///
|
||||
/// // This is the equivalent of: union([part001, part002])
|
||||
/// unionedPart = part001 + part002
|
||||
@ -73,18 +85,19 @@ pub async fn union(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
/// // NOTE: This will not work when using codemods through the UI.
|
||||
/// // Codemods will generate the stdlib function call instead.
|
||||
///
|
||||
/// fn cube(center) {
|
||||
/// fn cube(center, size) {
|
||||
/// return startSketchOn('XY')
|
||||
/// |> startProfileAt([center[0] - 10, center[1] - 10], %)
|
||||
/// |> line(endAbsolute = [center[0] + 10, center[1] - 10])
|
||||
/// |> line(endAbsolute = [center[0] + 10, center[1] + 10])
|
||||
/// |> line(endAbsolute = [center[0] - 10, center[1] + 10])
|
||||
/// |> startProfileAt([center[0] - size, center[1] - size], %)
|
||||
/// |> line(endAbsolute = [center[0] + size, center[1] - size])
|
||||
/// |> line(endAbsolute = [center[0] + size, center[1] + size])
|
||||
/// |> line(endAbsolute = [center[0] - size, center[1] + size])
|
||||
/// |> close()
|
||||
/// |> extrude(length = 10)
|
||||
/// }
|
||||
///
|
||||
/// part001 = cube([0, 0])
|
||||
/// part002 = cube([20, 10])
|
||||
/// part001 = cube([0, 0], 10)
|
||||
/// part002 = cube([7, 3], 5)
|
||||
/// |> translate(z = 1)
|
||||
///
|
||||
/// // This is the equivalent of: union([part001, part002])
|
||||
/// // Programmers will understand `|` as a union operation, but mechanical engineers
|
||||
@ -96,31 +109,64 @@ pub async fn union(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
feature_tree_operation = true,
|
||||
keywords = true,
|
||||
unlabeled_first = true,
|
||||
deprecated = true,
|
||||
args = {
|
||||
solids = {docs = "The solids to union."},
|
||||
tolerance = {docs = "The tolerance to use for the union operation."},
|
||||
}
|
||||
}]
|
||||
pub(crate) async fn inner_union(
|
||||
solids: Vec<Solid>,
|
||||
tolerance: Option<f64>,
|
||||
exec_state: &mut ExecState,
|
||||
args: Args,
|
||||
) -> Result<Vec<Solid>, KclError> {
|
||||
let solid_out_id = exec_state.next_uuid();
|
||||
|
||||
let mut solid = solids[0].clone();
|
||||
solid.id = solid_out_id;
|
||||
let mut new_solids = vec![solid.clone()];
|
||||
|
||||
if args.ctx.no_engine_commands().await {
|
||||
return Ok(new_solids);
|
||||
}
|
||||
|
||||
// Flush the fillets for the solids.
|
||||
args.flush_batch_for_solids(exec_state, &solids).await?;
|
||||
|
||||
// TODO: call the engine union operation.
|
||||
// TODO: figure out all the shit after for the faces etc.
|
||||
let result = args
|
||||
.send_modeling_cmd(
|
||||
solid_out_id,
|
||||
ModelingCmd::from(mcmd::BooleanUnion {
|
||||
solid_ids: solids.iter().map(|s| s.id).collect(),
|
||||
tolerance: LengthUnit(tolerance.unwrap_or(DEFAULT_TOLERANCE)),
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// For now just return the first solid.
|
||||
// Til we have a proper implementation.
|
||||
Ok(vec![solids[0].clone()])
|
||||
let OkWebSocketResponseData::Modeling {
|
||||
modeling_response: OkModelingCmdResponse::BooleanUnion(BooleanUnion { extra_solid_ids }),
|
||||
} = result
|
||||
else {
|
||||
return Err(KclError::Internal(KclErrorDetails {
|
||||
message: "Failed to get the result of the union operation.".to_string(),
|
||||
source_ranges: vec![args.source_range],
|
||||
}));
|
||||
};
|
||||
|
||||
// If we have more solids, set those as well.
|
||||
if !extra_solid_ids.is_empty() {
|
||||
solid.id = extra_solid_ids[0];
|
||||
new_solids.push(solid.clone());
|
||||
}
|
||||
|
||||
Ok(new_solids)
|
||||
}
|
||||
|
||||
/// Intersect returns the shared volume between multiple solids, preserving only
|
||||
/// overlapping regions.
|
||||
pub async fn intersect(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let solids: Vec<Solid> = args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::solids(), exec_state)?;
|
||||
let tolerance = args.get_kw_arg_opt("tolerance")?;
|
||||
|
||||
if solids.len() < 2 {
|
||||
return Err(KclError::UndefinedValue(KclErrorDetails {
|
||||
@ -129,7 +175,7 @@ pub async fn intersect(exec_state: &mut ExecState, args: Args) -> Result<KclValu
|
||||
}));
|
||||
}
|
||||
|
||||
let solids = inner_intersect(solids, exec_state, args).await?;
|
||||
let solids = inner_intersect(solids, tolerance, exec_state, args).await?;
|
||||
Ok(solids.into())
|
||||
}
|
||||
|
||||
@ -144,18 +190,19 @@ pub async fn intersect(exec_state: &mut ExecState, args: Args) -> Result<KclValu
|
||||
/// ```no_run
|
||||
/// // Intersect two cubes using the stdlib functions.
|
||||
///
|
||||
/// fn cube(center) {
|
||||
/// fn cube(center, size) {
|
||||
/// return startSketchOn('XY')
|
||||
/// |> startProfileAt([center[0] - 10, center[1] - 10], %)
|
||||
/// |> line(endAbsolute = [center[0] + 10, center[1] - 10])
|
||||
/// |> line(endAbsolute = [center[0] + 10, center[1] + 10])
|
||||
/// |> line(endAbsolute = [center[0] - 10, center[1] + 10])
|
||||
/// |> startProfileAt([center[0] - size, center[1] - size], %)
|
||||
/// |> line(endAbsolute = [center[0] + size, center[1] - size])
|
||||
/// |> line(endAbsolute = [center[0] + size, center[1] + size])
|
||||
/// |> line(endAbsolute = [center[0] - size, center[1] + size])
|
||||
/// |> close()
|
||||
/// |> extrude(length = 10)
|
||||
/// }
|
||||
///
|
||||
/// part001 = cube([0, 0])
|
||||
/// part002 = cube([8, 8])
|
||||
/// part001 = cube([0, 0], 10)
|
||||
/// part002 = cube([7, 3], 5)
|
||||
/// |> translate(z = 1)
|
||||
///
|
||||
/// intersectedPart = intersect([part001, part002])
|
||||
/// ```
|
||||
@ -165,18 +212,19 @@ pub async fn intersect(exec_state: &mut ExecState, args: Args) -> Result<KclValu
|
||||
/// // NOTE: This will not work when using codemods through the UI.
|
||||
/// // Codemods will generate the stdlib function call instead.
|
||||
///
|
||||
/// fn cube(center) {
|
||||
/// fn cube(center, size) {
|
||||
/// return startSketchOn('XY')
|
||||
/// |> startProfileAt([center[0] - 10, center[1] - 10], %)
|
||||
/// |> line(endAbsolute = [center[0] + 10, center[1] - 10])
|
||||
/// |> line(endAbsolute = [center[0] + 10, center[1] + 10])
|
||||
/// |> line(endAbsolute = [center[0] - 10, center[1] + 10])
|
||||
/// |> startProfileAt([center[0] - size, center[1] - size], %)
|
||||
/// |> line(endAbsolute = [center[0] + size, center[1] - size])
|
||||
/// |> line(endAbsolute = [center[0] + size, center[1] + size])
|
||||
/// |> line(endAbsolute = [center[0] - size, center[1] + size])
|
||||
/// |> close()
|
||||
/// |> extrude(length = 10)
|
||||
/// }
|
||||
///
|
||||
/// part001 = cube([0, 0])
|
||||
/// part002 = cube([8, 8])
|
||||
/// part001 = cube([0, 0], 10)
|
||||
/// part002 = cube([7, 3], 5)
|
||||
/// |> translate(z = 1)
|
||||
///
|
||||
/// // This is the equivalent of: intersect([part001, part002])
|
||||
/// intersectedPart = part001 & part002
|
||||
@ -186,25 +234,57 @@ pub async fn intersect(exec_state: &mut ExecState, args: Args) -> Result<KclValu
|
||||
feature_tree_operation = true,
|
||||
keywords = true,
|
||||
unlabeled_first = true,
|
||||
deprecated = true,
|
||||
args = {
|
||||
solids = {docs = "The solids to intersect."},
|
||||
tolerance = {docs = "The tolerance to use for the intersection operation."},
|
||||
}
|
||||
}]
|
||||
pub(crate) async fn inner_intersect(
|
||||
solids: Vec<Solid>,
|
||||
tolerance: Option<f64>,
|
||||
exec_state: &mut ExecState,
|
||||
args: Args,
|
||||
) -> Result<Vec<Solid>, KclError> {
|
||||
let solid_out_id = exec_state.next_uuid();
|
||||
|
||||
let mut solid = solids[0].clone();
|
||||
solid.id = solid_out_id;
|
||||
let mut new_solids = vec![solid.clone()];
|
||||
|
||||
if args.ctx.no_engine_commands().await {
|
||||
return Ok(new_solids);
|
||||
}
|
||||
|
||||
// Flush the fillets for the solids.
|
||||
args.flush_batch_for_solids(exec_state, &solids).await?;
|
||||
|
||||
// TODO: call the engine union operation.
|
||||
// TODO: figure out all the shit after for the faces etc.
|
||||
let result = args
|
||||
.send_modeling_cmd(
|
||||
solid_out_id,
|
||||
ModelingCmd::from(mcmd::BooleanIntersection {
|
||||
solid_ids: solids.iter().map(|s| s.id).collect(),
|
||||
tolerance: LengthUnit(tolerance.unwrap_or(DEFAULT_TOLERANCE)),
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// For now just return the first solid.
|
||||
// Til we have a proper implementation.
|
||||
Ok(vec![solids[0].clone()])
|
||||
let OkWebSocketResponseData::Modeling {
|
||||
modeling_response: OkModelingCmdResponse::BooleanIntersection(BooleanIntersection { extra_solid_ids }),
|
||||
} = result
|
||||
else {
|
||||
return Err(KclError::Internal(KclErrorDetails {
|
||||
message: "Failed to get the result of the intersection operation.".to_string(),
|
||||
source_ranges: vec![args.source_range],
|
||||
}));
|
||||
};
|
||||
|
||||
// If we have more solids, set those as well.
|
||||
if !extra_solid_ids.is_empty() {
|
||||
solid.id = extra_solid_ids[0];
|
||||
new_solids.push(solid.clone());
|
||||
}
|
||||
|
||||
Ok(new_solids)
|
||||
}
|
||||
|
||||
/// Subtract removes tool solids from base solids, leaving the remaining material.
|
||||
@ -212,7 +292,23 @@ pub async fn subtract(exec_state: &mut ExecState, args: Args) -> Result<KclValue
|
||||
let solids: Vec<Solid> = args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::solids(), exec_state)?;
|
||||
let tools: Vec<Solid> = args.get_kw_arg_typed("tools", &RuntimeType::solids(), exec_state)?;
|
||||
|
||||
let solids = inner_subtract(solids, tools, exec_state, args).await?;
|
||||
if solids.len() > 1 {
|
||||
return Err(KclError::UndefinedValue(KclErrorDetails {
|
||||
message: "Only one solid is allowed for a subtract operation, currently.".to_string(),
|
||||
source_ranges: vec![args.source_range],
|
||||
}));
|
||||
}
|
||||
|
||||
if tools.len() > 1 {
|
||||
return Err(KclError::UndefinedValue(KclErrorDetails {
|
||||
message: "Only one tool is allowed for a subtract operation, currently.".to_string(),
|
||||
source_ranges: vec![args.source_range],
|
||||
}));
|
||||
}
|
||||
|
||||
let tolerance = args.get_kw_arg_opt("tolerance")?;
|
||||
|
||||
let solids = inner_subtract(solids, tools, tolerance, exec_state, args).await?;
|
||||
Ok(solids.into())
|
||||
}
|
||||
|
||||
@ -227,20 +323,19 @@ pub async fn subtract(exec_state: &mut ExecState, args: Args) -> Result<KclValue
|
||||
/// ```no_run
|
||||
/// // Subtract a cylinder from a cube using the stdlib functions.
|
||||
///
|
||||
/// fn cube(center) {
|
||||
/// fn cube(center, size) {
|
||||
/// return startSketchOn('XY')
|
||||
/// |> startProfileAt([center[0] - 10, center[1] - 10], %)
|
||||
/// |> line(endAbsolute = [center[0] + 10, center[1] - 10])
|
||||
/// |> line(endAbsolute = [center[0] + 10, center[1] + 10])
|
||||
/// |> line(endAbsolute = [center[0] - 10, center[1] + 10])
|
||||
/// |> startProfileAt([center[0] - size, center[1] - size], %)
|
||||
/// |> line(endAbsolute = [center[0] + size, center[1] - size])
|
||||
/// |> line(endAbsolute = [center[0] + size, center[1] + size])
|
||||
/// |> line(endAbsolute = [center[0] - size, center[1] + size])
|
||||
/// |> close()
|
||||
/// |> extrude(length = 10)
|
||||
/// }
|
||||
///
|
||||
/// part001 = cube([0, 0])
|
||||
/// part002 = startSketchOn('XY')
|
||||
/// |> circle(center = [0, 0], radius = 2)
|
||||
/// |> extrude(length = 10)
|
||||
/// part001 = cube([0, 0], 10)
|
||||
/// part002 = cube([7, 3], 5)
|
||||
/// |> translate(z = 1)
|
||||
///
|
||||
/// subtractedPart = subtract([part001], tools=[part002])
|
||||
/// ```
|
||||
@ -250,20 +345,19 @@ pub async fn subtract(exec_state: &mut ExecState, args: Args) -> Result<KclValue
|
||||
/// // NOTE: This will not work when using codemods through the UI.
|
||||
/// // Codemods will generate the stdlib function call instead.
|
||||
///
|
||||
/// fn cube(center) {
|
||||
/// fn cube(center, size) {
|
||||
/// return startSketchOn('XY')
|
||||
/// |> startProfileAt([center[0] - 10, center[1] - 10], %)
|
||||
/// |> line(endAbsolute = [center[0] + 10, center[1] - 10])
|
||||
/// |> line(endAbsolute = [center[0] + 10, center[1] + 10])
|
||||
/// |> line(endAbsolute = [center[0] - 10, center[1] + 10])
|
||||
/// |> startProfileAt([center[0] - size, center[1] - size], %)
|
||||
/// |> line(endAbsolute = [center[0] + size, center[1] - size])
|
||||
/// |> line(endAbsolute = [center[0] + size, center[1] + size])
|
||||
/// |> line(endAbsolute = [center[0] - size, center[1] + size])
|
||||
/// |> close()
|
||||
/// |> extrude(length = 10)
|
||||
/// }
|
||||
///
|
||||
/// part001 = cube([0, 0])
|
||||
/// part002 = startSketchOn('XY')
|
||||
/// |> circle(center = [0, 0], radius = 2)
|
||||
/// |> extrude(length = 10)
|
||||
/// part001 = cube([0, 0], 10)
|
||||
/// part002 = cube([7, 3], 5)
|
||||
/// |> translate(z = 1)
|
||||
///
|
||||
/// // This is the equivalent of: subtract([part001], tools=[part002])
|
||||
/// subtractedPart = part001 - part002
|
||||
@ -273,26 +367,59 @@ pub async fn subtract(exec_state: &mut ExecState, args: Args) -> Result<KclValue
|
||||
feature_tree_operation = true,
|
||||
keywords = true,
|
||||
unlabeled_first = true,
|
||||
deprecated = true,
|
||||
args = {
|
||||
solids = {docs = "The solids to use as the base to subtract from."},
|
||||
tools = {docs = "The solids to subtract."},
|
||||
tolerance = {docs = "The tolerance to use for the subtraction operation."},
|
||||
}
|
||||
}]
|
||||
pub(crate) async fn inner_subtract(
|
||||
solids: Vec<Solid>,
|
||||
tools: Vec<Solid>,
|
||||
tolerance: Option<f64>,
|
||||
exec_state: &mut ExecState,
|
||||
args: Args,
|
||||
) -> Result<Vec<Solid>, KclError> {
|
||||
let solid_out_id = exec_state.next_uuid();
|
||||
|
||||
let mut solid = solids[0].clone();
|
||||
solid.id = solid_out_id;
|
||||
let mut new_solids = vec![solid.clone()];
|
||||
|
||||
if args.ctx.no_engine_commands().await {
|
||||
return Ok(new_solids);
|
||||
}
|
||||
|
||||
// Flush the fillets for the solids and the tools.
|
||||
let combined_solids = solids.iter().chain(tools.iter()).cloned().collect::<Vec<Solid>>();
|
||||
args.flush_batch_for_solids(exec_state, &combined_solids).await?;
|
||||
|
||||
// TODO: call the engine union operation.
|
||||
// TODO: figure out all the shit after for the faces etc.
|
||||
let result = args
|
||||
.send_modeling_cmd(
|
||||
solid_out_id,
|
||||
ModelingCmd::from(mcmd::BooleanSubtract {
|
||||
target_ids: solids.iter().map(|s| s.id).collect(),
|
||||
tool_ids: tools.iter().map(|s| s.id).collect(),
|
||||
tolerance: LengthUnit(tolerance.unwrap_or(DEFAULT_TOLERANCE)),
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// For now just return the first solid.
|
||||
// Til we have a proper implementation.
|
||||
Ok(vec![solids[0].clone()])
|
||||
let OkWebSocketResponseData::Modeling {
|
||||
modeling_response: OkModelingCmdResponse::BooleanSubtract(BooleanSubtract { extra_solid_ids }),
|
||||
} = result
|
||||
else {
|
||||
return Err(KclError::Internal(KclErrorDetails {
|
||||
message: "Failed to get the result of the subtract operation.".to_string(),
|
||||
source_ranges: vec![args.source_range],
|
||||
}));
|
||||
};
|
||||
|
||||
// If we have more solids, set those as well.
|
||||
if !extra_solid_ids.is_empty() {
|
||||
solid.id = extra_solid_ids[0];
|
||||
new_solids.push(solid.clone());
|
||||
}
|
||||
|
||||
Ok(new_solids)
|
||||
}
|
||||
|
@ -1,181 +0,0 @@
|
||||
//! Standard library functions involved in importing files.
|
||||
|
||||
use anyhow::Result;
|
||||
use kcl_derive_docs::stdlib;
|
||||
use kcmc::{coord::System, format::InputFormat3d, units::UnitLength};
|
||||
use kittycad_modeling_cmds as kcmc;
|
||||
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{import_foreign, send_import_to_engine, ExecState, ImportedGeometry, KclValue, ZOO_COORD_SYSTEM},
|
||||
std::Args,
|
||||
};
|
||||
|
||||
/// Import format specifier
|
||||
#[derive(serde :: Serialize, serde :: Deserialize, PartialEq, Debug, Clone, schemars :: JsonSchema)]
|
||||
#[cfg_attr(feature = "tabled", derive(tabled::Tabled))]
|
||||
#[serde(tag = "format")]
|
||||
pub enum ImportFormat {
|
||||
/// Autodesk Filmbox (FBX) format
|
||||
#[serde(rename = "fbx")]
|
||||
Fbx {},
|
||||
/// Binary glTF 2.0. We refer to this as glTF since that is how our customers refer to
|
||||
/// it, but this can also import binary glTF (glb).
|
||||
#[serde(rename = "gltf")]
|
||||
Gltf {},
|
||||
/// Wavefront OBJ format.
|
||||
#[serde(rename = "obj")]
|
||||
Obj {
|
||||
/// Co-ordinate system of input data.
|
||||
/// Defaults to the [KittyCAD co-ordinate system.
|
||||
coords: Option<System>,
|
||||
/// The units of the input data. This is very important for correct scaling and when
|
||||
/// calculating physics properties like mass, etc.
|
||||
/// Defaults to millimeters.
|
||||
units: UnitLength,
|
||||
},
|
||||
/// The PLY Polygon File Format.
|
||||
#[serde(rename = "ply")]
|
||||
Ply {
|
||||
/// Co-ordinate system of input data.
|
||||
/// Defaults to the [KittyCAD co-ordinate system.
|
||||
coords: Option<System>,
|
||||
/// The units of the input data. This is very important for correct scaling and when
|
||||
/// calculating physics properties like mass, etc.
|
||||
/// Defaults to millimeters.
|
||||
units: UnitLength,
|
||||
},
|
||||
/// SolidWorks part (SLDPRT) format.
|
||||
#[serde(rename = "sldprt")]
|
||||
Sldprt {},
|
||||
/// ISO 10303-21 (STEP) format.
|
||||
#[serde(rename = "step")]
|
||||
Step {},
|
||||
/// *ST**ereo**L**ithography format.
|
||||
#[serde(rename = "stl")]
|
||||
Stl {
|
||||
/// Co-ordinate system of input data.
|
||||
/// Defaults to the [KittyCAD co-ordinate system.
|
||||
coords: Option<System>,
|
||||
/// The units of the input data. This is very important for correct scaling and when
|
||||
/// calculating physics properties like mass, etc.
|
||||
/// Defaults to millimeters.
|
||||
units: UnitLength,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<ImportFormat> for InputFormat3d {
|
||||
fn from(format: ImportFormat) -> Self {
|
||||
match format {
|
||||
ImportFormat::Fbx {} => InputFormat3d::Fbx(Default::default()),
|
||||
ImportFormat::Gltf {} => InputFormat3d::Gltf(Default::default()),
|
||||
ImportFormat::Obj { coords, units } => InputFormat3d::Obj(kcmc::format::obj::import::Options {
|
||||
coords: coords.unwrap_or(ZOO_COORD_SYSTEM),
|
||||
units,
|
||||
}),
|
||||
ImportFormat::Ply { coords, units } => InputFormat3d::Ply(kcmc::format::ply::import::Options {
|
||||
coords: coords.unwrap_or(ZOO_COORD_SYSTEM),
|
||||
units,
|
||||
}),
|
||||
ImportFormat::Sldprt {} => InputFormat3d::Sldprt(kcmc::format::sldprt::import::Options {
|
||||
split_closed_faces: false,
|
||||
}),
|
||||
ImportFormat::Step {} => InputFormat3d::Step(kcmc::format::step::import::Options {
|
||||
split_closed_faces: false,
|
||||
}),
|
||||
ImportFormat::Stl { coords, units } => InputFormat3d::Stl(kcmc::format::stl::import::Options {
|
||||
coords: coords.unwrap_or(ZOO_COORD_SYSTEM),
|
||||
units,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Import a CAD file.
|
||||
/// For formats lacking unit data (STL, OBJ, PLY), the default import unit is millimeters.
|
||||
/// Otherwise you can specify the unit by passing in the options parameter.
|
||||
/// If you import a gltf file, we will try to find the bin file and import it as well.
|
||||
///
|
||||
/// Import paths are relative to the current project directory. This only works in the desktop app
|
||||
/// not in browser.
|
||||
pub async fn import(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let (file_path, options): (String, Option<ImportFormat>) = args.get_import_data()?;
|
||||
|
||||
let imported_geometry = inner_import(file_path, options, exec_state, args).await?;
|
||||
Ok(KclValue::ImportedGeometry(imported_geometry))
|
||||
}
|
||||
|
||||
/// Import a CAD file.
|
||||
///
|
||||
/// **DEPRECATED** Prefer to use import statements.
|
||||
///
|
||||
/// For formats lacking unit data (such as STL, OBJ, or PLY files), the default
|
||||
/// unit of measurement is millimeters. Alternatively you may specify the unit
|
||||
/// by passing your desired measurement unit in the options parameter. When
|
||||
/// importing a GLTF file, the bin file will be imported as well. Import paths
|
||||
/// are relative to the current project directory.
|
||||
///
|
||||
/// Note: The import command currently only works when using the native
|
||||
/// Design Studio.
|
||||
///
|
||||
/// ```no_run
|
||||
/// model = import("tests/inputs/cube.obj")
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// model = import("tests/inputs/cube.obj", {format: "obj", units: "m"})
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// model = import("tests/inputs/cube.gltf")
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// model = import("tests/inputs/cube.sldprt")
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// model = import("tests/inputs/cube.step")
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// import height, buildSketch from 'common.kcl'
|
||||
///
|
||||
/// plane = 'XZ'
|
||||
/// margin = 2
|
||||
/// s1 = buildSketch(plane, [0, 0])
|
||||
/// s2 = buildSketch(plane, [0, height() + margin])
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "import",
|
||||
feature_tree_operation = true,
|
||||
deprecated = true,
|
||||
tags = [],
|
||||
}]
|
||||
async fn inner_import(
|
||||
file_path: String,
|
||||
options: Option<ImportFormat>,
|
||||
exec_state: &mut ExecState,
|
||||
args: Args,
|
||||
) -> Result<ImportedGeometry, KclError> {
|
||||
if file_path.is_empty() {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: "No file path was provided.".to_string(),
|
||||
source_ranges: vec![args.source_range],
|
||||
}));
|
||||
}
|
||||
|
||||
let format = options.map(InputFormat3d::from);
|
||||
send_import_to_engine(
|
||||
import_foreign(
|
||||
std::path::Path::new(&file_path),
|
||||
format,
|
||||
exec_state,
|
||||
&args.ctx,
|
||||
args.source_range,
|
||||
)
|
||||
.await?,
|
||||
&args.ctx,
|
||||
)
|
||||
.await
|
||||
}
|
@ -12,7 +12,6 @@ pub mod edge;
|
||||
pub mod extrude;
|
||||
pub mod fillet;
|
||||
pub mod helix;
|
||||
pub mod import;
|
||||
pub mod loft;
|
||||
pub mod math;
|
||||
pub mod mirror;
|
||||
@ -111,7 +110,6 @@ lazy_static! {
|
||||
Box::new(crate::std::sweep::Sweep),
|
||||
Box::new(crate::std::loft::Loft),
|
||||
Box::new(crate::std::planes::OffsetPlane),
|
||||
Box::new(crate::std::import::Import),
|
||||
Box::new(crate::std::math::Acos),
|
||||
Box::new(crate::std::math::Asin),
|
||||
Box::new(crate::std::math::Atan),
|
||||
|
@ -234,40 +234,37 @@ pub fn is_on_circumference(center: Point2d, point: Point2d, radius: f64) -> bool
|
||||
(distance_squared - radius.powi(2)).abs() < 1e-9
|
||||
}
|
||||
|
||||
// Calculate the center of 3 points
|
||||
// To calculate the center of the 3 point circle 2 perpendicular lines are created
|
||||
// These perpendicular lines will intersect at the center of the circle.
|
||||
// Calculate the center of 3 points using an algebraic method
|
||||
// Handles if 3 points lie on the same line (collinear) by returning the average of the points (could return None instead..)
|
||||
pub fn calculate_circle_center(p1: [f64; 2], p2: [f64; 2], p3: [f64; 2]) -> [f64; 2] {
|
||||
// y2 - y1
|
||||
let y_2_1 = p2[1] - p1[1];
|
||||
// y3 - y2
|
||||
let y_3_2 = p3[1] - p2[1];
|
||||
// x2 - x1
|
||||
let x_2_1 = p2[0] - p1[0];
|
||||
// x3 - x2
|
||||
let x_3_2 = p3[0] - p2[0];
|
||||
let (x1, y1) = (p1[0], p1[1]);
|
||||
let (x2, y2) = (p2[0], p2[1]);
|
||||
let (x3, y3) = (p3[0], p3[1]);
|
||||
|
||||
// Slope of two perpendicular lines
|
||||
let slope_a = y_2_1 / x_2_1;
|
||||
let slope_b = y_3_2 / x_3_2;
|
||||
// Compute the determinant d = 2 * (x1*(y2-y3) + x2*(y3-y1) + x3*(y1-y2))
|
||||
// Visually d is twice the area of the triangle formed by the points,
|
||||
// also the same as: cross(p2 - p1, p3 - p1)
|
||||
let d = 2.0 * (x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2));
|
||||
|
||||
// Values for line intersection
|
||||
// y1 - y3
|
||||
let y_1_3 = p1[1] - p3[1];
|
||||
// x1 + x2
|
||||
let x_1_2 = p1[0] + p2[0];
|
||||
// x2 + x3
|
||||
let x_2_3 = p2[0] + p3[0];
|
||||
// y1 + y2
|
||||
let y_1_2 = p1[1] + p2[1];
|
||||
// If d is nearly zero, the points are collinear, and a unique circle cannot be defined.
|
||||
if d.abs() < f64::EPSILON {
|
||||
return [(x1 + x2 + x3) / 3.0, (y1 + y2 + y3) / 3.0];
|
||||
}
|
||||
|
||||
// Solve for the intersection of these two lines
|
||||
let numerator = (slope_a * slope_b * y_1_3) + (slope_b * x_1_2) - (slope_a * x_2_3);
|
||||
let x = numerator / (2.0 * (slope_b - slope_a));
|
||||
// squared lengths
|
||||
let p1_sq = x1 * x1 + y1 * y1;
|
||||
let p2_sq = x2 * x2 + y2 * y2;
|
||||
let p3_sq = x3 * x3 + y3 * y3;
|
||||
|
||||
let y = ((-1.0 / slope_a) * (x - (x_1_2 / 2.0))) + (y_1_2 / 2.0);
|
||||
|
||||
[x, y]
|
||||
// This formula is derived from the circle equations:
|
||||
// (x - cx)^2 + (y - cy)^2 = r^2
|
||||
// All 3 points will satisfy this equation, so we have 3 equations. Radius can be eliminated
|
||||
// by subtracting one of the equations from the other two and the remaining 2 equations can
|
||||
// be solved for cx and cy.
|
||||
[
|
||||
(p1_sq * (y2 - y3) + p2_sq * (y3 - y1) + p3_sq * (y1 - y2)) / d,
|
||||
(p1_sq * (x3 - x2) + p2_sq * (x1 - x3) + p3_sq * (x2 - x1)) / d,
|
||||
]
|
||||
}
|
||||
|
||||
pub struct CircleParams {
|
||||
@ -286,9 +283,11 @@ pub fn calculate_circle_from_3_points(points: [Point2d; 3]) -> CircleParams {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// Here you can bring your functions into scope
|
||||
use approx::assert_relative_eq;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::f64::consts::TAU;
|
||||
|
||||
use super::{get_x_component, get_y_component, Angle};
|
||||
use super::{calculate_circle_center, get_x_component, get_y_component, Angle};
|
||||
use crate::SourceRange;
|
||||
|
||||
static EACH_QUAD: [(i32, [i32; 2]); 12] = [
|
||||
@ -453,6 +452,75 @@ mod tests {
|
||||
assert_eq!(angle_start.to_degrees().round(), 0.0);
|
||||
assert_eq!(angle_end.to_degrees().round(), 180.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calculate_circle_center() {
|
||||
const EPS: f64 = 1e-4;
|
||||
|
||||
// Test: circle center = (4.1, 1.9)
|
||||
let p1 = [1.0, 2.0];
|
||||
let p2 = [4.0, 5.0];
|
||||
let p3 = [7.0, 3.0];
|
||||
let center = calculate_circle_center(p1, p2, p3);
|
||||
assert_relative_eq!(center[0], 4.1, epsilon = EPS);
|
||||
assert_relative_eq!(center[1], 1.9, epsilon = EPS);
|
||||
|
||||
// Tests: Generate a few circles and test its points
|
||||
let center = [3.2, 0.7];
|
||||
let radius_array = [0.001, 0.01, 0.6, 1.0, 5.0, 60.0, 500.0, 2000.0, 400_000.0];
|
||||
let points_array = [[0.0, 0.33, 0.66], [0.0, 0.1, 0.2], [0.0, -0.1, 0.1], [0.0, 0.5, 0.7]];
|
||||
|
||||
let get_point = |radius: f64, t: f64| {
|
||||
let angle = t * TAU;
|
||||
[center[0] + radius * angle.cos(), center[1] + radius * angle.sin()]
|
||||
};
|
||||
|
||||
for radius in radius_array {
|
||||
for point in points_array {
|
||||
let p1 = get_point(radius, point[0]);
|
||||
let p2 = get_point(radius, point[1]);
|
||||
let p3 = get_point(radius, point[2]);
|
||||
let c = calculate_circle_center(p1, p2, p3);
|
||||
assert_relative_eq!(c[0], center[0], epsilon = EPS);
|
||||
assert_relative_eq!(c[1], center[1], epsilon = EPS);
|
||||
}
|
||||
}
|
||||
|
||||
// Test: Equilateral triangle
|
||||
let p1 = [0.0, 0.0];
|
||||
let p2 = [1.0, 0.0];
|
||||
let p3 = [0.5, 3.0_f64.sqrt() / 2.0];
|
||||
let center = calculate_circle_center(p1, p2, p3);
|
||||
assert_relative_eq!(center[0], 0.5, epsilon = EPS);
|
||||
assert_relative_eq!(center[1], 1.0 / (2.0 * 3.0_f64.sqrt()), epsilon = EPS);
|
||||
|
||||
// Test: Collinear points (should return the average of the points)
|
||||
let p1 = [0.0, 0.0];
|
||||
let p2 = [1.0, 0.0];
|
||||
let p3 = [2.0, 0.0];
|
||||
let center = calculate_circle_center(p1, p2, p3);
|
||||
assert_relative_eq!(center[0], 1.0, epsilon = EPS);
|
||||
assert_relative_eq!(center[1], 0.0, epsilon = EPS);
|
||||
|
||||
// Test: Points forming a circle with radius = 1
|
||||
let p1 = [0.0, 0.0];
|
||||
let p2 = [0.0, 2.0];
|
||||
let p3 = [2.0, 0.0];
|
||||
let center = calculate_circle_center(p1, p2, p3);
|
||||
assert_relative_eq!(center[0], 1.0, epsilon = EPS);
|
||||
assert_relative_eq!(center[1], 1.0, epsilon = EPS);
|
||||
|
||||
// Test: Integer coordinates
|
||||
let p1 = [0.0, 0.0];
|
||||
let p2 = [0.0, 6.0];
|
||||
let p3 = [6.0, 0.0];
|
||||
let center = calculate_circle_center(p1, p2, p3);
|
||||
assert_relative_eq!(center[0], 3.0, epsilon = EPS);
|
||||
assert_relative_eq!(center[1], 3.0, epsilon = EPS);
|
||||
// Verify radius (should be 3 * sqrt(2))
|
||||
let radius = ((center[0] - p1[0]).powi(2) + (center[1] - p1[1]).powi(2)).sqrt();
|
||||
assert_relative_eq!(radius, 3.0 * 2.0_f64.sqrt(), epsilon = EPS);
|
||||
}
|
||||
}
|
||||
|
||||
pub type Coords2d = [f64; 2];
|
||||
|
@ -1,8 +1,5 @@
|
||||
use std::fmt::Write;
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
use clap::ValueEnum;
|
||||
|
||||
use crate::parsing::{
|
||||
ast::types::{
|
||||
Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem,
|
||||
@ -195,7 +192,7 @@ impl Node<Annotation> {
|
||||
result.push_str(&indentation);
|
||||
result.push_str(comment);
|
||||
}
|
||||
if !comment.ends_with("*/") && !result.ends_with("\n\n") && result != "\n" {
|
||||
if !result.ends_with("\n\n") && result != "\n" {
|
||||
result.push('\n');
|
||||
}
|
||||
}
|
||||
@ -867,29 +864,6 @@ impl Parameter {
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
|
||||
pub static ref IMPORT_FILE_EXTENSIONS: Vec<String> = {
|
||||
let mut import_file_extensions = vec!["stp".to_string(), "glb".to_string(), "fbxb".to_string()];
|
||||
#[cfg(feature = "cli")]
|
||||
let named_extensions = kittycad::types::FileImportFormat::value_variants()
|
||||
.iter()
|
||||
.map(|x| format!("{}", x))
|
||||
.collect::<Vec<String>>();
|
||||
#[cfg(not(feature = "cli"))]
|
||||
let named_extensions = vec![]; // We don't really need this outside of the CLI.
|
||||
// Add all the default import formats.
|
||||
import_file_extensions.extend_from_slice(&named_extensions);
|
||||
import_file_extensions
|
||||
};
|
||||
|
||||
pub static ref RELEVANT_EXTENSIONS: Vec<String> = {
|
||||
let mut relevant_extensions = IMPORT_FILE_EXTENSIONS.clone();
|
||||
relevant_extensions.push("kcl".to_string());
|
||||
relevant_extensions
|
||||
};
|
||||
}
|
||||
|
||||
/// Collect all the kcl (and other relevant) files in a directory, recursively.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[async_recursion::async_recursion]
|
||||
@ -909,7 +883,7 @@ pub async fn walk_dir(dir: &std::path::PathBuf) -> Result<Vec<std::path::PathBuf
|
||||
files.extend(walk_dir(&path).await?);
|
||||
} else if path
|
||||
.extension()
|
||||
.is_some_and(|ext| RELEVANT_EXTENSIONS.contains(&ext.to_string_lossy().to_string()))
|
||||
.is_some_and(|ext| crate::RELEVANT_FILE_EXTENSIONS.contains(&ext.to_string_lossy().to_string()))
|
||||
{
|
||||
files.push(path);
|
||||
}
|
||||
@ -1048,6 +1022,20 @@ bar = 0
|
||||
assert_eq!(output, input);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recast_annotations_with_block_comment() {
|
||||
let input = r#"/* Start comment
|
||||
|
||||
sdfsdfsdfs */
|
||||
@settings(defaultLengthUnit = in)
|
||||
|
||||
foo = 42
|
||||
"#;
|
||||
let program = crate::parsing::top_level_parse(input).unwrap();
|
||||
let output = program.recast(&Default::default(), 0);
|
||||
assert_eq!(output, input);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_recast_if_else_if_same() {
|
||||
let input = r#"b = if false {
|
||||
|
Reference in New Issue
Block a user