2023-08-24 15:34:51 -07:00
|
|
|
//! Functions related to extruding.
|
|
|
|
|
2024-09-06 17:02:04 -05:00
|
|
|
use std::collections::HashMap;
|
|
|
|
|
2023-08-29 14:12:48 -07:00
|
|
|
use anyhow::Result;
|
|
|
|
use derive_docs::stdlib;
|
2024-09-19 14:06:29 -07:00
|
|
|
use kcmc::{
|
|
|
|
each_cmd as mcmd, length_unit::LengthUnit, ok_response::OkModelingCmdResponse, output::ExtrusionFaceInfo,
|
|
|
|
shared::ExtrusionFaceCapType, websocket::OkWebSocketResponseData, ModelingCmd,
|
|
|
|
};
|
2024-09-18 17:04:04 -05:00
|
|
|
use kittycad_modeling_cmds as kcmc;
|
2023-08-29 14:12:48 -07:00
|
|
|
use schemars::JsonSchema;
|
2024-03-22 10:23:04 +11:00
|
|
|
use uuid::Uuid;
|
2023-08-29 14:12:48 -07:00
|
|
|
|
2023-08-24 15:34:51 -07:00
|
|
|
use crate::{
|
|
|
|
errors::{KclError, KclErrorDetails},
|
2024-04-23 10:31:20 -07:00
|
|
|
executor::{
|
2024-09-16 15:10:33 -04:00
|
|
|
ExecState, ExtrudeGroup, ExtrudeGroupSet, ExtrudeSurface, GeoMeta, KclValue, Path, SketchGroup, SketchGroupSet,
|
2024-05-10 17:28:14 -07:00
|
|
|
SketchSurface,
|
2024-04-23 10:31:20 -07:00
|
|
|
},
|
2023-08-24 15:34:51 -07:00
|
|
|
std::Args,
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Extrudes by a given amount.
|
2024-09-16 15:10:33 -04:00
|
|
|
pub async fn extrude(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
2024-04-23 10:31:20 -07:00
|
|
|
let (length, sketch_group_set) = args.get_number_sketch_group_set()?;
|
2023-08-24 15:34:51 -07:00
|
|
|
|
2024-04-23 10:31:20 -07:00
|
|
|
let result = inner_extrude(length, sketch_group_set, args).await?;
|
2023-08-25 13:41:04 -07:00
|
|
|
|
2024-06-23 23:04:32 -07:00
|
|
|
Ok(result.into())
|
2023-08-25 13:41:04 -07:00
|
|
|
}
|
|
|
|
|
2024-08-06 20:27:26 -04:00
|
|
|
/// 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.
|
2024-03-13 12:56:46 -07:00
|
|
|
///
|
|
|
|
/// ```no_run
|
2024-05-22 09:15:38 -07:00
|
|
|
/// const example = startSketchOn('XZ')
|
2024-05-14 17:10:47 -07:00
|
|
|
/// |> startProfileAt([0, 0], %)
|
|
|
|
/// |> line([10, 0], %)
|
|
|
|
/// |> arc({
|
2024-07-29 13:18:55 -07:00
|
|
|
/// angleStart: 120,
|
|
|
|
/// angleEnd: 0,
|
2024-05-14 17:10:47 -07:00
|
|
|
/// radius: 5,
|
|
|
|
/// }, %)
|
|
|
|
/// |> line([5, 0], %)
|
|
|
|
/// |> line([0, 10], %)
|
|
|
|
/// |> bezierCurve({
|
|
|
|
/// control1: [-10, 0],
|
|
|
|
/// control2: [2, 10],
|
|
|
|
/// to: [-5, 10],
|
|
|
|
/// }, %)
|
|
|
|
/// |> line([-5, -2], %)
|
|
|
|
/// |> close(%)
|
|
|
|
/// |> extrude(10, %)
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// ```no_run
|
2024-05-22 09:15:38 -07:00
|
|
|
/// const exampleSketch = startSketchOn('XZ')
|
2024-05-14 17:10:47 -07:00
|
|
|
/// |> startProfileAt([-10, 0], %)
|
|
|
|
/// |> arc({
|
2024-07-29 13:18:55 -07:00
|
|
|
/// angleStart: 120,
|
|
|
|
/// angleEnd: -60,
|
2024-05-14 17:10:47 -07:00
|
|
|
/// radius: 5,
|
|
|
|
/// }, %)
|
|
|
|
/// |> line([10, 0], %)
|
|
|
|
/// |> line([5, 0], %)
|
|
|
|
/// |> bezierCurve({
|
|
|
|
/// control1: [-3, 0],
|
|
|
|
/// control2: [2, 10],
|
|
|
|
/// to: [-5, 10],
|
|
|
|
/// }, %)
|
|
|
|
/// |> line([-4, 10], %)
|
|
|
|
/// |> line([-5, -2], %)
|
|
|
|
/// |> close(%)
|
|
|
|
///
|
|
|
|
/// const example = extrude(10, exampleSketch)
|
2024-03-13 12:56:46 -07:00
|
|
|
/// ```
|
2023-08-25 13:41:04 -07:00
|
|
|
#[stdlib {
|
|
|
|
name = "extrude"
|
|
|
|
}]
|
2024-04-23 10:31:20 -07:00
|
|
|
async fn inner_extrude(length: f64, sketch_group_set: SketchGroupSet, args: Args) -> Result<ExtrudeGroupSet, KclError> {
|
2023-08-24 15:34:51 -07:00
|
|
|
let id = uuid::Uuid::new_v4();
|
2024-03-26 19:07:16 -07:00
|
|
|
|
2024-04-23 10:31:20 -07:00
|
|
|
// Extrude the element(s).
|
Remove KclValue::SketchGroup variant (#3446)
We can store Rust types like `SketchGroup` as their own variant of `KclValue`, or as `KclValue::UserVal`. Sometimes we store in one and try to read from the other, which fails. This causes bugs, like #3338.
Instead, we should use either ::SketchGroup or ::UserVal, and stop using the other. If we stopped using ::UserVal, we'd need a new variant for every Rust type we wanted to build, including user-defined types. So I don't think that's practical.
Instead, we should store every KCL value by de/serializing it into UserVal. This is a first step along that path, removing just the SketchGroup variants. If it goes well, we can remove the other specialized variants too.
My only concern is there might be performance implications from how frequently we convert between serde_json::Value and Rust types via Serde. But I'm not too worried -- there's no parsing JSON strings, just traversing serde_json::Value trees. This isn't great for performance but I think it'll probably be miniscule in comparison to doing all the API calls.
2024-08-21 11:06:48 -05:00
|
|
|
let sketch_groups: Vec<SketchGroup> = sketch_group_set.into();
|
2024-04-23 10:31:20 -07:00
|
|
|
let mut extrude_groups = Vec::new();
|
2024-06-23 23:04:32 -07:00
|
|
|
for sketch_group in &sketch_groups {
|
2024-07-09 23:39:59 -04:00
|
|
|
// 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(
|
|
|
|
uuid::Uuid::new_v4(),
|
2024-09-18 17:04:04 -05:00
|
|
|
ModelingCmd::from(mcmd::EnableSketchMode {
|
2024-07-09 23:39:59 -04:00
|
|
|
animated: false,
|
|
|
|
ortho: false,
|
|
|
|
entity_id: sketch_group.on.id(),
|
|
|
|
adjust_camera: false,
|
|
|
|
planar_normal: if let SketchSurface::Plane(plane) = &sketch_group.on {
|
|
|
|
// We pass in the normal for the plane here.
|
2024-09-04 23:27:12 -05:00
|
|
|
Some(plane.z_axis.into())
|
2024-07-09 23:39:59 -04:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
},
|
2024-09-18 17:04:04 -05:00
|
|
|
}),
|
2024-07-09 23:39:59 -04:00
|
|
|
)
|
|
|
|
.await?;
|
2024-07-05 15:37:30 -07:00
|
|
|
|
Batch extrudes (#3764)
Adds a new KCL executor benchmark which builds a `10` wide by `n` tall lego, with varying `n`. The benchmark runs a n = 1, 2, 3 etc build, so we can get an idea of how the speed changes with size.
This change improves execution speed by 25-36% depending on how many bumps there are. Tested by:
* Rust unit tests
* Open up modeling app, sketch a square, use the command palette to extrude it
* Open up the Bambu printer "poop chute" model, it all extrudes and works fine
Also fixes a bug: extrude, loft, revolve all trigger a GetExtrusionFaceInfo command. Due to a bug, the GetExtrusionFaceInfo command reused the Command ID from the previous extrude/loft/revolve. Fixed that.
2024-09-06 16:55:24 -05:00
|
|
|
args.batch_modeling_cmd(
|
2024-04-23 10:31:20 -07:00
|
|
|
id,
|
2024-09-18 17:04:04 -05:00
|
|
|
ModelingCmd::from(mcmd::Extrude {
|
|
|
|
target: sketch_group.id.into(),
|
|
|
|
distance: LengthUnit(length),
|
|
|
|
}),
|
2024-04-23 10:31:20 -07:00
|
|
|
)
|
|
|
|
.await?;
|
2024-07-09 23:39:59 -04:00
|
|
|
|
|
|
|
// Disable the sketch mode.
|
2024-09-18 17:04:04 -05:00
|
|
|
args.batch_modeling_cmd(
|
|
|
|
uuid::Uuid::new_v4(),
|
|
|
|
ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable {}),
|
|
|
|
)
|
|
|
|
.await?;
|
Batch extrudes (#3764)
Adds a new KCL executor benchmark which builds a `10` wide by `n` tall lego, with varying `n`. The benchmark runs a n = 1, 2, 3 etc build, so we can get an idea of how the speed changes with size.
This change improves execution speed by 25-36% depending on how many bumps there are. Tested by:
* Rust unit tests
* Open up modeling app, sketch a square, use the command palette to extrude it
* Open up the Bambu printer "poop chute" model, it all extrudes and works fine
Also fixes a bug: extrude, loft, revolve all trigger a GetExtrusionFaceInfo command. Due to a bug, the GetExtrusionFaceInfo command reused the Command ID from the previous extrude/loft/revolve. Fixed that.
2024-09-06 16:55:24 -05:00
|
|
|
extrude_groups.push(do_post_extrude(sketch_group.clone(), length, args.clone()).await?);
|
2024-04-23 10:31:20 -07:00
|
|
|
}
|
2023-10-11 18:30:04 -07:00
|
|
|
|
2024-06-23 23:04:32 -07:00
|
|
|
Ok(extrude_groups.into())
|
2024-03-26 19:07:16 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) async fn do_post_extrude(
|
Remove KclValue::SketchGroup variant (#3446)
We can store Rust types like `SketchGroup` as their own variant of `KclValue`, or as `KclValue::UserVal`. Sometimes we store in one and try to read from the other, which fails. This causes bugs, like #3338.
Instead, we should use either ::SketchGroup or ::UserVal, and stop using the other. If we stopped using ::UserVal, we'd need a new variant for every Rust type we wanted to build, including user-defined types. So I don't think that's practical.
Instead, we should store every KCL value by de/serializing it into UserVal. This is a first step along that path, removing just the SketchGroup variants. If it goes well, we can remove the other specialized variants too.
My only concern is there might be performance implications from how frequently we convert between serde_json::Value and Rust types via Serde. But I'm not too worried -- there's no parsing JSON strings, just traversing serde_json::Value trees. This isn't great for performance but I think it'll probably be miniscule in comparison to doing all the API calls.
2024-08-21 11:06:48 -05:00
|
|
|
sketch_group: SketchGroup,
|
2024-03-26 19:07:16 -07:00
|
|
|
length: f64,
|
|
|
|
args: Args,
|
|
|
|
) -> Result<Box<ExtrudeGroup>, KclError> {
|
2023-10-11 18:30:04 -07:00
|
|
|
// Bring the object to the front of the scene.
|
|
|
|
// See: https://github.com/KittyCAD/modeling-app/issues/806
|
2024-06-19 13:57:50 -07:00
|
|
|
args.batch_modeling_cmd(
|
2023-10-11 18:30:04 -07:00
|
|
|
uuid::Uuid::new_v4(),
|
2024-09-18 17:04:04 -05:00
|
|
|
ModelingCmd::from(mcmd::ObjectBringToFront {
|
2023-10-11 18:30:04 -07:00
|
|
|
object_id: sketch_group.id,
|
2024-09-18 17:04:04 -05:00
|
|
|
}),
|
2023-10-11 18:30:04 -07:00
|
|
|
)
|
|
|
|
.await?;
|
2023-08-24 15:34:51 -07:00
|
|
|
|
2024-02-12 18:08:42 -08:00
|
|
|
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],
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2024-09-23 22:42:51 +10:00
|
|
|
let edge_id = sketch_group.value.iter().find_map(|segment| match segment {
|
|
|
|
Path::ToPoint { base } | Path::Circle { base, .. } => Some(base.geo_meta.id),
|
|
|
|
_ => None,
|
|
|
|
});
|
2024-02-12 18:08:42 -08:00
|
|
|
|
|
|
|
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],
|
|
|
|
}));
|
|
|
|
};
|
|
|
|
|
Remove KclValue::SketchGroup variant (#3446)
We can store Rust types like `SketchGroup` as their own variant of `KclValue`, or as `KclValue::UserVal`. Sometimes we store in one and try to read from the other, which fails. This causes bugs, like #3338.
Instead, we should use either ::SketchGroup or ::UserVal, and stop using the other. If we stopped using ::UserVal, we'd need a new variant for every Rust type we wanted to build, including user-defined types. So I don't think that's practical.
Instead, we should store every KCL value by de/serializing it into UserVal. This is a first step along that path, removing just the SketchGroup variants. If it goes well, we can remove the other specialized variants too.
My only concern is there might be performance implications from how frequently we convert between serde_json::Value and Rust types via Serde. But I'm not too worried -- there's no parsing JSON strings, just traversing serde_json::Value trees. This isn't great for performance but I think it'll probably be miniscule in comparison to doing all the API calls.
2024-08-21 11:06:48 -05:00
|
|
|
let mut sketch_group = sketch_group.clone();
|
2024-02-26 14:54:42 -08:00
|
|
|
|
|
|
|
// If we were sketching on a face, we need the original face id.
|
2024-06-21 19:54:18 -07:00
|
|
|
if let SketchSurface::Face(ref face) = sketch_group.on {
|
2024-06-23 19:19:24 -07:00
|
|
|
sketch_group.id = face.extrude_group.sketch_group.id;
|
2024-02-26 14:54:42 -08:00
|
|
|
}
|
|
|
|
|
2024-02-12 18:08:42 -08:00
|
|
|
let solid3d_info = args
|
|
|
|
.send_modeling_cmd(
|
Batch extrudes (#3764)
Adds a new KCL executor benchmark which builds a `10` wide by `n` tall lego, with varying `n`. The benchmark runs a n = 1, 2, 3 etc build, so we can get an idea of how the speed changes with size.
This change improves execution speed by 25-36% depending on how many bumps there are. Tested by:
* Rust unit tests
* Open up modeling app, sketch a square, use the command palette to extrude it
* Open up the Bambu printer "poop chute" model, it all extrudes and works fine
Also fixes a bug: extrude, loft, revolve all trigger a GetExtrusionFaceInfo command. Due to a bug, the GetExtrusionFaceInfo command reused the Command ID from the previous extrude/loft/revolve. Fixed that.
2024-09-06 16:55:24 -05:00
|
|
|
uuid::Uuid::new_v4(),
|
2024-09-18 17:04:04 -05:00
|
|
|
ModelingCmd::from(mcmd::Solid3dGetExtrusionFaceInfo {
|
2024-02-12 18:08:42 -08:00
|
|
|
edge_id,
|
|
|
|
object_id: sketch_group.id,
|
2024-09-18 17:04:04 -05:00
|
|
|
}),
|
2024-02-12 18:08:42 -08:00
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
|
2024-09-18 17:04:04 -05:00
|
|
|
let face_infos = if let OkWebSocketResponseData::Modeling {
|
|
|
|
modeling_response: OkModelingCmdResponse::Solid3dGetExtrusionFaceInfo(data),
|
2024-02-12 18:08:42 -08:00
|
|
|
} = solid3d_info
|
|
|
|
{
|
|
|
|
data.faces
|
|
|
|
} else {
|
|
|
|
vec![]
|
|
|
|
};
|
|
|
|
|
2024-09-04 23:27:12 -05:00
|
|
|
for (curve_id, face_id) in face_infos
|
|
|
|
.iter()
|
|
|
|
.filter(|face_info| face_info.cap == ExtrusionFaceCapType::None)
|
|
|
|
.filter_map(|face_info| {
|
2024-08-30 19:46:48 +10:00
|
|
|
if let (Some(curve_id), Some(face_id)) = (face_info.curve_id, face_info.face_id) {
|
2024-09-04 23:27:12 -05:00
|
|
|
Some((curve_id, face_id))
|
|
|
|
} else {
|
|
|
|
None
|
2024-08-30 19:46:48 +10:00
|
|
|
}
|
2024-09-04 23:27:12 -05:00
|
|
|
})
|
|
|
|
{
|
|
|
|
// 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(
|
|
|
|
uuid::Uuid::new_v4(),
|
2024-09-18 17:04:04 -05:00
|
|
|
ModelingCmd::from(mcmd::Solid3dGetOppositeEdge {
|
2024-09-04 23:27:12 -05:00
|
|
|
edge_id: curve_id,
|
|
|
|
object_id: sketch_group.id,
|
|
|
|
face_id,
|
2024-09-18 17:04:04 -05:00
|
|
|
}),
|
2024-09-04 23:27:12 -05:00
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
args.batch_modeling_cmd(
|
|
|
|
uuid::Uuid::new_v4(),
|
2024-09-26 18:25:05 +10:00
|
|
|
ModelingCmd::from(mcmd::Solid3dGetNextAdjacentEdge {
|
2024-09-04 23:27:12 -05:00
|
|
|
edge_id: curve_id,
|
|
|
|
object_id: sketch_group.id,
|
|
|
|
face_id,
|
2024-09-18 17:04:04 -05:00
|
|
|
}),
|
2024-09-04 23:27:12 -05:00
|
|
|
)
|
|
|
|
.await?;
|
2024-08-30 19:46:48 +10:00
|
|
|
}
|
|
|
|
|
2024-09-06 17:02:04 -05:00
|
|
|
let Faces {
|
|
|
|
sides: face_id_map,
|
|
|
|
start_cap_id,
|
|
|
|
end_cap_id,
|
|
|
|
} = analyze_faces(&args, face_infos);
|
2024-02-12 18:08:42 -08:00
|
|
|
// Iterate over the sketch_group.value array and add face_id to GeoMeta
|
2024-09-04 23:27:12 -05:00
|
|
|
let new_value = sketch_group
|
|
|
|
.value
|
|
|
|
.iter()
|
|
|
|
.flat_map(|path| {
|
|
|
|
if let Some(Some(actual_face_id)) = face_id_map.get(&path.get_base().geo_meta.id) {
|
|
|
|
match path {
|
2024-09-23 22:42:51 +10:00
|
|
|
Path::TangentialArc { .. } | Path::TangentialArcTo { .. } | Path::Circle { .. } => {
|
2024-09-04 23:27:12 -05:00
|
|
|
let extrude_surface = ExtrudeSurface::ExtrudeArc(crate::executor::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.clone(),
|
|
|
|
},
|
|
|
|
});
|
|
|
|
Some(extrude_surface)
|
|
|
|
}
|
|
|
|
Path::Base { .. } | Path::ToPoint { .. } | Path::Horizontal { .. } | Path::AngledLineTo { .. } => {
|
|
|
|
let extrude_surface = ExtrudeSurface::ExtrudePlane(crate::executor::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.clone(),
|
|
|
|
},
|
|
|
|
});
|
|
|
|
Some(extrude_surface)
|
|
|
|
}
|
2024-02-22 19:07:17 -08:00
|
|
|
}
|
2024-09-04 23:27:12 -05:00
|
|
|
} else if args.ctx.is_mock {
|
|
|
|
// Only pre-populate the extrude surface if we are in mock mode.
|
|
|
|
|
|
|
|
let extrude_surface = ExtrudeSurface::ExtrudePlane(crate::executor::ExtrudePlane {
|
|
|
|
// pushing this values with a fake face_id to make extrudes mock-execute safe
|
|
|
|
face_id: Uuid::new_v4(),
|
|
|
|
tag: path.get_base().tag.clone(),
|
|
|
|
geo_meta: GeoMeta {
|
|
|
|
id: path.get_base().geo_meta.id,
|
|
|
|
metadata: path.get_base().geo_meta.metadata.clone(),
|
|
|
|
},
|
|
|
|
});
|
|
|
|
Some(extrude_surface)
|
|
|
|
} else {
|
|
|
|
None
|
2024-02-22 19:07:17 -08:00
|
|
|
}
|
2024-09-04 23:27:12 -05:00
|
|
|
})
|
|
|
|
.collect();
|
2024-02-12 18:08:42 -08:00
|
|
|
|
2023-09-19 14:20:14 -07:00
|
|
|
Ok(Box::new(ExtrudeGroup {
|
2024-02-13 10:26:09 -08:00
|
|
|
// Ok so you would think that the id would be the id of the extrude group,
|
|
|
|
// that we passed in to the function, but it's actually the id of the
|
|
|
|
// sketch group.
|
|
|
|
id: sketch_group.id,
|
2024-02-12 18:08:42 -08:00
|
|
|
value: new_value,
|
2024-09-04 23:27:12 -05:00
|
|
|
meta: sketch_group.meta.clone(),
|
|
|
|
sketch_group,
|
2023-08-24 15:34:51 -07:00
|
|
|
height: length,
|
2024-02-12 18:08:42 -08:00
|
|
|
start_cap_id,
|
|
|
|
end_cap_id,
|
2024-08-12 17:56:45 -05:00
|
|
|
edge_cuts: vec![],
|
2023-09-19 14:20:14 -07:00
|
|
|
}))
|
2023-08-24 15:34:51 -07:00
|
|
|
}
|
2024-09-06 17:02:04 -05:00
|
|
|
|
|
|
|
#[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>,
|
|
|
|
}
|
|
|
|
|
|
|
|
fn analyze_faces(args: &Args, face_infos: Vec<ExtrusionFaceInfo>) -> Faces {
|
|
|
|
let mut faces = Faces {
|
|
|
|
sides: HashMap::with_capacity(face_infos.len()),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
if args.ctx.is_mock {
|
|
|
|
// Create fake IDs for start and end caps, to make extrudes mock-execute safe
|
|
|
|
faces.start_cap_id = Some(Uuid::new_v4());
|
|
|
|
faces.end_cap_id = Some(Uuid::new_v4());
|
|
|
|
}
|
|
|
|
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::None => {
|
|
|
|
if let Some(curve_id) = face_info.curve_id {
|
|
|
|
faces.sides.insert(curve_id, face_info.face_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
faces
|
|
|
|
}
|