use deterministic ids in more places (#6064)
* dont redact the ids now that they are deterministic Signed-off-by: Jess Frazelle <github@jessfraz.com> pass arouund id generator more Signed-off-by: Jess Frazelle <github@jessfraz.com> change the anme space Signed-off-by: Jess Frazelle <github@jessfraz.com> updates and re-run Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixups Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanup old files Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanup old files Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
@ -153,8 +153,3 @@ harness = false
|
||||
name = "executor"
|
||||
path = "e2e/executor/main.rs"
|
||||
required-features = ["engine"]
|
||||
|
||||
[[test]]
|
||||
name = "modify"
|
||||
path = "e2e/modify/main.rs"
|
||||
required-features = ["engine"]
|
||||
|
@ -1,232 +0,0 @@
|
||||
use anyhow::Result;
|
||||
use kcl_lib::{
|
||||
exec::{KclValue, PlaneType},
|
||||
modify_ast_for_sketch, ExecState, ExecutorContext, ModuleId, Program, SourceRange,
|
||||
};
|
||||
use kittycad_modeling_cmds::{each_cmd as mcmd, length_unit::LengthUnit, shared::Point3d, ModelingCmd};
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
/// Setup the engine and parse code for an ast.
|
||||
async fn setup(code: &str, name: &str) -> Result<(ExecutorContext, Program, ModuleId, uuid::Uuid)> {
|
||||
let program = Program::parse_no_errs(code)?;
|
||||
let ctx = kcl_lib::ExecutorContext::new_with_default_client(Default::default()).await?;
|
||||
let mut exec_state = ExecState::new(&ctx);
|
||||
let result = ctx.run(&program, &mut exec_state).await?;
|
||||
let outcome = exec_state.to_wasm_outcome(result.0).await;
|
||||
|
||||
// We need to get the sketch ID.
|
||||
let KclValue::Sketch { value: sketch } = outcome.variables.get(name).unwrap() else {
|
||||
anyhow::bail!("part001 not found in: {:?}", outcome.variables);
|
||||
};
|
||||
let sketch_id = sketch.id;
|
||||
|
||||
let plane_id = uuid::Uuid::new_v4();
|
||||
ctx.engine
|
||||
.send_modeling_cmd(
|
||||
plane_id,
|
||||
SourceRange::default(),
|
||||
&ModelingCmd::from(mcmd::MakePlane {
|
||||
clobber: false,
|
||||
origin: Point3d::default(),
|
||||
size: LengthUnit(60.0),
|
||||
x_axis: Point3d { x: 1.0, y: 0.0, z: 0.0 },
|
||||
y_axis: Point3d { x: 0.0, y: 1.0, z: 0.0 },
|
||||
hide: Some(true),
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Enter sketch mode.
|
||||
// We can't get control points without being in sketch mode.
|
||||
// You can however get path info without sketch mode.
|
||||
ctx.engine
|
||||
.send_modeling_cmd(
|
||||
uuid::Uuid::new_v4(),
|
||||
SourceRange::default(),
|
||||
&ModelingCmd::from(mcmd::EnableSketchMode {
|
||||
animated: false,
|
||||
ortho: true,
|
||||
entity_id: plane_id,
|
||||
planar_normal: Some(Point3d { x: 0.0, y: 0.0, z: 1.0 }),
|
||||
adjust_camera: false,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok((ctx, program, ModuleId::default(), sketch_id))
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn kcl_test_modify_sketch_part001() {
|
||||
let name = "part001";
|
||||
let code = format!(
|
||||
r#"{} = startSketchOn(XY)
|
||||
|> startProfileAt([8.41, 5.78], %)
|
||||
|> line(end = [7.37, -11])
|
||||
|> line(end = [-8.69, -3.75])
|
||||
|> line(end = [-5, 4.25])
|
||||
"#,
|
||||
name
|
||||
);
|
||||
|
||||
let (ctx, program, module_id, sketch_id) = setup(&code, name).await.unwrap();
|
||||
let mut new_program = program.clone();
|
||||
let new_code = modify_ast_for_sketch(&ctx.engine, &mut new_program, module_id, name, PlaneType::XY, sketch_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Make sure the code is the same.
|
||||
assert_eq!(code, new_code);
|
||||
// Make sure the program is the same.
|
||||
assert_eq!(new_program, program);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn kcl_test_modify_sketch_part002() {
|
||||
let name = "part002";
|
||||
let code = format!(
|
||||
r#"{} = startSketchOn(XY)
|
||||
|> startProfileAt([8.41, 5.78], %)
|
||||
|> line(end = [7.42, -8.62])
|
||||
|> line(end = [-6.38, -3.51])
|
||||
|> line(end = [-3.77, 3.56])
|
||||
"#,
|
||||
name
|
||||
);
|
||||
|
||||
let (ctx, program, module_id, sketch_id) = setup(&code, name).await.unwrap();
|
||||
let mut new_program = program.clone();
|
||||
let new_code = modify_ast_for_sketch(&ctx.engine, &mut new_program, module_id, name, PlaneType::XY, sketch_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Make sure the code is the same.
|
||||
assert_eq!(code, new_code);
|
||||
// Make sure the program is the same.
|
||||
assert_eq!(new_program, program);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[ignore] // until KittyCAD/engine#1434 is fixed.
|
||||
async fn kcl_test_modify_close_sketch() {
|
||||
let name = "part002";
|
||||
let code = format!(
|
||||
r#"{} = startSketchOn(XY)
|
||||
|> startProfileAt([7.91, 3.89], %)
|
||||
|> line(end = [7.42, -8.62])
|
||||
|> line(end = [-6.38, -3.51])
|
||||
|> line(end = [-3.77, 3.56])
|
||||
|> close()
|
||||
"#,
|
||||
name
|
||||
);
|
||||
|
||||
let (ctx, program, module_id, sketch_id) = setup(&code, name).await.unwrap();
|
||||
let mut new_program = program.clone();
|
||||
let new_code = modify_ast_for_sketch(&ctx.engine, &mut new_program, module_id, name, PlaneType::XY, sketch_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Make sure the code is the same.
|
||||
assert_eq!(code, new_code);
|
||||
// Make sure the program is the same.
|
||||
assert_eq!(new_program, program);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn kcl_test_modify_line_to_close_sketch() {
|
||||
let name = "part002";
|
||||
let code = format!(
|
||||
r#"const {} = startSketchOn(XY)
|
||||
|> startProfileAt([7.91, 3.89], %)
|
||||
|> line(end = [7.42, -8.62])
|
||||
|> line(end = [-6.38, -3.51])
|
||||
|> line(end = [-3.77, 3.56])
|
||||
|> line(endAbsolute = [7.91, 3.89])
|
||||
"#,
|
||||
name
|
||||
);
|
||||
|
||||
let (ctx, program, module_id, sketch_id) = setup(&code, name).await.unwrap();
|
||||
let mut new_program = program.clone();
|
||||
let new_code = modify_ast_for_sketch(&ctx.engine, &mut new_program, module_id, name, PlaneType::XY, sketch_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Make sure the code is the same.
|
||||
assert_eq!(
|
||||
new_code,
|
||||
format!(
|
||||
r#"{} = startSketchOn(XY)
|
||||
|> startProfileAt([7.91, 3.89], %)
|
||||
|> line(end = [7.42, -8.62])
|
||||
|> line(end = [-6.38, -3.51])
|
||||
|> line(end = [-3.77, 3.56])
|
||||
|> close()
|
||||
"#,
|
||||
name
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn kcl_test_modify_with_constraint() {
|
||||
let name = "part002";
|
||||
let code = format!(
|
||||
r#"const thing = 12
|
||||
const {} = startSketchOn(XY)
|
||||
|> startProfileAt([7.91, 3.89], %)
|
||||
|> line(end = [7.42, -8.62])
|
||||
|> line(end = [-6.38, -3.51])
|
||||
|> line(end = [-3.77, 3.56])
|
||||
|> line(endAbsolute = [thing, 3.89])
|
||||
"#,
|
||||
name
|
||||
);
|
||||
|
||||
let (ctx, program, module_id, sketch_id) = setup(&code, name).await.unwrap();
|
||||
let mut new_program = program.clone();
|
||||
let result = modify_ast_for_sketch(&ctx.engine, &mut new_program, module_id, name, PlaneType::XY, sketch_id).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
result.unwrap_err().message(),
|
||||
"Sketch part002 is constrained `partial` and cannot be modified",
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn kcl_test_modify_line_should_close_sketch() {
|
||||
let name = "part003";
|
||||
let code = format!(
|
||||
r#"const {} = startSketchOn(XY)
|
||||
|> startProfileAt([13.69, 3.8], %)
|
||||
|> line(end = [4.23, -11.79])
|
||||
|> line(end = [-10.7, -1.16])
|
||||
|> line(end = [-3.72, 8.69])
|
||||
|> line(end = [10.19, 4.26])
|
||||
"#,
|
||||
name
|
||||
);
|
||||
|
||||
let (ctx, program, module_id, sketch_id) = setup(&code, name).await.unwrap();
|
||||
let mut new_program = program.clone();
|
||||
let new_code = modify_ast_for_sketch(&ctx.engine, &mut new_program, module_id, name, PlaneType::XY, sketch_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Make sure the code is the same.
|
||||
assert_eq!(
|
||||
new_code,
|
||||
format!(
|
||||
r#"{} = startSketchOn(XY)
|
||||
|> startProfileAt([13.69, 3.8], %)
|
||||
|> line(end = [4.23, -11.79])
|
||||
|> line(end = [-10.7, -1.16])
|
||||
|> line(end = [-3.72, 8.69])
|
||||
|> close()
|
||||
"#,
|
||||
name
|
||||
)
|
||||
);
|
||||
}
|
@ -170,7 +170,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
self.clear_queues().await;
|
||||
|
||||
self.batch_modeling_cmd(
|
||||
uuid::Uuid::new_v4(),
|
||||
id_generator.next_uuid(),
|
||||
source_range,
|
||||
&ModelingCmd::SceneClearAll(mcmd::SceneClearAll::default()),
|
||||
)
|
||||
@ -195,9 +195,10 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
&self,
|
||||
visible: bool,
|
||||
source_range: SourceRange,
|
||||
id_generator: &mut IdGenerator,
|
||||
) -> Result<(), crate::errors::KclError> {
|
||||
self.batch_modeling_cmd(
|
||||
uuid::Uuid::new_v4(),
|
||||
id_generator.next_uuid(),
|
||||
source_range,
|
||||
&ModelingCmd::from(mcmd::EdgeLinesVisible { hidden: !visible }),
|
||||
)
|
||||
@ -231,10 +232,11 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
&self,
|
||||
units: crate::UnitLength,
|
||||
source_range: SourceRange,
|
||||
id_generator: &mut IdGenerator,
|
||||
) -> Result<(), crate::errors::KclError> {
|
||||
// Before we even start executing the program, set the units.
|
||||
self.batch_modeling_cmd(
|
||||
uuid::Uuid::new_v4(),
|
||||
id_generator.next_uuid(),
|
||||
source_range,
|
||||
&ModelingCmd::from(mcmd::SetSceneUnits { unit: units.into() }),
|
||||
)
|
||||
@ -248,15 +250,18 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
&self,
|
||||
settings: &crate::ExecutorSettings,
|
||||
source_range: SourceRange,
|
||||
id_generator: &mut IdGenerator,
|
||||
) -> Result<(), crate::errors::KclError> {
|
||||
// Set the edge visibility.
|
||||
self.set_edge_visibility(settings.highlight_edges, source_range).await?;
|
||||
self.set_edge_visibility(settings.highlight_edges, source_range, id_generator)
|
||||
.await?;
|
||||
|
||||
// Change the units.
|
||||
self.set_units(settings.units, source_range).await?;
|
||||
self.set_units(settings.units, source_range, id_generator).await?;
|
||||
|
||||
// Send the command to show the grid.
|
||||
self.modify_grid(!settings.show_grid, source_range).await?;
|
||||
self.modify_grid(!settings.show_grid, source_range, id_generator)
|
||||
.await?;
|
||||
|
||||
// We do not have commands for changing ssao on the fly.
|
||||
|
||||
@ -502,6 +507,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
y_axis: Point3d,
|
||||
color: Option<Color>,
|
||||
source_range: SourceRange,
|
||||
id_generator: &mut IdGenerator,
|
||||
) -> Result<uuid::Uuid, KclError> {
|
||||
// Create new default planes.
|
||||
let default_size = 100.0;
|
||||
@ -524,7 +530,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
if let Some(color) = color {
|
||||
// Set the color.
|
||||
self.batch_modeling_cmd(
|
||||
uuid::Uuid::new_v4(),
|
||||
id_generator.next_uuid(),
|
||||
source_range,
|
||||
&ModelingCmd::from(mcmd::PlaneSetColor { color, plane_id }),
|
||||
)
|
||||
@ -615,7 +621,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
for (name, plane_id, x_axis, y_axis, color) in plane_settings {
|
||||
planes.insert(
|
||||
name,
|
||||
self.make_default_plane(plane_id, x_axis, y_axis, color, source_range)
|
||||
self.make_default_plane(plane_id, x_axis, y_axis, color, source_range, id_generator)
|
||||
.await?,
|
||||
);
|
||||
}
|
||||
@ -701,10 +707,15 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
}))
|
||||
}
|
||||
|
||||
async fn modify_grid(&self, hidden: bool, source_range: SourceRange) -> Result<(), KclError> {
|
||||
async fn modify_grid(
|
||||
&self,
|
||||
hidden: bool,
|
||||
source_range: SourceRange,
|
||||
id_generator: &mut IdGenerator,
|
||||
) -> Result<(), KclError> {
|
||||
// Hide/show the grid.
|
||||
self.batch_modeling_cmd(
|
||||
uuid::Uuid::new_v4(),
|
||||
id_generator.next_uuid(),
|
||||
source_range,
|
||||
&ModelingCmd::from(mcmd::ObjectVisible {
|
||||
hidden,
|
||||
@ -715,7 +726,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
|
||||
// Hide/show the grid scale text.
|
||||
self.batch_modeling_cmd(
|
||||
uuid::Uuid::new_v4(),
|
||||
id_generator.next_uuid(),
|
||||
source_range,
|
||||
&ModelingCmd::from(mcmd::ObjectVisible {
|
||||
hidden,
|
||||
|
@ -64,7 +64,11 @@ impl ExecutorContext {
|
||||
let new_units = exec_state.length_unit();
|
||||
if !self.engine.execution_kind().await.is_isolated() {
|
||||
self.engine
|
||||
.set_units(new_units.into(), annotation.as_source_range())
|
||||
.set_units(
|
||||
new_units.into(),
|
||||
annotation.as_source_range(),
|
||||
exec_state.id_generator(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
} else {
|
||||
@ -143,7 +147,9 @@ impl ExecutorContext {
|
||||
// command and we'd need to flush the batch again.
|
||||
// This avoids that.
|
||||
if !exec_kind.is_isolated() && new_units != old_units && *path != ModulePath::Main {
|
||||
self.engine.set_units(old_units.into(), Default::default()).await?;
|
||||
self.engine
|
||||
.set_units(old_units.into(), Default::default(), exec_state.id_generator())
|
||||
.await?;
|
||||
}
|
||||
self.engine.replace_execution_kind(original_execution).await;
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
use crate::execution::ModuleId;
|
||||
|
||||
const NAMESPACE_KCL: uuid::Uuid = uuid::uuid!("efcd6508-4ce6-4a09-8317-e6a6994a3cd7");
|
||||
const NAMESPACE_KCL: uuid::Uuid = uuid::uuid!("8bda3118-75eb-58c7-a866-bef1dcb495e7");
|
||||
|
||||
/// A generator for ArtifactIds that can be stable across executions.
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
|
@ -618,7 +618,7 @@ impl ExecutorContext {
|
||||
if reapply_settings
|
||||
&& self
|
||||
.engine
|
||||
.reapply_settings(&self.settings, Default::default())
|
||||
.reapply_settings(&self.settings, Default::default(), old_state.id_generator())
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
@ -636,7 +636,7 @@ impl ExecutorContext {
|
||||
CacheResult::NoAction(true) => {
|
||||
if self
|
||||
.engine
|
||||
.reapply_settings(&self.settings, Default::default())
|
||||
.reapply_settings(&self.settings, Default::default(), old_state.id_generator())
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
@ -737,7 +737,7 @@ impl ExecutorContext {
|
||||
|
||||
// Re-apply the settings, in case the cache was busted.
|
||||
self.engine
|
||||
.reapply_settings(&self.settings, Default::default())
|
||||
.reapply_settings(&self.settings, Default::default(), exec_state.id_generator())
|
||||
.await
|
||||
.map_err(KclErrorWithOutputs::no_outputs)?;
|
||||
|
||||
|
@ -93,7 +93,7 @@ pub use lsp::{
|
||||
kcl::{Backend as KclLspBackend, Server as KclLspServerSubCommand},
|
||||
};
|
||||
pub use modules::ModuleId;
|
||||
pub use parsing::ast::{modify::modify_ast_for_sketch, types::FormatOptions};
|
||||
pub use parsing::ast::types::FormatOptions;
|
||||
pub use settings::types::{project::ProjectConfiguration, Configuration, UnitLength};
|
||||
pub use source_range::SourceRange;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
|
@ -1,5 +1,4 @@
|
||||
pub(crate) mod digest;
|
||||
pub mod modify;
|
||||
pub mod types;
|
||||
|
||||
use crate::{
|
||||
|
@ -1,285 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use kcmc::{
|
||||
each_cmd as mcmd, ok_response::OkModelingCmdResponse, shared::PathCommand, websocket::OkWebSocketResponseData,
|
||||
ModelingCmd,
|
||||
};
|
||||
use kittycad_modeling_cmds as kcmc;
|
||||
|
||||
use super::types::{CallExpressionKw, Identifier, LabeledArg, LiteralValue};
|
||||
use crate::{
|
||||
engine::EngineManager,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::Point2d,
|
||||
parsing::ast::types::{
|
||||
ArrayExpression, CallExpression, ConstraintLevel, FormatOptions, Literal, Node, PipeExpression,
|
||||
PipeSubstitution, VariableDeclarator,
|
||||
},
|
||||
source_range::SourceRange,
|
||||
ModuleId, Program,
|
||||
};
|
||||
|
||||
type Point3d = kcmc::shared::Point3d<f64>;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// The control point data for a curve or line.
|
||||
struct ControlPointData {
|
||||
/// The control points for the curve or line.
|
||||
points: Vec<Point3d>,
|
||||
/// The command that created this curve or line.
|
||||
_command: PathCommand,
|
||||
/// The id of the curve or line.
|
||||
_id: uuid::Uuid,
|
||||
}
|
||||
|
||||
const EPSILON: f64 = 0.015625; // or 2^-6
|
||||
|
||||
/// Update the AST to reflect the new state of the program after something like
|
||||
/// a move or a new line.
|
||||
pub async fn modify_ast_for_sketch(
|
||||
engine: &Arc<Box<dyn EngineManager>>,
|
||||
program: &mut Program,
|
||||
module_id: ModuleId,
|
||||
// The name of the sketch.
|
||||
sketch_name: &str,
|
||||
// The type of plane the sketch is on. `XY` or `XZ`, etc
|
||||
plane: crate::execution::PlaneType,
|
||||
// The ID of the parent sketch.
|
||||
sketch_id: uuid::Uuid,
|
||||
) -> Result<String, KclError> {
|
||||
// First we need to check if this sketch is constrained (even partially).
|
||||
// If it is, we cannot modify it.
|
||||
|
||||
// Get the information about the sketch.
|
||||
if let Some(ast_sketch) = program.ast.get_variable(sketch_name) {
|
||||
let constraint_level = match ast_sketch {
|
||||
super::types::Definition::Variable(var) => var.get_constraint_level(),
|
||||
super::types::Definition::Import(import) => import.get_constraint_level(),
|
||||
super::types::Definition::Type(_) => ConstraintLevel::Ignore {
|
||||
source_ranges: Vec::new(),
|
||||
},
|
||||
};
|
||||
match &constraint_level {
|
||||
ConstraintLevel::None { source_ranges: _ } => {}
|
||||
ConstraintLevel::Ignore { source_ranges: _ } => {}
|
||||
ConstraintLevel::Partial {
|
||||
source_ranges: _,
|
||||
levels,
|
||||
} => {
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
message: format!(
|
||||
"Sketch {} is constrained `{}` and cannot be modified",
|
||||
sketch_name, constraint_level
|
||||
),
|
||||
source_ranges: levels.get_all_partial_or_full_source_ranges(),
|
||||
}));
|
||||
}
|
||||
ConstraintLevel::Full { source_ranges } => {
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
message: format!(
|
||||
"Sketch {} is constrained `{}` and cannot be modified",
|
||||
sketch_name, constraint_level
|
||||
),
|
||||
source_ranges: source_ranges.clone(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Let's start by getting the path info.
|
||||
|
||||
// Let's get the path info.
|
||||
let resp = engine
|
||||
.send_modeling_cmd(
|
||||
uuid::Uuid::new_v4(),
|
||||
SourceRange::default(),
|
||||
&ModelingCmd::PathGetInfo(mcmd::PathGetInfo { path_id: sketch_id }),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let OkWebSocketResponseData::Modeling {
|
||||
modeling_response: OkModelingCmdResponse::PathGetInfo(path_info),
|
||||
} = &resp
|
||||
else {
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
message: format!("Get path info response was not as expected: {:?}", resp),
|
||||
source_ranges: vec![SourceRange::default()],
|
||||
}));
|
||||
};
|
||||
|
||||
// Now let's get the control points for all the segments.
|
||||
// TODO: We should probably await all these at once so we aren't going one by one.
|
||||
// But I guess this is fine for now.
|
||||
// We absolutely have to preserve the order of the control points.
|
||||
let mut control_points = Vec::new();
|
||||
for segment in &path_info.segments {
|
||||
if let Some(command_id) = &segment.command_id {
|
||||
let cmd = ModelingCmd::from(mcmd::CurveGetControlPoints {
|
||||
curve_id: (*command_id).into(),
|
||||
});
|
||||
let h = engine.send_modeling_cmd(uuid::Uuid::new_v4(), SourceRange::default(), &cmd);
|
||||
|
||||
let OkWebSocketResponseData::Modeling {
|
||||
modeling_response: OkModelingCmdResponse::CurveGetControlPoints(data),
|
||||
} = h.await?
|
||||
else {
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
message: format!("Curve get control points response was not as expected: {:?}", resp),
|
||||
source_ranges: vec![SourceRange::default()],
|
||||
}));
|
||||
};
|
||||
|
||||
control_points.push(ControlPointData {
|
||||
points: data.control_points.clone(),
|
||||
_command: segment.command,
|
||||
_id: (*command_id).into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if control_points.is_empty() {
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
message: format!("No control points found for sketch {}", sketch_name),
|
||||
source_ranges: vec![SourceRange::default()],
|
||||
}));
|
||||
}
|
||||
|
||||
let first_control_points = control_points.first().ok_or_else(|| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("No control points found for sketch {}", sketch_name),
|
||||
source_ranges: vec![SourceRange::default()],
|
||||
})
|
||||
})?;
|
||||
|
||||
let mut additional_lines = Vec::new();
|
||||
let mut last_point = first_control_points.points[1];
|
||||
for control_point in control_points[1..].iter() {
|
||||
additional_lines.push([
|
||||
(control_point.points[1].x - last_point.x),
|
||||
(control_point.points[1].y - last_point.y),
|
||||
]);
|
||||
last_point = Point3d {
|
||||
x: control_point.points[1].x,
|
||||
y: control_point.points[1].y,
|
||||
z: control_point.points[1].z,
|
||||
};
|
||||
}
|
||||
|
||||
// Okay now let's recalculate the sketch from the control points.
|
||||
let start_sketch_at_end = Point3d {
|
||||
x: (first_control_points.points[1].x - first_control_points.points[0].x),
|
||||
y: (first_control_points.points[1].y - first_control_points.points[0].y),
|
||||
z: (first_control_points.points[1].z - first_control_points.points[0].z),
|
||||
};
|
||||
let sketch = create_start_sketch_on(
|
||||
sketch_name,
|
||||
[first_control_points.points[0].x, first_control_points.points[0].y],
|
||||
[start_sketch_at_end.x, start_sketch_at_end.y],
|
||||
plane,
|
||||
additional_lines,
|
||||
)?;
|
||||
|
||||
// Add the sketch back to the program.
|
||||
program.ast.replace_variable(sketch_name, sketch);
|
||||
|
||||
let recasted = program.ast.recast(&FormatOptions::default(), 0);
|
||||
|
||||
// Re-parse the ast so we get the correct source ranges.
|
||||
program.ast = crate::parsing::parse_str(&recasted, module_id).parse_errs_as_err()?;
|
||||
|
||||
Ok(recasted)
|
||||
}
|
||||
|
||||
/// Create a pipe expression that starts a sketch at the given point and draws a line to the given point.
|
||||
fn create_start_sketch_on(
|
||||
name: &str,
|
||||
start: [f64; 2],
|
||||
end: [f64; 2],
|
||||
plane: crate::execution::PlaneType,
|
||||
additional_lines: Vec<[f64; 2]>,
|
||||
) -> Result<Node<VariableDeclarator>, KclError> {
|
||||
let start_sketch_on = CallExpression::new("startSketchOn", vec![Literal::new(plane.to_string().into()).into()])?;
|
||||
let start_profile_at = CallExpression::new(
|
||||
"startProfileAt",
|
||||
vec![
|
||||
ArrayExpression::new(vec![
|
||||
Literal::new(LiteralValue::from_f64_no_uom(round_before_recast(start[0]))).into(),
|
||||
Literal::new(LiteralValue::from_f64_no_uom(round_before_recast(start[1]))).into(),
|
||||
])
|
||||
.into(),
|
||||
PipeSubstitution::new().into(),
|
||||
],
|
||||
)?;
|
||||
|
||||
// Keep track of where we are so we can close the sketch if we need to.
|
||||
let mut current_position = Point2d {
|
||||
x: start[0],
|
||||
y: start[1],
|
||||
};
|
||||
current_position.x += end[0];
|
||||
current_position.y += end[1];
|
||||
|
||||
let expr = ArrayExpression::new(vec![
|
||||
Literal::new(LiteralValue::from_f64_no_uom(round_before_recast(end[0]))).into(),
|
||||
Literal::new(LiteralValue::from_f64_no_uom(round_before_recast(end[1]))).into(),
|
||||
])
|
||||
.into();
|
||||
let initial_line = CallExpressionKw::new(
|
||||
"line",
|
||||
None,
|
||||
vec![LabeledArg {
|
||||
label: Node::no_src(super::types::Identifier {
|
||||
name: "end".to_owned(),
|
||||
digest: None,
|
||||
}),
|
||||
arg: expr,
|
||||
}],
|
||||
)?;
|
||||
|
||||
let mut pipe_body = vec![start_sketch_on.into(), start_profile_at.into(), initial_line.into()];
|
||||
|
||||
for (index, line) in additional_lines.iter().enumerate() {
|
||||
current_position.x += line[0];
|
||||
current_position.y += line[1];
|
||||
|
||||
// If we are on the last line, check if we have to close the sketch.
|
||||
if index == additional_lines.len() - 1 {
|
||||
let diff_x = (current_position.x - start[0]).abs();
|
||||
let diff_y = (current_position.y - start[1]).abs();
|
||||
// Compare the end of the last line to the start of the first line.
|
||||
// This is a bit more lenient if you look at the value of epsilon.
|
||||
if diff_x <= EPSILON && diff_y <= EPSILON {
|
||||
// We have to close the sketch.
|
||||
let close = CallExpressionKw::new("close", None, vec![])?;
|
||||
pipe_body.push(close.into());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: we should check if we should close the sketch.
|
||||
let expr = ArrayExpression::new(vec![
|
||||
Literal::new(LiteralValue::from_f64_no_uom(round_before_recast(line[0]))).into(),
|
||||
Literal::new(LiteralValue::from_f64_no_uom(round_before_recast(line[1]))).into(),
|
||||
])
|
||||
.into();
|
||||
let line = CallExpressionKw::new(
|
||||
"line",
|
||||
None,
|
||||
vec![LabeledArg {
|
||||
arg: expr,
|
||||
label: Node::no_src(Identifier {
|
||||
name: "end".to_owned(),
|
||||
digest: None,
|
||||
}),
|
||||
}],
|
||||
)?;
|
||||
pipe_body.push(line.into());
|
||||
}
|
||||
|
||||
Ok(VariableDeclarator::new(name, PipeExpression::new(pipe_body).into()))
|
||||
}
|
||||
|
||||
fn round_before_recast(num: f64) -> f64 {
|
||||
// We use 2 decimal places.
|
||||
(num * 100.0).round() / 100.0
|
||||
}
|
@ -66,7 +66,7 @@ pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result<KclVal
|
||||
}));
|
||||
}
|
||||
|
||||
let result = inner_appearance(solids, data.color, data.metalness, data.roughness, args).await?;
|
||||
let result = inner_appearance(solids, data.color, data.metalness, data.roughness, exec_state, args).await?;
|
||||
Ok(result.into())
|
||||
}
|
||||
|
||||
@ -287,6 +287,7 @@ async fn inner_appearance(
|
||||
color: String,
|
||||
metalness: Option<f64>,
|
||||
roughness: Option<f64>,
|
||||
exec_state: &mut ExecState,
|
||||
args: Args,
|
||||
) -> Result<Vec<Solid>, KclError> {
|
||||
for solid in &solids {
|
||||
@ -306,7 +307,7 @@ async fn inner_appearance(
|
||||
};
|
||||
|
||||
args.batch_modeling_cmd(
|
||||
uuid::Uuid::new_v4(),
|
||||
exec_state.next_uuid(),
|
||||
ModelingCmd::from(mcmd::ObjectSetMaterialParamsPbr {
|
||||
object_id: solid.id,
|
||||
color,
|
||||
|
Reference in New Issue
Block a user