Move the wasm lib, and cleanup rust directory and all references (#5585)
* git mv src/wasm-lib rust Signed-off-by: Jess Frazelle <github@jessfraz.com> * mv wasm-lib to workspace Signed-off-by: Jess Frazelle <github@jessfraz.com> * mv kcl-lib Signed-off-by: Jess Frazelle <github@jessfraz.com> * mv derive docs Signed-off-by: Jess Frazelle <github@jessfraz.com> * resolve file paths Signed-off-by: Jess Frazelle <github@jessfraz.com> * clippy Signed-off-by: Jess Frazelle <github@jessfraz.com> * move more shit Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix more paths Signed-off-by: Jess Frazelle <github@jessfraz.com> * make yarn build:wasm work Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix scripts Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixups Signed-off-by: Jess Frazelle <github@jessfraz.com> * better references Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix cargo ci Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix reference Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix more ci Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix tests Signed-off-by: Jess Frazelle <github@jessfraz.com> * cargo sort Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix script Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix Signed-off-by: Jess Frazelle <github@jessfraz.com> * fmt Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix a dep Signed-off-by: Jess Frazelle <github@jessfraz.com> * sort Signed-off-by: Jess Frazelle <github@jessfraz.com> * remove unused deps Signed-off-by: Jess Frazelle <github@jessfraz.com> * Revert "remove unused deps" This reverts commit fbabdb062e275fd5cbc1476f8480a1afee15d972. * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * deps; Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
343
rust/kcl-lib/src/std/extrude.rs
Normal file
343
rust/kcl-lib/src/std/extrude.rs
Normal file
@ -0,0 +1,343 @@
|
||||
//! Functions related to extruding.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use kcl_derive_docs::stdlib;
|
||||
use kcmc::{
|
||||
each_cmd as mcmd, length_unit::LengthUnit, ok_response::OkModelingCmdResponse, output::ExtrusionFaceInfo,
|
||||
shared::ExtrusionFaceCapType, websocket::OkWebSocketResponseData, ModelingCmd,
|
||||
};
|
||||
use kittycad_modeling_cmds as kcmc;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{
|
||||
ArtifactId, ExecState, ExtrudeSurface, GeoMeta, KclValue, Path, Sketch, SketchSet, SketchSurface, Solid,
|
||||
SolidSet,
|
||||
},
|
||||
std::Args,
|
||||
};
|
||||
|
||||
/// Extrudes by a given amount.
|
||||
pub async fn extrude(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let sketch_set = args.get_unlabeled_kw_arg("sketch_set")?;
|
||||
let length = args.get_kw_arg("length")?;
|
||||
|
||||
let result = inner_extrude(sketch_set, length, exec_state, args).await?;
|
||||
|
||||
Ok(result.into())
|
||||
}
|
||||
|
||||
/// Extend a 2-dimensional sketch through a third dimension in order to
|
||||
/// create new 3-dimensional volume, or if extruded into an existing volume,
|
||||
/// cut into an existing solid.
|
||||
///
|
||||
/// ```no_run
|
||||
/// example = startSketchOn('XZ')
|
||||
/// |> startProfileAt([0, 0], %)
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> arc({
|
||||
/// angleStart = 120,
|
||||
/// angleEnd = 0,
|
||||
/// radius = 5,
|
||||
/// }, %)
|
||||
/// |> line(end = [5, 0])
|
||||
/// |> line(end = [0, 10])
|
||||
/// |> bezierCurve({
|
||||
/// control1 = [-10, 0],
|
||||
/// control2 = [2, 10],
|
||||
/// to = [-5, 10],
|
||||
/// }, %)
|
||||
/// |> line(end = [-5, -2])
|
||||
/// |> close()
|
||||
/// |> extrude(length = 10)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn('XZ')
|
||||
/// |> startProfileAt([-10, 0], %)
|
||||
/// |> arc({
|
||||
/// angleStart = 120,
|
||||
/// angleEnd = -60,
|
||||
/// radius = 5,
|
||||
/// }, %)
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> line(end = [5, 0])
|
||||
/// |> bezierCurve({
|
||||
/// control1 = [-3, 0],
|
||||
/// control2 = [2, 10],
|
||||
/// to = [-5, 10],
|
||||
/// }, %)
|
||||
/// |> line(end = [-4, 10])
|
||||
/// |> line(end = [-5, -2])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 10)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "extrude",
|
||||
feature_tree_operation = true,
|
||||
keywords = true,
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch_set = { docs = "Which sketches should be extruded"},
|
||||
length = { docs = "How far to extrude the given sketches"},
|
||||
}
|
||||
}]
|
||||
async fn inner_extrude(
|
||||
sketch_set: SketchSet,
|
||||
length: f64,
|
||||
exec_state: &mut ExecState,
|
||||
args: Args,
|
||||
) -> Result<SolidSet, KclError> {
|
||||
let id = exec_state.next_uuid();
|
||||
|
||||
// Extrude the element(s).
|
||||
let sketches: Vec<Sketch> = sketch_set.into();
|
||||
let mut solids = Vec::new();
|
||||
for sketch in &sketches {
|
||||
// Before we extrude, we need to enable the sketch mode.
|
||||
// We do this here in case extrude is called out of order.
|
||||
args.batch_modeling_cmd(
|
||||
exec_state.next_uuid(),
|
||||
ModelingCmd::from(mcmd::EnableSketchMode {
|
||||
animated: false,
|
||||
ortho: false,
|
||||
entity_id: sketch.on.id(),
|
||||
adjust_camera: false,
|
||||
planar_normal: if let SketchSurface::Plane(plane) = &sketch.on {
|
||||
// We pass in the normal for the plane here.
|
||||
Some(plane.z_axis.into())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// TODO: We're reusing the same UUID for multiple commands. This seems
|
||||
// like the artifact graph would never be able to find all the
|
||||
// responses.
|
||||
args.batch_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::from(mcmd::Extrude {
|
||||
target: sketch.id.into(),
|
||||
distance: LengthUnit(length),
|
||||
faces: Default::default(),
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Disable the sketch mode.
|
||||
args.batch_modeling_cmd(
|
||||
exec_state.next_uuid(),
|
||||
ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable::default()),
|
||||
)
|
||||
.await?;
|
||||
solids.push(do_post_extrude(sketch.clone(), id.into(), length, exec_state, args.clone()).await?);
|
||||
}
|
||||
|
||||
Ok(solids.into())
|
||||
}
|
||||
|
||||
pub(crate) async fn do_post_extrude(
|
||||
sketch: Sketch,
|
||||
solid_id: ArtifactId,
|
||||
length: f64,
|
||||
exec_state: &mut ExecState,
|
||||
args: Args,
|
||||
) -> Result<Box<Solid>, KclError> {
|
||||
// Bring the object to the front of the scene.
|
||||
// See: https://github.com/KittyCAD/modeling-app/issues/806
|
||||
args.batch_modeling_cmd(
|
||||
exec_state.next_uuid(),
|
||||
ModelingCmd::from(mcmd::ObjectBringToFront { object_id: sketch.id }),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// The "get extrusion face info" API call requires *any* edge on the sketch being extruded.
|
||||
// So, let's just use the first one.
|
||||
let Some(any_edge_id) = sketch.paths.first().map(|edge| edge.get_base().geo_meta.id) else {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
message: "Expected a non-empty sketch".to_string(),
|
||||
source_ranges: vec![args.source_range],
|
||||
}));
|
||||
};
|
||||
|
||||
let mut sketch = sketch.clone();
|
||||
|
||||
// If we were sketching on a face, we need the original face id.
|
||||
if let SketchSurface::Face(ref face) = sketch.on {
|
||||
sketch.id = face.solid.sketch.id;
|
||||
}
|
||||
|
||||
let solid3d_info = args
|
||||
.send_modeling_cmd(
|
||||
exec_state.next_uuid(),
|
||||
ModelingCmd::from(mcmd::Solid3dGetExtrusionFaceInfo {
|
||||
edge_id: any_edge_id,
|
||||
object_id: sketch.id,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let face_infos = if let OkWebSocketResponseData::Modeling {
|
||||
modeling_response: OkModelingCmdResponse::Solid3dGetExtrusionFaceInfo(data),
|
||||
} = solid3d_info
|
||||
{
|
||||
data.faces
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
for (curve_id, face_id) in face_infos
|
||||
.iter()
|
||||
.filter(|face_info| face_info.cap == ExtrusionFaceCapType::None)
|
||||
.filter_map(|face_info| {
|
||||
if let (Some(curve_id), Some(face_id)) = (face_info.curve_id, face_info.face_id) {
|
||||
Some((curve_id, face_id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
{
|
||||
// Batch these commands, because the Rust code doesn't actually care about the outcome.
|
||||
// So, there's no need to await them.
|
||||
// Instead, the Typescript codebases (which handles WebSocket sends when compiled via Wasm)
|
||||
// uses this to build the artifact graph, which the UI needs.
|
||||
args.batch_modeling_cmd(
|
||||
exec_state.next_uuid(),
|
||||
ModelingCmd::from(mcmd::Solid3dGetOppositeEdge {
|
||||
edge_id: curve_id,
|
||||
object_id: sketch.id,
|
||||
face_id,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
args.batch_modeling_cmd(
|
||||
exec_state.next_uuid(),
|
||||
ModelingCmd::from(mcmd::Solid3dGetNextAdjacentEdge {
|
||||
edge_id: curve_id,
|
||||
object_id: sketch.id,
|
||||
face_id,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let Faces {
|
||||
sides: face_id_map,
|
||||
start_cap_id,
|
||||
end_cap_id,
|
||||
} = analyze_faces(exec_state, &args, face_infos).await;
|
||||
// Iterate over the sketch.value array and add face_id to GeoMeta
|
||||
let no_engine_commands = args.ctx.no_engine_commands().await;
|
||||
let new_value = sketch
|
||||
.paths
|
||||
.iter()
|
||||
.flat_map(|path| {
|
||||
if let Some(Some(actual_face_id)) = face_id_map.get(&path.get_base().geo_meta.id) {
|
||||
match path {
|
||||
Path::Arc { .. }
|
||||
| Path::TangentialArc { .. }
|
||||
| Path::TangentialArcTo { .. }
|
||||
| Path::Circle { .. }
|
||||
| Path::CircleThreePoint { .. } => {
|
||||
let extrude_surface = ExtrudeSurface::ExtrudeArc(crate::execution::ExtrudeArc {
|
||||
face_id: *actual_face_id,
|
||||
tag: path.get_base().tag.clone(),
|
||||
geo_meta: GeoMeta {
|
||||
id: path.get_base().geo_meta.id,
|
||||
metadata: path.get_base().geo_meta.metadata,
|
||||
},
|
||||
});
|
||||
Some(extrude_surface)
|
||||
}
|
||||
Path::Base { .. } | Path::ToPoint { .. } | Path::Horizontal { .. } | Path::AngledLineTo { .. } => {
|
||||
let extrude_surface = ExtrudeSurface::ExtrudePlane(crate::execution::ExtrudePlane {
|
||||
face_id: *actual_face_id,
|
||||
tag: path.get_base().tag.clone(),
|
||||
geo_meta: GeoMeta {
|
||||
id: path.get_base().geo_meta.id,
|
||||
metadata: path.get_base().geo_meta.metadata,
|
||||
},
|
||||
});
|
||||
Some(extrude_surface)
|
||||
}
|
||||
}
|
||||
} else if no_engine_commands {
|
||||
// Only pre-populate the extrude surface if we are in mock mode.
|
||||
|
||||
let extrude_surface = ExtrudeSurface::ExtrudePlane(crate::execution::ExtrudePlane {
|
||||
// pushing this values with a fake face_id to make extrudes mock-execute safe
|
||||
face_id: exec_state.next_uuid(),
|
||||
tag: path.get_base().tag.clone(),
|
||||
geo_meta: GeoMeta {
|
||||
id: path.get_base().geo_meta.id,
|
||||
metadata: path.get_base().geo_meta.metadata,
|
||||
},
|
||||
});
|
||||
Some(extrude_surface)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Box::new(Solid {
|
||||
// Ok so you would think that the id would be the id of the solid,
|
||||
// that we passed in to the function, but it's actually the id of the
|
||||
// sketch.
|
||||
id: sketch.id,
|
||||
artifact_id: solid_id,
|
||||
value: new_value,
|
||||
meta: sketch.meta.clone(),
|
||||
units: sketch.units,
|
||||
sketch,
|
||||
height: length,
|
||||
start_cap_id,
|
||||
end_cap_id,
|
||||
edge_cuts: vec![],
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Faces {
|
||||
/// Maps curve ID to face ID for each side.
|
||||
sides: HashMap<Uuid, Option<Uuid>>,
|
||||
/// Top face ID.
|
||||
end_cap_id: Option<Uuid>,
|
||||
/// Bottom face ID.
|
||||
start_cap_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
async fn analyze_faces(exec_state: &mut ExecState, args: &Args, face_infos: Vec<ExtrusionFaceInfo>) -> Faces {
|
||||
let mut faces = Faces {
|
||||
sides: HashMap::with_capacity(face_infos.len()),
|
||||
..Default::default()
|
||||
};
|
||||
if args.ctx.no_engine_commands().await {
|
||||
// Create fake IDs for start and end caps, to make extrudes mock-execute safe
|
||||
faces.start_cap_id = Some(exec_state.next_uuid());
|
||||
faces.end_cap_id = Some(exec_state.next_uuid());
|
||||
}
|
||||
for face_info in face_infos {
|
||||
match face_info.cap {
|
||||
ExtrusionFaceCapType::Bottom => faces.start_cap_id = face_info.face_id,
|
||||
ExtrusionFaceCapType::Top => faces.end_cap_id = face_info.face_id,
|
||||
ExtrusionFaceCapType::Both => {
|
||||
faces.end_cap_id = face_info.face_id;
|
||||
faces.start_cap_id = face_info.face_id;
|
||||
}
|
||||
ExtrusionFaceCapType::None => {
|
||||
if let Some(curve_id) = face_info.curve_id {
|
||||
faces.sides.insert(curve_id, face_info.face_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
faces
|
||||
}
|
Reference in New Issue
Block a user