merge main and modeling-api
This commit is contained in:
@ -85,10 +85,8 @@ pub struct CompositeSolid {
|
||||
pub id: ArtifactId,
|
||||
pub sub_type: CompositeSolidSubType,
|
||||
/// Constituent solids of the composite solid.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub solid_ids: Vec<ArtifactId>,
|
||||
/// Tool solids used for asymmetric operations like subtract.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub tool_ids: Vec<ArtifactId>,
|
||||
pub code_ref: CodeRef,
|
||||
/// This is the ID of the composite solid that this is part of, if any, as a
|
||||
@ -141,12 +139,10 @@ pub struct Segment {
|
||||
pub path_id: ArtifactId,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub surface_id: Option<ArtifactId>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub edge_ids: Vec<ArtifactId>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub edge_cut_id: Option<ArtifactId>,
|
||||
pub code_ref: CodeRef,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub common_surface_ids: Vec<ArtifactId>,
|
||||
}
|
||||
|
||||
@ -158,9 +154,7 @@ pub struct Sweep {
|
||||
pub id: ArtifactId,
|
||||
pub sub_type: SweepSubType,
|
||||
pub path_id: ArtifactId,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub surface_ids: Vec<ArtifactId>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub edge_ids: Vec<ArtifactId>,
|
||||
pub code_ref: CodeRef,
|
||||
}
|
||||
@ -209,10 +203,8 @@ pub struct StartSketchOnPlane {
|
||||
pub struct Wall {
|
||||
pub id: ArtifactId,
|
||||
pub seg_id: ArtifactId,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub edge_cut_edge_ids: Vec<ArtifactId>,
|
||||
pub sweep_id: ArtifactId,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub path_ids: Vec<ArtifactId>,
|
||||
/// This is for the sketch-on-face plane, not for the wall itself. Traverse
|
||||
/// to the extrude and/or segment to get the wall's code_ref.
|
||||
@ -227,10 +219,8 @@ pub struct Wall {
|
||||
pub struct Cap {
|
||||
pub id: ArtifactId,
|
||||
pub sub_type: CapSubType,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub edge_cut_edge_ids: Vec<ArtifactId>,
|
||||
pub sweep_id: ArtifactId,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub path_ids: Vec<ArtifactId>,
|
||||
/// This is for the sketch-on-face plane, not for the cap itself. Traverse
|
||||
/// to the extrude and/or segment to get the cap's code_ref.
|
||||
@ -259,7 +249,6 @@ pub struct SweepEdge {
|
||||
#[serde(skip)]
|
||||
pub index: usize,
|
||||
pub sweep_id: ArtifactId,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub common_surface_ids: Vec<ArtifactId>,
|
||||
}
|
||||
|
||||
@ -278,7 +267,6 @@ pub struct EdgeCut {
|
||||
pub id: ArtifactId,
|
||||
pub sub_type: EdgeCutSubType,
|
||||
pub consumed_edge_id: ArtifactId,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub edge_ids: Vec<ArtifactId>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub surface_id: Option<ArtifactId>,
|
||||
@ -540,6 +528,11 @@ impl ArtifactGraph {
|
||||
self.map.is_empty()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn iter(&self) -> impl Iterator<Item = (&ArtifactId, &Artifact)> {
|
||||
self.map.iter()
|
||||
}
|
||||
|
||||
pub fn values(&self) -> impl Iterator<Item = &Artifact> {
|
||||
self.map.values()
|
||||
}
|
||||
@ -728,10 +721,7 @@ fn artifacts_to_update(
|
||||
exec_artifacts: &IndexMap<ArtifactId, Artifact>,
|
||||
) -> Result<Vec<Artifact>, KclError> {
|
||||
let uuid = artifact_command.cmd_id;
|
||||
let Some(response) = responses.get(&uuid) else {
|
||||
// Response not found or not successful.
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
let response = responses.get(&uuid);
|
||||
|
||||
// TODO: Build path-to-node from artifact_command source range. Right now,
|
||||
// we're serializing an empty array, and the TS wrapper fills it in with the
|
||||
@ -875,7 +865,7 @@ fn artifacts_to_update(
|
||||
new_path.seg_ids = vec![id];
|
||||
return_arr.push(Artifact::Path(new_path));
|
||||
}
|
||||
if let OkModelingCmdResponse::ClosePath(close_path) = response {
|
||||
if let Some(OkModelingCmdResponse::ClosePath(close_path)) = response {
|
||||
return_arr.push(Artifact::Solid2d(Solid2d {
|
||||
id: close_path.face_id.into(),
|
||||
path_id,
|
||||
@ -895,8 +885,8 @@ fn artifacts_to_update(
|
||||
ids: original_path_ids, ..
|
||||
}) => {
|
||||
let face_edge_infos = match response {
|
||||
OkModelingCmdResponse::EntityMirror(resp) => &resp.entity_face_edge_ids,
|
||||
OkModelingCmdResponse::EntityMirrorAcrossEdge(resp) => &resp.entity_face_edge_ids,
|
||||
Some(OkModelingCmdResponse::EntityMirror(resp)) => &resp.entity_face_edge_ids,
|
||||
Some(OkModelingCmdResponse::EntityMirrorAcrossEdge(resp)) => &resp.entity_face_edge_ids,
|
||||
_ => internal_error!(
|
||||
range,
|
||||
"Mirror response variant not handled: id={id:?}, cmd={cmd:?}, response={response:?}"
|
||||
@ -983,7 +973,7 @@ fn artifacts_to_update(
|
||||
return Ok(return_arr);
|
||||
}
|
||||
ModelingCmd::Loft(loft_cmd) => {
|
||||
let OkModelingCmdResponse::Loft(_) = response else {
|
||||
let Some(OkModelingCmdResponse::Loft(_)) = response else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
let mut return_arr = Vec::new();
|
||||
@ -1013,7 +1003,7 @@ fn artifacts_to_update(
|
||||
return Ok(return_arr);
|
||||
}
|
||||
ModelingCmd::Solid3dGetExtrusionFaceInfo(_) => {
|
||||
let OkModelingCmdResponse::Solid3dGetExtrusionFaceInfo(face_info) = response else {
|
||||
let Some(OkModelingCmdResponse::Solid3dGetExtrusionFaceInfo(face_info)) = response else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
let mut return_arr = Vec::new();
|
||||
@ -1134,7 +1124,7 @@ fn artifacts_to_update(
|
||||
return Ok(return_arr);
|
||||
}
|
||||
ModelingCmd::Solid3dGetAdjacencyInfo(kcmc::Solid3dGetAdjacencyInfo { .. }) => {
|
||||
let OkModelingCmdResponse::Solid3dGetAdjacencyInfo(info) = response else {
|
||||
let Some(OkModelingCmdResponse::Solid3dGetAdjacencyInfo(info)) = response else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
|
||||
@ -1315,21 +1305,21 @@ fn artifacts_to_update(
|
||||
let not_cmd_id = move |solid_id: &ArtifactId| *solid_id != id;
|
||||
|
||||
match response {
|
||||
OkModelingCmdResponse::BooleanIntersection(intersection) => intersection
|
||||
Some(OkModelingCmdResponse::BooleanIntersection(intersection)) => intersection
|
||||
.extra_solid_ids
|
||||
.iter()
|
||||
.copied()
|
||||
.map(ArtifactId::new)
|
||||
.filter(not_cmd_id)
|
||||
.for_each(|id| new_solid_ids.push(id)),
|
||||
OkModelingCmdResponse::BooleanSubtract(subtract) => subtract
|
||||
Some(OkModelingCmdResponse::BooleanSubtract(subtract)) => subtract
|
||||
.extra_solid_ids
|
||||
.iter()
|
||||
.copied()
|
||||
.map(ArtifactId::new)
|
||||
.filter(not_cmd_id)
|
||||
.for_each(|id| new_solid_ids.push(id)),
|
||||
OkModelingCmdResponse::BooleanUnion(union) => union
|
||||
Some(OkModelingCmdResponse::BooleanUnion(union)) => union
|
||||
.extra_solid_ids
|
||||
.iter()
|
||||
.copied()
|
||||
|
@ -20,7 +20,7 @@ use crate::{
|
||||
lazy_static::lazy_static! {
|
||||
/// A static mutable lock for updating the last successful execution state for the cache.
|
||||
static ref OLD_AST: Arc<RwLock<Option<GlobalState>>> = Default::default();
|
||||
// The last successful run's memory. Not cleared after an unssuccessful run.
|
||||
// The last successful run's memory. Not cleared after an unsuccessful run.
|
||||
static ref PREV_MEMORY: Arc<RwLock<Option<(Stack, ModuleInfoMap)>>> = Default::default();
|
||||
}
|
||||
|
||||
|
@ -17,11 +17,14 @@ use crate::{
|
||||
},
|
||||
fmt,
|
||||
modules::{ModuleId, ModulePath, ModuleRepr},
|
||||
parsing::ast::types::{
|
||||
Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryOperator,
|
||||
BinaryPart, BodyItem, Expr, IfExpression, ImportPath, ImportSelector, ItemVisibility, LiteralIdentifier,
|
||||
LiteralValue, MemberExpression, Name, Node, NodeRef, ObjectExpression, PipeExpression, Program, TagDeclarator,
|
||||
Type, UnaryExpression, UnaryOperator,
|
||||
parsing::{
|
||||
ast::types::{
|
||||
Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryOperator,
|
||||
BinaryPart, BodyItem, Expr, IfExpression, ImportPath, ImportSelector, ItemVisibility, LiteralIdentifier,
|
||||
LiteralValue, MemberExpression, Name, Node, NodeRef, ObjectExpression, PipeExpression, Program,
|
||||
TagDeclarator, Type, UnaryExpression, UnaryOperator,
|
||||
},
|
||||
token::NumericSuffix,
|
||||
},
|
||||
source_range::SourceRange,
|
||||
std::args::TyF64,
|
||||
@ -1666,12 +1669,18 @@ impl Property {
|
||||
LiteralIdentifier::Literal(literal) => {
|
||||
let value = literal.value.clone();
|
||||
match value {
|
||||
LiteralValue::Number { value, .. } => {
|
||||
n @ LiteralValue::Number { value, suffix } => {
|
||||
if !matches!(suffix, NumericSuffix::None | NumericSuffix::Count) {
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("{n} is not a valid index, indices must be non-dimensional numbers"),
|
||||
property_sr,
|
||||
)));
|
||||
}
|
||||
if let Some(x) = crate::try_f64_to_usize(value) {
|
||||
Ok(Property::UInt(x))
|
||||
} else {
|
||||
Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("{value} is not a valid index, indices must be whole numbers >= 0"),
|
||||
format!("{n} is not a valid index, indices must be whole numbers >= 0"),
|
||||
property_sr,
|
||||
)))
|
||||
}
|
||||
@ -1690,10 +1699,13 @@ fn jvalue_to_prop(value: &KclValue, property_sr: Vec<SourceRange>, name: &str) -
|
||||
let make_err =
|
||||
|message: String| Err::<Property, _>(KclError::new_semantic(KclErrorDetails::new(message, property_sr)));
|
||||
match value {
|
||||
KclValue::Number{value: num, .. } => {
|
||||
n @ KclValue::Number{value: num, ty, .. } => {
|
||||
if !matches!(ty, NumericType::Known(crate::exec::UnitType::Count) | NumericType::Default { .. } | NumericType::Any ) {
|
||||
return make_err(format!("arrays can only be indexed by non-dimensioned numbers, found {}", n.human_friendly_type()));
|
||||
}
|
||||
let num = *num;
|
||||
if num < 0.0 {
|
||||
return make_err(format!("'{num}' is negative, so you can't index an array with it"))
|
||||
return make_err(format!("'{num}' is negative, so you can't index an array with it"));
|
||||
}
|
||||
let nearest_int = crate::try_f64_to_usize(num);
|
||||
if let Some(nearest_int) = nearest_int {
|
||||
@ -2141,4 +2153,23 @@ c = ((PI * 2) / 3): number(deg)
|
||||
let result = parse_execute(ast).await.unwrap();
|
||||
assert_eq!(result.exec_state.errors().len(), 2);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn non_count_indexing() {
|
||||
let ast = r#"x = [0, 0]
|
||||
y = x[1mm]
|
||||
"#;
|
||||
parse_execute(ast).await.unwrap_err();
|
||||
|
||||
let ast = r#"x = [0, 0]
|
||||
y = 1deg
|
||||
z = x[y]
|
||||
"#;
|
||||
parse_execute(ast).await.unwrap_err();
|
||||
|
||||
let ast = r#"x = [0, 0]
|
||||
y = x[0mm + 1]
|
||||
"#;
|
||||
parse_execute(ast).await.unwrap_err();
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ pub use state::{ExecState, MetaSettings};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
engine::EngineManager,
|
||||
engine::{EngineManager, GridScaleBehavior},
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{
|
||||
cache::{CacheInformation, CacheResult},
|
||||
@ -295,6 +295,8 @@ pub struct ExecutorSettings {
|
||||
/// This is the path to the current file being executed.
|
||||
/// We use this for preventing cyclic imports.
|
||||
pub current_file: Option<TypedPath>,
|
||||
/// Whether or not to automatically scale the grid when user zooms.
|
||||
pub fixed_size_grid: bool,
|
||||
}
|
||||
|
||||
impl Default for ExecutorSettings {
|
||||
@ -306,33 +308,34 @@ impl Default for ExecutorSettings {
|
||||
replay: None,
|
||||
project_directory: None,
|
||||
current_file: None,
|
||||
fixed_size_grid: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::settings::types::Configuration> for ExecutorSettings {
|
||||
fn from(config: crate::settings::types::Configuration) -> Self {
|
||||
Self::from(config.settings)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::settings::types::Settings> for ExecutorSettings {
|
||||
fn from(settings: crate::settings::types::Settings) -> Self {
|
||||
Self {
|
||||
highlight_edges: config.settings.modeling.highlight_edges.into(),
|
||||
enable_ssao: config.settings.modeling.enable_ssao.into(),
|
||||
show_grid: config.settings.modeling.show_scale_grid,
|
||||
highlight_edges: settings.modeling.highlight_edges.into(),
|
||||
enable_ssao: settings.modeling.enable_ssao.into(),
|
||||
show_grid: settings.modeling.show_scale_grid,
|
||||
replay: None,
|
||||
project_directory: None,
|
||||
current_file: None,
|
||||
fixed_size_grid: settings.app.fixed_size_grid,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::settings::types::project::ProjectConfiguration> for ExecutorSettings {
|
||||
fn from(config: crate::settings::types::project::ProjectConfiguration) -> Self {
|
||||
Self {
|
||||
highlight_edges: config.settings.modeling.highlight_edges.into(),
|
||||
enable_ssao: config.settings.modeling.enable_ssao.into(),
|
||||
show_grid: Default::default(),
|
||||
replay: None,
|
||||
project_directory: None,
|
||||
current_file: None,
|
||||
}
|
||||
Self::from(config.settings.modeling)
|
||||
}
|
||||
}
|
||||
|
||||
@ -345,6 +348,7 @@ impl From<crate::settings::types::ModelingSettings> for ExecutorSettings {
|
||||
replay: None,
|
||||
project_directory: None,
|
||||
current_file: None,
|
||||
fixed_size_grid: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -358,6 +362,7 @@ impl From<crate::settings::types::project::ProjectModelingSettings> for Executor
|
||||
replay: None,
|
||||
project_directory: None,
|
||||
current_file: None,
|
||||
fixed_size_grid: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -497,6 +502,7 @@ impl ExecutorContext {
|
||||
replay: None,
|
||||
project_directory: None,
|
||||
current_file: None,
|
||||
fixed_size_grid: false,
|
||||
},
|
||||
None,
|
||||
engine_addr,
|
||||
@ -592,6 +598,18 @@ impl ExecutorContext {
|
||||
|
||||
pub async fn run_with_caching(&self, program: crate::Program) -> Result<ExecOutcome, KclErrorWithOutputs> {
|
||||
assert!(!self.is_mock());
|
||||
let grid_scale = if self.settings.fixed_size_grid {
|
||||
GridScaleBehavior::Fixed(
|
||||
program
|
||||
.meta_settings()
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|s| s.default_length_units)
|
||||
.map(kcmc::units::UnitLength::from),
|
||||
)
|
||||
} else {
|
||||
GridScaleBehavior::ScaleWithZoom
|
||||
};
|
||||
|
||||
let (program, exec_state, result) = match cache::read_old_ast().await {
|
||||
Some(mut cached_state) => {
|
||||
@ -618,6 +636,7 @@ impl ExecutorContext {
|
||||
&self.settings,
|
||||
Default::default(),
|
||||
&mut cached_state.main.exec_state.id_generator,
|
||||
grid_scale,
|
||||
)
|
||||
.await
|
||||
.is_err()
|
||||
@ -645,6 +664,7 @@ impl ExecutorContext {
|
||||
&self.settings,
|
||||
Default::default(),
|
||||
&mut cached_state.main.exec_state.id_generator,
|
||||
grid_scale,
|
||||
)
|
||||
.await
|
||||
.is_err()
|
||||
@ -689,6 +709,7 @@ impl ExecutorContext {
|
||||
&self.settings,
|
||||
Default::default(),
|
||||
&mut cached_state.main.exec_state.id_generator,
|
||||
grid_scale,
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
@ -1077,8 +1098,25 @@ impl ExecutorContext {
|
||||
let _stats = crate::log::LogPerfStats::new("Interpretation");
|
||||
|
||||
// Re-apply the settings, in case the cache was busted.
|
||||
let grid_scale = if self.settings.fixed_size_grid {
|
||||
GridScaleBehavior::Fixed(
|
||||
program
|
||||
.meta_settings()
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|s| s.default_length_units)
|
||||
.map(kcmc::units::UnitLength::from),
|
||||
)
|
||||
} else {
|
||||
GridScaleBehavior::ScaleWithZoom
|
||||
};
|
||||
self.engine
|
||||
.reapply_settings(&self.settings, Default::default(), exec_state.id_generator())
|
||||
.reapply_settings(
|
||||
&self.settings,
|
||||
Default::default(),
|
||||
exec_state.id_generator(),
|
||||
grid_scale,
|
||||
)
|
||||
.await
|
||||
.map_err(KclErrorWithOutputs::no_outputs)?;
|
||||
|
||||
@ -2286,6 +2324,25 @@ w = f() + f()
|
||||
ctx2.close().await;
|
||||
}
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn mock_has_stable_ids() {
|
||||
let ctx = ExecutorContext::new_mock(None).await;
|
||||
let code = "sk = startSketchOn(XY)
|
||||
|> startProfile(at = [0, 0])";
|
||||
let program = crate::Program::parse_no_errs(code).unwrap();
|
||||
let result = ctx.run_mock(program, false).await.unwrap();
|
||||
let ids = result.artifact_graph.iter().map(|(k, _)| *k).collect::<Vec<_>>();
|
||||
assert!(!ids.is_empty(), "IDs should not be empty");
|
||||
|
||||
let ctx2 = ExecutorContext::new_mock(None).await;
|
||||
let program2 = crate::Program::parse_no_errs(code).unwrap();
|
||||
let result = ctx2.run_mock(program2, false).await.unwrap();
|
||||
let ids2 = result.artifact_graph.iter().map(|(k, _)| *k).collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(ids, ids2, "Generated IDs should match");
|
||||
}
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn sim_sketch_mode_real_mock_real() {
|
||||
|
@ -177,7 +177,7 @@ impl ExecState {
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
operations: Default::default(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifact_graph: Default::default(),
|
||||
artifact_graph: self.global.artifacts.graph,
|
||||
errors: self.global.errors,
|
||||
filenames: Default::default(),
|
||||
default_planes: ctx.engine.get_default_planes().read().await.clone(),
|
||||
|
@ -188,6 +188,7 @@ impl<'de> serde::de::Deserialize<'de> for TypedPath {
|
||||
|
||||
impl ts_rs::TS for TypedPath {
|
||||
type WithoutGenerics = Self;
|
||||
type OptionInnerType = Self;
|
||||
|
||||
fn name() -> String {
|
||||
"string".to_string()
|
||||
@ -209,7 +210,7 @@ impl ts_rs::TS for TypedPath {
|
||||
std::path::PathBuf::inline_flattened()
|
||||
}
|
||||
|
||||
fn output_path() -> Option<&'static std::path::Path> {
|
||||
fn output_path() -> Option<std::path::PathBuf> {
|
||||
std::path::PathBuf::output_path()
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user