Sketch on face (#1371)
* add extra metadata to extrudeGroup * add boilerplate Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanup and generate docs Signed-off-by: Jess Frazelle <github@jessfraz.com> * change plane id to entity id Signed-off-by: Jess Frazelle <github@jessfraz.com> * generate docs Signed-off-by: Jess Frazelle <github@jessfraz.com> * get face id from extrude using segment tag * cleanup a bit Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanup a bit Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix doc comment Signed-off-by: Jess Frazelle <github@jessfraz.com> * get rid of face_id in geo_meta Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * sketch on face test Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanup edge_id Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix value Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix test Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix tests Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix tests Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com> Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
This commit is contained in:
6513
docs/kcl/std.json
6513
docs/kcl/std.json
File diff suppressed because it is too large
Load Diff
1281
docs/kcl/std.md
1281
docs/kcl/std.md
File diff suppressed because it is too large
Load Diff
@ -55,11 +55,11 @@ show(mySketch001)`
|
|||||||
],
|
],
|
||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
rotation: [0, 0, 0, 1],
|
rotation: [0, 0, 0, 1],
|
||||||
xAxis: [1, 0, 0],
|
xAxis: { x: 1, y: 0, z: 0 },
|
||||||
yAxis: [0, 1, 0],
|
yAxis: { x: 0, y: 1, z: 0 },
|
||||||
zAxis: [0, 0, 1],
|
zAxis: { x: 0, y: 0, z: 1 },
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
planeId: expect.any(String),
|
entityId: expect.any(String),
|
||||||
__meta: [{ sourceRange: [46, 71] }],
|
__meta: [{ sourceRange: [46, 71] }],
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
@ -88,6 +88,11 @@ show(mySketch001)`
|
|||||||
height: 2,
|
height: 2,
|
||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
rotation: [0, 0, 0, 1],
|
rotation: [0, 0, 0, 1],
|
||||||
|
endCapId: null,
|
||||||
|
startCapId: null,
|
||||||
|
xAxis: { x: 1, y: 0, z: 0 },
|
||||||
|
yAxis: { x: 0, y: 1, z: 0 },
|
||||||
|
zAxis: { x: 0, y: 0, z: 1 },
|
||||||
__meta: [{ sourceRange: [46, 71] }],
|
__meta: [{ sourceRange: [46, 71] }],
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
@ -130,6 +135,11 @@ show(theExtrude, sk2)`
|
|||||||
height: 2,
|
height: 2,
|
||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
rotation: [0, 0, 0, 1],
|
rotation: [0, 0, 0, 1],
|
||||||
|
endCapId: null,
|
||||||
|
startCapId: null,
|
||||||
|
xAxis: { x: 1, y: 0, z: 0 },
|
||||||
|
yAxis: { x: 0, y: 1, z: 0 },
|
||||||
|
zAxis: { x: 0, y: 0, z: 1 },
|
||||||
__meta: [{ sourceRange: [38, 63] }],
|
__meta: [{ sourceRange: [38, 63] }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -139,6 +149,12 @@ show(theExtrude, sk2)`
|
|||||||
height: 2,
|
height: 2,
|
||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
rotation: [0, 0, 0, 1],
|
rotation: [0, 0, 0, 1],
|
||||||
|
|
||||||
|
endCapId: null,
|
||||||
|
startCapId: null,
|
||||||
|
xAxis: { x: 1, y: 0, z: 0 },
|
||||||
|
yAxis: { x: 0, y: 1, z: 0 },
|
||||||
|
zAxis: { x: 0, y: 0, z: 1 },
|
||||||
__meta: [{ sourceRange: [356, 381] }],
|
__meta: [{ sourceRange: [356, 381] }],
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
@ -186,11 +186,11 @@ show(mySketch)
|
|||||||
],
|
],
|
||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
rotation: [0, 0, 0, 1],
|
rotation: [0, 0, 0, 1],
|
||||||
xAxis: [1, 0, 0],
|
xAxis: { x: 1, y: 0, z: 0 },
|
||||||
yAxis: [0, 1, 0],
|
yAxis: { x: 0, y: 1, z: 0 },
|
||||||
zAxis: [0, 0, 1],
|
zAxis: { x: 0, y: 0, z: 1 },
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
planeId: expect.any(String),
|
entityId: expect.any(String),
|
||||||
__meta: [{ sourceRange: [39, 63] }],
|
__meta: [{ sourceRange: [39, 63] }],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -306,7 +306,7 @@ pub fn get_type_string_from_schema(schema: &schemars::schema::Schema) -> Result<
|
|||||||
if let Some(format) = &o.format {
|
if let Some(format) = &o.format {
|
||||||
if format == "uuid" {
|
if format == "uuid" {
|
||||||
return Ok((Primitive::Uuid.to_string(), false));
|
return Ok((Primitive::Uuid.to_string(), false));
|
||||||
} else if format == "double" || format == "uint" {
|
} else if format == "double" || format == "uint" || format == "int64" {
|
||||||
return Ok((Primitive::Number.to_string(), false));
|
return Ok((Primitive::Number.to_string(), false));
|
||||||
} else {
|
} else {
|
||||||
anyhow::bail!("unknown format: {}", format);
|
anyhow::bail!("unknown format: {}", format);
|
||||||
@ -424,7 +424,7 @@ pub fn get_autocomplete_string_from_schema(schema: &schemars::schema::Schema) ->
|
|||||||
if let Some(format) = &o.format {
|
if let Some(format) = &o.format {
|
||||||
if format == "uuid" {
|
if format == "uuid" {
|
||||||
return Ok(Primitive::Uuid.to_string());
|
return Ok(Primitive::Uuid.to_string());
|
||||||
} else if format == "double" || format == "uint" {
|
} else if format == "double" || format == "uint" || format == "int64" {
|
||||||
return Ok(Primitive::Number.to_string());
|
return Ok(Primitive::Number.to_string());
|
||||||
} else {
|
} else {
|
||||||
anyhow::bail!("unknown format: {}", format);
|
anyhow::bail!("unknown format: {}", format);
|
||||||
|
@ -139,6 +139,7 @@ impl ProgramReturn {
|
|||||||
pub enum MemoryItem {
|
pub enum MemoryItem {
|
||||||
UserVal(UserVal),
|
UserVal(UserVal),
|
||||||
Plane(Box<Plane>),
|
Plane(Box<Plane>),
|
||||||
|
Face(Box<Face>),
|
||||||
SketchGroup(Box<SketchGroup>),
|
SketchGroup(Box<SketchGroup>),
|
||||||
SketchGroups {
|
SketchGroups {
|
||||||
value: Vec<Box<SketchGroup>>,
|
value: Vec<Box<SketchGroup>>,
|
||||||
@ -230,6 +231,25 @@ pub struct Plane {
|
|||||||
pub meta: Vec<Metadata>,
|
pub meta: Vec<Metadata>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A face.
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Face {
|
||||||
|
/// The id of the face.
|
||||||
|
pub id: uuid::Uuid,
|
||||||
|
/// The tag of the face.
|
||||||
|
pub value: String,
|
||||||
|
/// What should the face’s X axis be?
|
||||||
|
pub x_axis: Point3d,
|
||||||
|
/// What should the face’s Y axis be?
|
||||||
|
pub y_axis: Point3d,
|
||||||
|
/// The z-axis (normal).
|
||||||
|
pub z_axis: Point3d,
|
||||||
|
#[serde(rename = "__meta")]
|
||||||
|
pub meta: Vec<Metadata>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Type for a plane.
|
/// Type for a plane.
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, FromStr, Display)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, FromStr, Display)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
@ -312,6 +332,7 @@ impl From<MemoryItem> for Vec<SourceRange> {
|
|||||||
MemoryItem::ExtrudeTransform(e) => e.meta.iter().map(|m| m.source_range).collect(),
|
MemoryItem::ExtrudeTransform(e) => e.meta.iter().map(|m| m.source_range).collect(),
|
||||||
MemoryItem::Function { meta, .. } => meta.iter().map(|m| m.source_range).collect(),
|
MemoryItem::Function { meta, .. } => meta.iter().map(|m| m.source_range).collect(),
|
||||||
MemoryItem::Plane(p) => p.meta.iter().map(|m| m.source_range).collect(),
|
MemoryItem::Plane(p) => p.meta.iter().map(|m| m.source_range).collect(),
|
||||||
|
MemoryItem::Face(f) => f.meta.iter().map(|m| m.source_range).collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -404,13 +425,13 @@ pub struct SketchGroup {
|
|||||||
/// The rotation of the sketch group base plane.
|
/// The rotation of the sketch group base plane.
|
||||||
pub rotation: Rotation,
|
pub rotation: Rotation,
|
||||||
/// The x-axis of the sketch group base plane in the 3D space
|
/// The x-axis of the sketch group base plane in the 3D space
|
||||||
pub x_axis: Position,
|
pub x_axis: Point3d,
|
||||||
/// The y-axis of the sketch group base plane in the 3D space
|
/// The y-axis of the sketch group base plane in the 3D space
|
||||||
pub y_axis: Position,
|
pub y_axis: Point3d,
|
||||||
/// The z-axis of the sketch group base plane in the 3D space
|
/// The z-axis of the sketch group base plane in the 3D space
|
||||||
pub z_axis: Position,
|
pub z_axis: Point3d,
|
||||||
/// The plane id of the sketch group.
|
/// The plane id or face id of the sketch group.
|
||||||
pub plane_id: Option<uuid::Uuid>,
|
pub entity_id: Option<uuid::Uuid>,
|
||||||
/// Metadata.
|
/// Metadata.
|
||||||
#[serde(rename = "__meta")]
|
#[serde(rename = "__meta")]
|
||||||
pub meta: Vec<Metadata>,
|
pub meta: Vec<Metadata>,
|
||||||
@ -503,6 +524,16 @@ pub struct ExtrudeGroup {
|
|||||||
pub position: Position,
|
pub position: Position,
|
||||||
/// The rotation of the extrude group.
|
/// The rotation of the extrude group.
|
||||||
pub rotation: Rotation,
|
pub rotation: Rotation,
|
||||||
|
/// The x-axis of the extrude group base plane in the 3D space
|
||||||
|
pub x_axis: Point3d,
|
||||||
|
/// The y-axis of the extrude group base plane in the 3D space
|
||||||
|
pub y_axis: Point3d,
|
||||||
|
/// The z-axis of the extrude group base plane in the 3D space
|
||||||
|
pub z_axis: Point3d,
|
||||||
|
/// The id of the extrusion start cap
|
||||||
|
pub start_cap_id: Option<uuid::Uuid>,
|
||||||
|
/// The id of the extrusion end cap
|
||||||
|
pub end_cap_id: Option<uuid::Uuid>,
|
||||||
/// Metadata.
|
/// Metadata.
|
||||||
#[serde(rename = "__meta")]
|
#[serde(rename = "__meta")]
|
||||||
pub meta: Vec<Metadata>,
|
pub meta: Vec<Metadata>,
|
||||||
@ -531,6 +562,16 @@ pub enum BodyType {
|
|||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct Position(#[ts(type = "[number, number, number]")] pub [f64; 3]);
|
pub struct Position(#[ts(type = "[number, number, number]")] pub [f64; 3]);
|
||||||
|
|
||||||
|
impl From<Position> for Point3d {
|
||||||
|
fn from(p: Position) -> Self {
|
||||||
|
Self {
|
||||||
|
x: p.0[0],
|
||||||
|
y: p.0[1],
|
||||||
|
z: p.0[2],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct Rotation(#[ts(type = "[number, number, number, number]")] pub [f64; 4]);
|
pub struct Rotation(#[ts(type = "[number, number, number, number]")] pub [f64; 4]);
|
||||||
@ -773,6 +814,16 @@ impl Path {
|
|||||||
Path::TangentialArcTo { base, .. } => base,
|
Path::TangentialArcTo { base, .. } => base,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_base_mut(&mut self) -> Option<&mut BasePath> {
|
||||||
|
match self {
|
||||||
|
Path::ToPoint { base } => Some(base),
|
||||||
|
Path::Horizontal { base, .. } => Some(base),
|
||||||
|
Path::AngledLineTo { base, .. } => Some(base),
|
||||||
|
Path::Base { base } => Some(base),
|
||||||
|
Path::TangentialArcTo { base, .. } => Some(base),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An extrude surface.
|
/// An extrude surface.
|
||||||
@ -781,41 +832,49 @@ impl Path {
|
|||||||
#[serde(tag = "type", rename_all = "camelCase")]
|
#[serde(tag = "type", rename_all = "camelCase")]
|
||||||
pub enum ExtrudeSurface {
|
pub enum ExtrudeSurface {
|
||||||
/// An extrude plane.
|
/// An extrude plane.
|
||||||
ExtrudePlane {
|
ExtrudePlane(ExtrudePlane),
|
||||||
/// The position.
|
}
|
||||||
position: Position,
|
|
||||||
/// The rotation.
|
/// An extruded plane.
|
||||||
rotation: Rotation,
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
/// The name.
|
#[ts(export)]
|
||||||
name: String,
|
#[serde(rename_all = "camelCase")]
|
||||||
/// Metadata.
|
pub struct ExtrudePlane {
|
||||||
#[serde(flatten)]
|
/// The position.
|
||||||
geo_meta: GeoMeta,
|
pub position: Position,
|
||||||
},
|
/// The rotation.
|
||||||
|
pub rotation: Rotation,
|
||||||
|
/// The face id for the extrude plane.
|
||||||
|
pub face_id: uuid::Uuid,
|
||||||
|
/// The name.
|
||||||
|
pub name: String,
|
||||||
|
/// Metadata.
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub geo_meta: GeoMeta,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExtrudeSurface {
|
impl ExtrudeSurface {
|
||||||
pub fn get_id(&self) -> uuid::Uuid {
|
pub fn get_id(&self) -> uuid::Uuid {
|
||||||
match self {
|
match self {
|
||||||
ExtrudeSurface::ExtrudePlane { geo_meta, .. } => geo_meta.id,
|
ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_name(&self) -> String {
|
pub fn get_name(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
ExtrudeSurface::ExtrudePlane { name, .. } => name.clone(),
|
ExtrudeSurface::ExtrudePlane(ep) => ep.name.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_position(&self) -> Position {
|
pub fn get_position(&self) -> Position {
|
||||||
match self {
|
match self {
|
||||||
ExtrudeSurface::ExtrudePlane { position, .. } => *position,
|
ExtrudeSurface::ExtrudePlane(ep) => ep.position,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_rotation(&self) -> Rotation {
|
pub fn get_rotation(&self) -> Rotation {
|
||||||
match self {
|
match self {
|
||||||
ExtrudeSurface::ExtrudePlane { rotation, .. } => *rotation,
|
ExtrudeSurface::ExtrudePlane(ep) => ep.rotation,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ use schemars::JsonSchema;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
executor::{ExtrudeGroup, ExtrudeTransform, MemoryItem, SketchGroup},
|
executor::{ExtrudeGroup, ExtrudeSurface, ExtrudeTransform, GeoMeta, MemoryItem, Path, SketchGroup},
|
||||||
std::Args,
|
std::Args,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -46,17 +46,93 @@ async fn inner_extrude(length: f64, sketch_group: Box<SketchGroup>, args: Args)
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
if sketch_group.value.is_empty() {
|
||||||
|
return Err(KclError::Type(KclErrorDetails {
|
||||||
|
message: "Expected a non-empty sketch group".to_string(),
|
||||||
|
source_ranges: vec![args.source_range],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut edge_id = None;
|
||||||
|
for segment in sketch_group.value.iter() {
|
||||||
|
if let Path::ToPoint { base } = segment {
|
||||||
|
edge_id = Some(base.geo_meta.id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(edge_id) = edge_id else {
|
||||||
|
return Err(KclError::Type(KclErrorDetails {
|
||||||
|
message: "Expected a Path::ToPoint variant".to_string(),
|
||||||
|
source_ranges: vec![args.source_range],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
let solid3d_info = args
|
||||||
|
.send_modeling_cmd(
|
||||||
|
id,
|
||||||
|
kittycad::types::ModelingCmd::Solid3DGetExtrusionFaceInfo {
|
||||||
|
edge_id,
|
||||||
|
object_id: sketch_group.id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let face_infos = if let kittycad::types::OkWebSocketResponseData::Modeling {
|
||||||
|
modeling_response: kittycad::types::OkModelingCmdResponse::Solid3DGetExtrusionFaceInfo { data },
|
||||||
|
} = solid3d_info
|
||||||
|
{
|
||||||
|
data.faces
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a hashmap for quick id lookup
|
||||||
|
let mut face_id_map = std::collections::HashMap::new();
|
||||||
|
let mut start_cap_id = None;
|
||||||
|
let mut end_cap_id = None;
|
||||||
|
|
||||||
|
for face_info in face_infos {
|
||||||
|
match face_info.cap {
|
||||||
|
kittycad::types::ExtrusionFaceCapType::Bottom => start_cap_id = face_info.face_id,
|
||||||
|
kittycad::types::ExtrusionFaceCapType::Top => end_cap_id = face_info.face_id,
|
||||||
|
_ => {
|
||||||
|
if let Some(curve_id) = face_info.curve_id {
|
||||||
|
face_id_map.insert(curve_id, face_info.face_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over the sketch_group.value array and add face_id to GeoMeta
|
||||||
|
let mut new_value: Vec<ExtrudeSurface> = Vec::new();
|
||||||
|
for path in sketch_group.value.iter() {
|
||||||
|
if let Some(Some(actual_face_id)) = face_id_map.get(&path.get_base().geo_meta.id) {
|
||||||
|
let extrude_surface = ExtrudeSurface::ExtrudePlane(crate::executor::ExtrudePlane {
|
||||||
|
position: sketch_group.position, // TODO should be for the extrude surface
|
||||||
|
rotation: sketch_group.rotation, // TODO should be for the extrude surface
|
||||||
|
face_id: *actual_face_id,
|
||||||
|
name: path.get_base().name.clone(),
|
||||||
|
geo_meta: GeoMeta {
|
||||||
|
id: path.get_base().geo_meta.id,
|
||||||
|
metadata: path.get_base().geo_meta.metadata.clone(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
new_value.push(extrude_surface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Box::new(ExtrudeGroup {
|
Ok(Box::new(ExtrudeGroup {
|
||||||
// Ok so you would think that the id would be the id of the extrude group,
|
id,
|
||||||
// that we passed in to the function, but it's actually the id of the
|
value: new_value,
|
||||||
// sketch group.
|
|
||||||
id: sketch_group.id,
|
|
||||||
// TODO, this is just an empty array now, should be deleted. This
|
|
||||||
// comment was originally in the JS code.
|
|
||||||
value: Default::default(),
|
|
||||||
height: length,
|
height: length,
|
||||||
position: sketch_group.position,
|
position: sketch_group.position,
|
||||||
rotation: sketch_group.rotation,
|
rotation: sketch_group.rotation,
|
||||||
|
x_axis: sketch_group.x_axis,
|
||||||
|
y_axis: sketch_group.y_axis,
|
||||||
|
z_axis: sketch_group.z_axis,
|
||||||
|
start_cap_id,
|
||||||
|
end_cap_id,
|
||||||
meta: sketch_group.meta,
|
meta: sketch_group.meta,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -22,13 +22,14 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use self::kcl_stdlib::KclStdLibFn;
|
use self::kcl_stdlib::KclStdLibFn;
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::types::parse_json_number_as_f64,
|
ast::types::{parse_json_number_as_f64, parse_json_value_as_string},
|
||||||
docs::StdLibFn,
|
docs::StdLibFn,
|
||||||
engine::EngineManager,
|
engine::EngineManager,
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
executor::{
|
executor::{
|
||||||
ExecutorContext, ExtrudeGroup, Geometry, MemoryItem, Metadata, Plane, SketchGroup, SketchGroupSet, SourceRange,
|
ExecutorContext, ExtrudeGroup, Geometry, MemoryItem, Metadata, SketchGroup, SketchGroupSet, SourceRange,
|
||||||
},
|
},
|
||||||
|
std::sketch::SketchSurface,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type StdFn = fn(Args) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<MemoryItem, KclError>>>>;
|
pub type StdFn = fn(Args) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<MemoryItem, KclError>>>>;
|
||||||
@ -384,7 +385,6 @@ impl Args {
|
|||||||
})
|
})
|
||||||
})?
|
})?
|
||||||
.get_json_value()?;
|
.get_json_value()?;
|
||||||
|
|
||||||
let data: String = serde_json::from_value(first_value).map_err(|e| {
|
let data: String = serde_json::from_value(first_value).map_err(|e| {
|
||||||
KclError::Type(KclErrorDetails {
|
KclError::Type(KclErrorDetails {
|
||||||
message: format!("Expected a file path string: {}", e),
|
message: format!("Expected a file path string: {}", e),
|
||||||
@ -406,6 +406,33 @@ impl Args {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_data_and_optional_tag<T: serde::de::DeserializeOwned>(&self) -> Result<(T, Option<String>), KclError> {
|
||||||
|
let first_value = self
|
||||||
|
.args
|
||||||
|
.first()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
KclError::Type(KclErrorDetails {
|
||||||
|
message: format!("Expected a struct as the first argument, found `{:?}`", self.args),
|
||||||
|
source_ranges: vec![self.source_range],
|
||||||
|
})
|
||||||
|
})?
|
||||||
|
.get_json_value()?;
|
||||||
|
|
||||||
|
let data: T = serde_json::from_value(first_value).map_err(|e| {
|
||||||
|
KclError::Type(KclErrorDetails {
|
||||||
|
message: format!("Failed to deserialize struct from JSON: {}", e),
|
||||||
|
source_ranges: vec![self.source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if let Some(second_value) = self.args.get(1) {
|
||||||
|
let tag = parse_json_value_as_string(&second_value.get_json_value()?);
|
||||||
|
Ok((data, tag))
|
||||||
|
} else {
|
||||||
|
Ok((data, None))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn get_data_and_sketch_group<T: serde::de::DeserializeOwned>(&self) -> Result<(T, Box<SketchGroup>), KclError> {
|
fn get_data_and_sketch_group<T: serde::de::DeserializeOwned>(&self) -> Result<(T, Box<SketchGroup>), KclError> {
|
||||||
let first_value = self
|
let first_value = self
|
||||||
.args
|
.args
|
||||||
@ -487,7 +514,7 @@ impl Args {
|
|||||||
Ok((data, geometry))
|
Ok((data, geometry))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_data_and_plane<T: serde::de::DeserializeOwned>(&self) -> Result<(T, Box<Plane>), KclError> {
|
fn get_data_and_sketch_surface<T: serde::de::DeserializeOwned>(&self) -> Result<(T, SketchSurface), KclError> {
|
||||||
let first_value = self
|
let first_value = self
|
||||||
.args
|
.args
|
||||||
.first()
|
.first()
|
||||||
@ -513,16 +540,21 @@ impl Args {
|
|||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let plane = if let MemoryItem::Plane(p) = second_value {
|
let sketch_surface = if let MemoryItem::Plane(p) = second_value {
|
||||||
p.clone()
|
SketchSurface::Plane(p.clone())
|
||||||
|
} else if let MemoryItem::Face(face) = second_value {
|
||||||
|
SketchSurface::Face(face.clone())
|
||||||
} else {
|
} else {
|
||||||
return Err(KclError::Type(KclErrorDetails {
|
return Err(KclError::Type(KclErrorDetails {
|
||||||
message: format!("Expected a Plane as the second argument, found `{:?}`", self.args),
|
message: format!(
|
||||||
|
"Expected a plane or face (SketchSurface) as the second argument, found `{:?}`",
|
||||||
|
self.args
|
||||||
|
),
|
||||||
source_ranges: vec![self.source_range],
|
source_ranges: vec![self.source_range],
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((data, plane))
|
Ok((data, sketch_surface))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_segment_name_to_number_sketch_group(&self) -> Result<(String, f64, Box<SketchGroup>), KclError> {
|
fn get_segment_name_to_number_sketch_group(&self) -> Result<(String, f64, Box<SketchGroup>), KclError> {
|
||||||
|
@ -10,8 +10,8 @@ use serde::{Deserialize, Serialize};
|
|||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
executor::{
|
executor::{
|
||||||
BasePath, GeoMeta, MemoryItem, Path, Plane, PlaneType, Point2d, Point3d, Position, Rotation, SketchGroup,
|
BasePath, ExtrudeGroup, ExtrudeSurface, Face, GeoMeta, MemoryItem, Path, Plane, PlaneType, Point2d, Point3d,
|
||||||
SketchGroupSet, SourceRange,
|
Position, Rotation, SketchGroup, SketchGroupSet, SourceRange,
|
||||||
},
|
},
|
||||||
std::{
|
std::{
|
||||||
utils::{
|
utils::{
|
||||||
@ -646,11 +646,21 @@ pub async fn start_sketch_at(args: Args) -> Result<MemoryItem, KclError> {
|
|||||||
async fn inner_start_sketch_at(data: LineData, args: Args) -> Result<Box<SketchGroup>, KclError> {
|
async fn inner_start_sketch_at(data: LineData, args: Args) -> Result<Box<SketchGroup>, KclError> {
|
||||||
// Let's assume it's the XY plane for now, this is just for backwards compatibility.
|
// Let's assume it's the XY plane for now, this is just for backwards compatibility.
|
||||||
let xy_plane = PlaneData::XY;
|
let xy_plane = PlaneData::XY;
|
||||||
let plane = inner_start_sketch_on(xy_plane, args.clone()).await?;
|
let sketch_surface = inner_start_sketch_on(SketchData::Plane(xy_plane), None, args.clone()).await?;
|
||||||
let sketch_group = inner_start_profile_at(data, plane, args).await?;
|
let sketch_group = inner_start_profile_at(data, sketch_surface, args).await?;
|
||||||
Ok(sketch_group)
|
Ok(sketch_group)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Data for start sketch on.
|
||||||
|
/// You can start a sketch on a plane or an extrude group.
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase", untagged)]
|
||||||
|
pub enum SketchData {
|
||||||
|
Plane(PlaneData),
|
||||||
|
ExtrudeGroup(Box<ExtrudeGroup>),
|
||||||
|
}
|
||||||
|
|
||||||
/// Data for a plane.
|
/// Data for a plane.
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, ExecutionPlanValue)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, ExecutionPlanValue)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
@ -763,19 +773,116 @@ impl From<PlaneData> for Plane {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start a sketch on a specific plane.
|
/// A plane or a face.
|
||||||
pub async fn start_sketch_on(args: Args) -> Result<MemoryItem, KclError> {
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
let data: PlaneData = args.get_data()?;
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase", untagged)]
|
||||||
let plane = inner_start_sketch_on(data, args).await?;
|
pub enum SketchSurface {
|
||||||
Ok(MemoryItem::Plane(plane))
|
/// A plane.
|
||||||
|
Plane(Box<Plane>),
|
||||||
|
/// A face.
|
||||||
|
Face(Box<Face>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start a sketch at a given point.
|
impl SketchSurface {
|
||||||
|
pub fn id(&self) -> uuid::Uuid {
|
||||||
|
match self {
|
||||||
|
SketchSurface::Plane(plane) => plane.id,
|
||||||
|
SketchSurface::Face(face) => face.id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn x_axis(&self) -> Point3d {
|
||||||
|
match self {
|
||||||
|
SketchSurface::Plane(plane) => plane.x_axis.clone(),
|
||||||
|
SketchSurface::Face(face) => face.x_axis.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn y_axis(&self) -> Point3d {
|
||||||
|
match self {
|
||||||
|
SketchSurface::Plane(plane) => plane.y_axis.clone(),
|
||||||
|
SketchSurface::Face(face) => face.y_axis.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn z_axis(&self) -> Point3d {
|
||||||
|
match self {
|
||||||
|
SketchSurface::Plane(plane) => plane.z_axis.clone(),
|
||||||
|
SketchSurface::Face(face) => face.z_axis.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start a sketch on a specific plane or face.
|
||||||
|
pub async fn start_sketch_on(args: Args) -> Result<MemoryItem, KclError> {
|
||||||
|
let (data, tag): (SketchData, Option<String>) = args.get_data_and_optional_tag()?;
|
||||||
|
|
||||||
|
match inner_start_sketch_on(data, tag, args).await? {
|
||||||
|
SketchSurface::Plane(plane) => Ok(MemoryItem::Plane(plane)),
|
||||||
|
SketchSurface::Face(face) => Ok(MemoryItem::Face(face)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start a sketch on a specific plane or face.
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "startSketchOn",
|
name = "startSketchOn",
|
||||||
}]
|
}]
|
||||||
async fn inner_start_sketch_on(data: PlaneData, args: Args) -> Result<Box<Plane>, KclError> {
|
async fn inner_start_sketch_on(data: SketchData, tag: Option<String>, args: Args) -> Result<SketchSurface, KclError> {
|
||||||
|
match data {
|
||||||
|
SketchData::Plane(plane_data) => {
|
||||||
|
let plane = start_sketch_on_plane(plane_data, args).await?;
|
||||||
|
Ok(SketchSurface::Plane(plane))
|
||||||
|
}
|
||||||
|
SketchData::ExtrudeGroup(extrude_group) => {
|
||||||
|
let Some(tag) = tag else {
|
||||||
|
return Err(KclError::Type(KclErrorDetails {
|
||||||
|
message: "Expected a tag for the face to sketch on".to_string(),
|
||||||
|
source_ranges: vec![args.source_range],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
let face = start_sketch_on_face(extrude_group, &tag, args).await?;
|
||||||
|
Ok(SketchSurface::Face(face))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn start_sketch_on_face(extrude_group: Box<ExtrudeGroup>, tag: &str, args: Args) -> Result<Box<Face>, KclError> {
|
||||||
|
let extrude_plane = extrude_group
|
||||||
|
.value
|
||||||
|
.iter()
|
||||||
|
.find_map(|extrude_surface| match extrude_surface {
|
||||||
|
ExtrudeSurface::ExtrudePlane(extrude_plane) if extrude_plane.name == tag => Some(extrude_plane),
|
||||||
|
ExtrudeSurface::ExtrudePlane(_) => None,
|
||||||
|
})
|
||||||
|
.ok_or_else(|| {
|
||||||
|
KclError::Type(KclErrorDetails {
|
||||||
|
message: format!("Expected a face with the tag `{}`", tag),
|
||||||
|
source_ranges: vec![args.source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Enter sketch mode on the face.
|
||||||
|
let id = uuid::Uuid::new_v4();
|
||||||
|
args.send_modeling_cmd(
|
||||||
|
id,
|
||||||
|
ModelingCmd::EnableSketchMode {
|
||||||
|
animated: false,
|
||||||
|
ortho: false,
|
||||||
|
entity_id: extrude_plane.face_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Box::new(Face {
|
||||||
|
id,
|
||||||
|
value: tag.to_string(),
|
||||||
|
// TODO: get this from the extrude plane data.
|
||||||
|
x_axis: extrude_group.x_axis,
|
||||||
|
y_axis: extrude_group.y_axis,
|
||||||
|
z_axis: extrude_group.z_axis,
|
||||||
|
meta: vec![args.source_range.into()],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn start_sketch_on_plane(data: PlaneData, args: Args) -> Result<Box<Plane>, KclError> {
|
||||||
let mut plane: Plane = data.clone().into();
|
let mut plane: Plane = data.clone().into();
|
||||||
let id = uuid::Uuid::new_v4();
|
let id = uuid::Uuid::new_v4();
|
||||||
let default_origin = Point3D { x: 0.0, y: 0.0, z: 0.0 };
|
let default_origin = Point3D { x: 0.0, y: 0.0, z: 0.0 };
|
||||||
@ -874,9 +981,9 @@ async fn inner_start_sketch_on(data: PlaneData, args: Args) -> Result<Box<Plane>
|
|||||||
|
|
||||||
/// Start a profile at a given point.
|
/// Start a profile at a given point.
|
||||||
pub async fn start_profile_at(args: Args) -> Result<MemoryItem, KclError> {
|
pub async fn start_profile_at(args: Args) -> Result<MemoryItem, KclError> {
|
||||||
let (data, plane): (LineData, Box<Plane>) = args.get_data_and_plane()?;
|
let (data, sketch_surface): (LineData, SketchSurface) = args.get_data_and_sketch_surface()?;
|
||||||
|
|
||||||
let sketch_group = inner_start_profile_at(data, plane, args).await?;
|
let sketch_group = inner_start_profile_at(data, sketch_surface, args).await?;
|
||||||
Ok(MemoryItem::SketchGroup(sketch_group))
|
Ok(MemoryItem::SketchGroup(sketch_group))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -884,7 +991,11 @@ pub async fn start_profile_at(args: Args) -> Result<MemoryItem, KclError> {
|
|||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "startProfileAt",
|
name = "startProfileAt",
|
||||||
}]
|
}]
|
||||||
async fn inner_start_profile_at(data: LineData, plane: Box<Plane>, args: Args) -> Result<Box<SketchGroup>, KclError> {
|
async fn inner_start_profile_at(
|
||||||
|
data: LineData,
|
||||||
|
sketch_surface: SketchSurface,
|
||||||
|
args: Args,
|
||||||
|
) -> Result<Box<SketchGroup>, KclError> {
|
||||||
let to = match &data {
|
let to = match &data {
|
||||||
LineData::PointWithTag { to, .. } => *to,
|
LineData::PointWithTag { to, .. } => *to,
|
||||||
LineData::Point(to) => *to,
|
LineData::Point(to) => *to,
|
||||||
@ -925,10 +1036,10 @@ async fn inner_start_profile_at(data: LineData, plane: Box<Plane>, args: Args) -
|
|||||||
id: path_id,
|
id: path_id,
|
||||||
position: Position([0.0, 0.0, 0.0]),
|
position: Position([0.0, 0.0, 0.0]),
|
||||||
rotation: Rotation([0.0, 0.0, 0.0, 1.0]),
|
rotation: Rotation([0.0, 0.0, 0.0, 1.0]),
|
||||||
x_axis: Position([plane.x_axis.x, plane.x_axis.y, plane.x_axis.z]),
|
x_axis: sketch_surface.x_axis(),
|
||||||
y_axis: Position([plane.y_axis.x, plane.y_axis.y, plane.y_axis.z]),
|
y_axis: sketch_surface.y_axis(),
|
||||||
z_axis: Position([plane.z_axis.x, plane.z_axis.y, plane.z_axis.z]),
|
z_axis: sketch_surface.z_axis(),
|
||||||
plane_id: Some(plane.id),
|
entity_id: Some(sketch_surface.id()),
|
||||||
value: vec![],
|
value: vec![],
|
||||||
start: current_path,
|
start: current_path,
|
||||||
meta: vec![args.source_range.into()],
|
meta: vec![args.source_range.into()],
|
||||||
@ -963,8 +1074,8 @@ async fn inner_close(sketch_group: Box<SketchGroup>, args: Args) -> Result<Box<S
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Exit sketch mode, since if we were in a plane we'd want to disable the sketch mode after.
|
// Exit sketch mode, since if we were in a plane or entity we'd want to disable the sketch mode after.
|
||||||
if sketch_group.plane_id.is_some() {
|
if sketch_group.entity_id.is_some() {
|
||||||
// We were on a plane, disable the sketch mode.
|
// We were on a plane, disable the sketch mode.
|
||||||
args.send_modeling_cmd(uuid::Uuid::new_v4(), ModelingCmd::SketchModeDisable {})
|
args.send_modeling_cmd(uuid::Uuid::new_v4(), ModelingCmd::SketchModeDisable {})
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -23,6 +23,8 @@ async fn execute_and_snapshot(code: &str) -> Result<image::DynamicImage> {
|
|||||||
|
|
||||||
// Create the client.
|
// Create the client.
|
||||||
let client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
|
let client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
|
||||||
|
// uncomment to use a local server
|
||||||
|
// client.set_base_url("http://your-local-server:8080/");
|
||||||
|
|
||||||
let ws = client
|
let ws = client
|
||||||
.modeling()
|
.modeling()
|
||||||
@ -66,6 +68,29 @@ async fn execute_and_snapshot(code: &str) -> Result<image::DynamicImage> {
|
|||||||
Ok(actual)
|
Ok(actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn serial_test_sketch_on_face() {
|
||||||
|
let code = r#"const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([11.19, 28.35], %)
|
||||||
|
|> line({to: [28.67, -13.25], tag: "here"}, %)
|
||||||
|
|> line([-4.12, -22.81], %)
|
||||||
|
|> line([-33.24, 14.55], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(5, %)
|
||||||
|
|
||||||
|
const part002 = startSketchOn(part001, "here")
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|
|> line([0, 10], %)
|
||||||
|
|> line([10, 0], %)
|
||||||
|
|> line([0, -10], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(5, %)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let result = execute_and_snapshot(code).await.unwrap();
|
||||||
|
twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face.png", &result, 0.999);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn serial_test_execute_with_function_sketch() {
|
async fn serial_test_execute_with_function_sketch() {
|
||||||
let code = r#"fn box = (h, l, w) => {
|
let code = r#"fn box = (h, l, w) => {
|
||||||
|
BIN
src/wasm-lib/tests/executor/outputs/sketch_on_face.png
Normal file
BIN
src/wasm-lib/tests/executor/outputs/sketch_on_face.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
Reference in New Issue
Block a user