merge main
This commit is contained in:
@ -351,7 +351,7 @@ fn docs_for_type(ty: &str, kcl_std: &ModData) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn generate_const_from_kcl(cnst: &ConstData, file_name: String, example_name: String) -> Result<()> {
|
||||
fn generate_const_from_kcl(cnst: &ConstData, file_name: String, example_name: String, kcl_std: &ModData) -> Result<()> {
|
||||
if cnst.properties.doc_hidden {
|
||||
return Ok(());
|
||||
}
|
||||
@ -371,11 +371,13 @@ fn generate_const_from_kcl(cnst: &ConstData, file_name: String, example_name: St
|
||||
"description": cnst.description,
|
||||
"deprecated": cnst.properties.deprecated,
|
||||
"type_": cnst.ty,
|
||||
"type_desc": cnst.ty.as_ref().map(|t| docs_for_type(t, kcl_std).unwrap_or_default()),
|
||||
"examples": examples,
|
||||
"value": cnst.value.as_deref().unwrap_or(""),
|
||||
});
|
||||
|
||||
let output = hbs.render("const", &data)?;
|
||||
let output = cleanup_types(&output, kcl_std);
|
||||
expectorate::assert_contents(format!("../../docs/kcl-std/{}.md", file_name), &output);
|
||||
|
||||
Ok(())
|
||||
@ -529,7 +531,8 @@ fn cleanup_type_string(input: &str, fmt_for_text: bool, kcl_std: &ModData) -> St
|
||||
format!("[{prefix}{ty}{suffix}](/docs/kcl-std/types/std-types-number)")
|
||||
} else if fmt_for_text && ty.starts_with("fn") {
|
||||
format!("[{prefix}{ty}{suffix}](/docs/kcl-std/types/std-types-fn)")
|
||||
} else if fmt_for_text && matches!(kcl_std.find_by_name(ty), Some(DocData::Ty(_))) {
|
||||
// Special case for `tag` because it exists as a type but is deprecated and mostly used as an arg name
|
||||
} else if fmt_for_text && matches!(kcl_std.find_by_name(ty), Some(DocData::Ty(_))) && ty != "tag" {
|
||||
format!("[{prefix}{ty}{suffix}](/docs/kcl-std/types/std-types-{ty})")
|
||||
} else {
|
||||
format!("{prefix}{ty}{suffix}")
|
||||
@ -550,7 +553,7 @@ fn test_generate_stdlib_markdown_docs() {
|
||||
for d in kcl_std.all_docs() {
|
||||
match d {
|
||||
DocData::Fn(f) => generate_function_from_kcl(f, d.file_name(), d.example_name(), &kcl_std).unwrap(),
|
||||
DocData::Const(c) => generate_const_from_kcl(c, d.file_name(), d.example_name()).unwrap(),
|
||||
DocData::Const(c) => generate_const_from_kcl(c, d.file_name(), d.example_name(), &kcl_std).unwrap(),
|
||||
DocData::Ty(t) => generate_type_from_kcl(t, d.file_name(), d.example_name(), &kcl_std).unwrap(),
|
||||
DocData::Mod(m) => generate_mod_from_kcl(m, d.file_name()).unwrap(),
|
||||
}
|
||||
|
||||
@ -359,6 +359,7 @@ impl ConstData {
|
||||
crate::parsing::ast::types::LiteralValue::Bool { .. } => "boolean".to_owned(),
|
||||
}),
|
||||
),
|
||||
crate::parsing::ast::types::Expr::AscribedExpression(e) => (None, Some(e.ty.to_string())),
|
||||
_ => (None, None),
|
||||
};
|
||||
|
||||
@ -831,7 +832,7 @@ impl ArgData {
|
||||
Some("Edge") => Some((index, format!(r#"{label}${{{index}:tag_or_edge_fn}}"#))),
|
||||
Some("[Edge; 1+]") => Some((index, format!(r#"{label}[${{{index}:tag_or_edge_fn}}]"#))),
|
||||
Some("Plane") | Some("Solid | Plane") => Some((index, format!(r#"{label}${{{}:XY}}"#, index))),
|
||||
Some("[tag; 2]") => Some((
|
||||
Some("[TaggedFace; 2]") => Some((
|
||||
index + 1,
|
||||
format!(r#"{label}[${{{}:tag}}, ${{{}:tag}}]"#, index, index + 1),
|
||||
)),
|
||||
@ -1098,7 +1099,7 @@ trait ApplyMeta {
|
||||
self.impl_kind(annotations::Impl::from_str(s).unwrap());
|
||||
}
|
||||
}
|
||||
"deprecated" => {
|
||||
annotations::DEPRECATED => {
|
||||
if let Some(b) = p.value.literal_bool() {
|
||||
self.deprecated(b);
|
||||
}
|
||||
|
||||
@ -281,8 +281,11 @@ mod tests {
|
||||
length: number(Length),
|
||||
symmetric?: bool,
|
||||
bidirectionalLength?: number(Length),
|
||||
tagStart?: tag,
|
||||
tagEnd?: tag,
|
||||
tagStart?: TagDecl,
|
||||
tagEnd?: TagDecl,
|
||||
twistAngle?: number(Angle),
|
||||
twistAngleStep?: number(Angle),
|
||||
twistCenter?: Point2d,
|
||||
): [Solid; 1+]"#
|
||||
);
|
||||
}
|
||||
|
||||
6
rust/kcl-lib/src/docs/templates/const.hbs
vendored
6
rust/kcl-lib/src/docs/templates/const.hbs
vendored
@ -17,6 +17,12 @@ layout: manual
|
||||
|
||||
{{{description}}}
|
||||
|
||||
{{#if type_}}
|
||||
### Type
|
||||
|
||||
`{{type_}}`{{#if type_desc}} - {{{firstLine type_desc}}}{{/if}}
|
||||
|
||||
{{/if}}
|
||||
{{#if examples}}
|
||||
### Examples
|
||||
|
||||
|
||||
@ -18,8 +18,6 @@ use tokio::sync::{mpsc, oneshot, RwLock};
|
||||
use tokio_tungstenite::tungstenite::Message as WsMsg;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
use crate::execution::ArtifactCommand;
|
||||
use crate::{
|
||||
engine::{AsyncTasks, EngineManager, EngineStats},
|
||||
errors::{KclError, KclErrorDetails},
|
||||
@ -45,8 +43,6 @@ pub struct EngineConnection {
|
||||
socket_health: Arc<RwLock<SocketHealth>>,
|
||||
batch: Arc<RwLock<Vec<(WebSocketRequest, SourceRange)>>>,
|
||||
batch_end: Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>,
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifact_commands: Arc<RwLock<Vec<ArtifactCommand>>>,
|
||||
ids_of_async_commands: Arc<RwLock<IndexMap<Uuid, SourceRange>>>,
|
||||
|
||||
/// The default planes for the scene.
|
||||
@ -378,8 +374,6 @@ impl EngineConnection {
|
||||
socket_health,
|
||||
batch: Arc::new(RwLock::new(Vec::new())),
|
||||
batch_end: Arc::new(RwLock::new(IndexMap::new())),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifact_commands: Arc::new(RwLock::new(Vec::new())),
|
||||
ids_of_async_commands,
|
||||
default_planes: Default::default(),
|
||||
session_data,
|
||||
@ -404,11 +398,6 @@ impl EngineManager for EngineConnection {
|
||||
self.responses.responses.clone()
|
||||
}
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
fn artifact_commands(&self) -> Arc<RwLock<Vec<ArtifactCommand>>> {
|
||||
self.artifact_commands.clone()
|
||||
}
|
||||
|
||||
fn ids_of_async_commands(&self) -> Arc<RwLock<IndexMap<Uuid, SourceRange>>> {
|
||||
self.ids_of_async_commands.clone()
|
||||
}
|
||||
@ -508,8 +497,9 @@ impl EngineManager for EngineConnection {
|
||||
.await?;
|
||||
|
||||
// Wait for the response.
|
||||
let response_timeout = 300;
|
||||
let current_time = std::time::Instant::now();
|
||||
while current_time.elapsed().as_secs() < 60 {
|
||||
while current_time.elapsed().as_secs() < response_timeout {
|
||||
let guard = self.socket_health.read().await;
|
||||
if *guard == SocketHealth::Inactive {
|
||||
// Check if we have any pending errors.
|
||||
|
||||
@ -16,8 +16,6 @@ use kittycad_modeling_cmds::{self as kcmc, websocket::ModelingCmdReq, ImportFile
|
||||
use tokio::sync::RwLock;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
use crate::execution::ArtifactCommand;
|
||||
use crate::{
|
||||
engine::{AsyncTasks, EngineStats},
|
||||
errors::KclError,
|
||||
@ -30,8 +28,6 @@ use crate::{
|
||||
pub struct EngineConnection {
|
||||
batch: Arc<RwLock<Vec<(WebSocketRequest, SourceRange)>>>,
|
||||
batch_end: Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>,
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifact_commands: Arc<RwLock<Vec<ArtifactCommand>>>,
|
||||
ids_of_async_commands: Arc<RwLock<IndexMap<Uuid, SourceRange>>>,
|
||||
responses: Arc<RwLock<IndexMap<Uuid, WebSocketResponse>>>,
|
||||
/// The default planes for the scene.
|
||||
@ -45,8 +41,6 @@ impl EngineConnection {
|
||||
Ok(EngineConnection {
|
||||
batch: Arc::new(RwLock::new(Vec::new())),
|
||||
batch_end: Arc::new(RwLock::new(IndexMap::new())),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifact_commands: Arc::new(RwLock::new(Vec::new())),
|
||||
ids_of_async_commands: Arc::new(RwLock::new(IndexMap::new())),
|
||||
responses: Arc::new(RwLock::new(IndexMap::new())),
|
||||
default_planes: Default::default(),
|
||||
@ -74,11 +68,6 @@ impl crate::engine::EngineManager for EngineConnection {
|
||||
&self.stats
|
||||
}
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
fn artifact_commands(&self) -> Arc<RwLock<Vec<ArtifactCommand>>> {
|
||||
self.artifact_commands.clone()
|
||||
}
|
||||
|
||||
fn ids_of_async_commands(&self) -> Arc<RwLock<IndexMap<Uuid, SourceRange>>> {
|
||||
self.ids_of_async_commands.clone()
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ use wasm_bindgen::prelude::*;
|
||||
use crate::{
|
||||
engine::{AsyncTasks, EngineStats},
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{ArtifactCommand, DefaultPlanes, IdGenerator},
|
||||
execution::{DefaultPlanes, IdGenerator},
|
||||
SourceRange,
|
||||
};
|
||||
|
||||
@ -56,7 +56,6 @@ pub struct EngineConnection {
|
||||
response_context: Arc<ResponseContext>,
|
||||
batch: Arc<RwLock<Vec<(WebSocketRequest, SourceRange)>>>,
|
||||
batch_end: Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>,
|
||||
artifact_commands: Arc<RwLock<Vec<ArtifactCommand>>>,
|
||||
ids_of_async_commands: Arc<RwLock<IndexMap<Uuid, SourceRange>>>,
|
||||
/// The default planes for the scene.
|
||||
default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
|
||||
@ -129,7 +128,6 @@ impl EngineConnection {
|
||||
batch: Arc::new(RwLock::new(Vec::new())),
|
||||
batch_end: Arc::new(RwLock::new(IndexMap::new())),
|
||||
response_context,
|
||||
artifact_commands: Arc::new(RwLock::new(Vec::new())),
|
||||
ids_of_async_commands: Arc::new(RwLock::new(IndexMap::new())),
|
||||
default_planes: Default::default(),
|
||||
stats: Default::default(),
|
||||
@ -277,10 +275,6 @@ impl crate::engine::EngineManager for EngineConnection {
|
||||
&self.stats
|
||||
}
|
||||
|
||||
fn artifact_commands(&self) -> Arc<RwLock<Vec<ArtifactCommand>>> {
|
||||
self.artifact_commands.clone()
|
||||
}
|
||||
|
||||
fn ids_of_async_commands(&self) -> Arc<RwLock<IndexMap<Uuid, SourceRange>>> {
|
||||
self.ids_of_async_commands.clone()
|
||||
}
|
||||
|
||||
@ -19,8 +19,6 @@ use std::{
|
||||
|
||||
pub use async_tasks::AsyncTasks;
|
||||
use indexmap::IndexMap;
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
use kcmc::id::ModelingCmdId;
|
||||
use kcmc::{
|
||||
each_cmd as mcmd,
|
||||
length_unit::LengthUnit,
|
||||
@ -38,9 +36,8 @@ use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::RwLock;
|
||||
use uuid::Uuid;
|
||||
use web_time::Instant;
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
use crate::execution::ArtifactCommand;
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{types::UnitLen, DefaultPlanes, IdGenerator, PlaneInfo, Point3d},
|
||||
@ -113,10 +110,6 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
/// Get the command responses from the engine.
|
||||
fn responses(&self) -> Arc<RwLock<IndexMap<Uuid, WebSocketResponse>>>;
|
||||
|
||||
/// Get the artifact commands that have accumulated so far.
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
fn artifact_commands(&self) -> Arc<RwLock<Vec<ArtifactCommand>>>;
|
||||
|
||||
/// Get the ids of the async commands we are waiting for.
|
||||
fn ids_of_async_commands(&self) -> Arc<RwLock<IndexMap<Uuid, SourceRange>>>;
|
||||
|
||||
@ -133,18 +126,6 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
std::mem::take(&mut *self.batch_end().write().await)
|
||||
}
|
||||
|
||||
/// Clear all artifact commands that have accumulated so far.
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
async fn clear_artifact_commands(&self) {
|
||||
self.artifact_commands().write().await.clear();
|
||||
}
|
||||
|
||||
/// Take the artifact commands that have accumulated so far and clear them.
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
async fn take_artifact_commands(&self) -> Vec<ArtifactCommand> {
|
||||
std::mem::take(&mut *self.artifact_commands().write().await)
|
||||
}
|
||||
|
||||
/// Take the ids of async commands that have accumulated so far and clear them.
|
||||
async fn take_ids_of_async_commands(&self) -> IndexMap<Uuid, SourceRange> {
|
||||
std::mem::take(&mut *self.ids_of_async_commands().write().await)
|
||||
@ -237,11 +218,6 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
// Otherwise the hooks below won't work.
|
||||
self.flush_batch(false, source_range).await?;
|
||||
|
||||
// Ensure artifact commands are cleared so that we don't accumulate them
|
||||
// across runs.
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
self.clear_artifact_commands().await;
|
||||
|
||||
// Do the after clear scene hook.
|
||||
self.clear_scene_post_hook(id_generator, source_range).await?;
|
||||
|
||||
@ -266,7 +242,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
.unwrap_or_default()
|
||||
};
|
||||
|
||||
let current_time = instant::Instant::now();
|
||||
let current_time = Instant::now();
|
||||
while current_time.elapsed().as_secs() < 60 {
|
||||
let responses = self.responses().read().await.clone();
|
||||
let Some(resp) = responses.get(&id) else {
|
||||
@ -274,7 +250,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
// No seriously WE DO NOT WANT TO PAUSE THE WHOLE APP ON THE JS SIDE.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
let duration = instant::Duration::from_millis(1);
|
||||
let duration = web_time::Duration::from_millis(1);
|
||||
wasm_timer::Delay::new(duration).await.map_err(|err| {
|
||||
KclError::new_internal(KclErrorDetails::new(
|
||||
format!("Failed to sleep: {:?}", err),
|
||||
@ -341,28 +317,6 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
async fn handle_artifact_command(
|
||||
&self,
|
||||
cmd: &ModelingCmd,
|
||||
cmd_id: ModelingCmdId,
|
||||
id_to_source_range: &HashMap<Uuid, SourceRange>,
|
||||
) -> Result<(), KclError> {
|
||||
let cmd_id = *cmd_id.as_ref();
|
||||
let range = id_to_source_range
|
||||
.get(&cmd_id)
|
||||
.copied()
|
||||
.ok_or_else(|| KclError::internal(format!("Failed to get source range for command ID: {:?}", cmd_id)))?;
|
||||
|
||||
// Add artifact command.
|
||||
self.artifact_commands().write().await.push(ArtifactCommand {
|
||||
cmd_id,
|
||||
range,
|
||||
command: cmd.clone(),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Re-run the command to apply the settings.
|
||||
async fn reapply_settings(
|
||||
&self,
|
||||
@ -481,11 +435,6 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
// Add the command ID to the list of async commands.
|
||||
self.ids_of_async_commands().write().await.insert(id, source_range);
|
||||
|
||||
// Add to artifact commands.
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
self.handle_artifact_command(cmd, id.into(), &HashMap::from([(id, source_range)]))
|
||||
.await?;
|
||||
|
||||
// Fire off the command now, but don't wait for the response, we don't care about it.
|
||||
self.inner_fire_modeling_cmd(
|
||||
id,
|
||||
@ -555,24 +504,6 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
}
|
||||
}
|
||||
|
||||
// Do the artifact commands.
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
for (req, _) in orig_requests.iter() {
|
||||
match &req {
|
||||
WebSocketRequest::ModelingCmdBatchReq(ModelingBatch { requests, .. }) => {
|
||||
for request in requests {
|
||||
self.handle_artifact_command(&request.cmd, request.cmd_id, &id_to_source_range)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
WebSocketRequest::ModelingCmdReq(request) => {
|
||||
self.handle_artifact_command(&request.cmd, request.cmd_id, &id_to_source_range)
|
||||
.await?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
self.stats().batches_sent.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
// We pop off the responses to cleanup our mappings.
|
||||
|
||||
@ -135,8 +135,10 @@ pub struct KclErrorWithOutputs {
|
||||
pub non_fatal: Vec<CompilationError>,
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub operations: Vec<Operation>,
|
||||
// TODO: Remove this field. Doing so breaks the ts-rs output for some
|
||||
// reason.
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub artifact_commands: Vec<ArtifactCommand>,
|
||||
pub _artifact_commands: Vec<ArtifactCommand>,
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub artifact_graph: ArtifactGraph,
|
||||
pub filenames: IndexMap<ModuleId, ModulePath>,
|
||||
@ -162,7 +164,7 @@ impl KclErrorWithOutputs {
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
operations,
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifact_commands,
|
||||
_artifact_commands: artifact_commands,
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifact_graph,
|
||||
filenames,
|
||||
@ -177,7 +179,7 @@ impl KclErrorWithOutputs {
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
operations: Default::default(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifact_commands: Default::default(),
|
||||
_artifact_commands: Default::default(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifact_graph: Default::default(),
|
||||
filenames: Default::default(),
|
||||
@ -781,6 +783,7 @@ impl Severity {
|
||||
pub enum Tag {
|
||||
Deprecated,
|
||||
Unnecessary,
|
||||
UnknownNumericUnits,
|
||||
None,
|
||||
}
|
||||
|
||||
|
||||
@ -32,6 +32,8 @@ pub(crate) const IMPL_KCL: &str = "kcl";
|
||||
pub(crate) const IMPL_PRIMITIVE: &str = "primitive";
|
||||
pub(super) const IMPL_VALUES: [&str; 3] = [IMPL_RUST, IMPL_KCL, IMPL_PRIMITIVE];
|
||||
|
||||
pub(crate) const DEPRECATED: &str = "deprecated";
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug, Default)]
|
||||
pub enum Impl {
|
||||
#[default]
|
||||
|
||||
@ -45,26 +45,6 @@ pub struct ArtifactCommand {
|
||||
pub command: ModelingCmd,
|
||||
}
|
||||
|
||||
impl PartialOrd for ArtifactCommand {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
// Order by the source range.
|
||||
let range = self.range.cmp(&other.range);
|
||||
if range != std::cmp::Ordering::Equal {
|
||||
return Some(range);
|
||||
}
|
||||
#[cfg(test)]
|
||||
{
|
||||
// If the ranges are equal, order by the serde variant.
|
||||
Some(
|
||||
crate::variant_name::variant_name(&self.command)
|
||||
.cmp(&crate::variant_name::variant_name(&other.command)),
|
||||
)
|
||||
}
|
||||
#[cfg(not(test))]
|
||||
self.cmd_id.partial_cmp(&other.cmd_id)
|
||||
}
|
||||
}
|
||||
|
||||
pub type DummyPathToNode = Vec<()>;
|
||||
|
||||
fn serialize_dummy_path_to_node<S>(_path_to_node: &DummyPathToNode, serializer: S) -> Result<S::Ok, S::Error>
|
||||
@ -185,11 +165,12 @@ pub struct Sweep {
|
||||
pub code_ref: CodeRef,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, PartialOrd, Ord, ts_rs::TS)]
|
||||
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)]
|
||||
#[ts(export_to = "Artifact.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum SweepSubType {
|
||||
Extrusion,
|
||||
ExtrusionTwist,
|
||||
Revolve,
|
||||
RevolveAboutEdge,
|
||||
Loft,
|
||||
@ -258,7 +239,7 @@ pub struct Cap {
|
||||
pub cmd_id: uuid::Uuid,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Ord, PartialOrd, Eq, ts_rs::TS)]
|
||||
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)]
|
||||
#[ts(export_to = "Artifact.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum CapSubType {
|
||||
@ -282,7 +263,7 @@ pub struct SweepEdge {
|
||||
pub common_surface_ids: Vec<ArtifactId>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Ord, PartialOrd, Eq, ts_rs::TS)]
|
||||
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)]
|
||||
#[ts(export_to = "Artifact.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum SweepEdgeSubType {
|
||||
@ -304,7 +285,7 @@ pub struct EdgeCut {
|
||||
pub code_ref: CodeRef,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, PartialEq, PartialOrd, Ord, Eq, ts_rs::TS)]
|
||||
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)]
|
||||
#[ts(export_to = "Artifact.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum EdgeCutSubType {
|
||||
@ -361,135 +342,6 @@ pub enum Artifact {
|
||||
Helix(Helix),
|
||||
}
|
||||
|
||||
impl Artifact {
|
||||
pub(crate) fn rank(&self) -> u8 {
|
||||
match self {
|
||||
Artifact::Plane(_) => 0,
|
||||
Artifact::StartSketchOnPlane(_) => 1,
|
||||
Artifact::StartSketchOnFace(_) => 2,
|
||||
Artifact::Path(_) => 3,
|
||||
Artifact::Segment(_) => 4,
|
||||
Artifact::Solid2d(_) => 5,
|
||||
Artifact::Sweep(_) => 6,
|
||||
Artifact::CompositeSolid(_) => 7,
|
||||
Artifact::Wall(_) => 8,
|
||||
Artifact::Cap(Cap { sub_type, .. }) if *sub_type == CapSubType::Start => 9,
|
||||
Artifact::Cap(Cap { sub_type, .. }) if *sub_type == CapSubType::Start => 10,
|
||||
Artifact::Cap(_) => 11,
|
||||
Artifact::SweepEdge(SweepEdge { sub_type, .. }) if *sub_type == SweepEdgeSubType::Adjacent => 12,
|
||||
Artifact::SweepEdge(SweepEdge { sub_type, .. }) if *sub_type == SweepEdgeSubType::Opposite => 13,
|
||||
Artifact::SweepEdge(_) => 14,
|
||||
Artifact::EdgeCut(_) => 15,
|
||||
Artifact::EdgeCutEdge(_) => 16,
|
||||
Artifact::Helix(_) => 17,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Artifact {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
// The only thing we want to sort is if we have two sweep edges, we want
|
||||
// to sort them by the sub_type.
|
||||
match (self, other) {
|
||||
(Artifact::SweepEdge(a), Artifact::SweepEdge(b)) => {
|
||||
if a.sub_type != b.sub_type {
|
||||
return Some(a.sub_type.cmp(&b.sub_type));
|
||||
}
|
||||
if a.sweep_id != b.sweep_id {
|
||||
return Some(a.sweep_id.cmp(&b.sweep_id));
|
||||
}
|
||||
if a.cmd_id != b.cmd_id {
|
||||
return Some(a.cmd_id.cmp(&b.cmd_id));
|
||||
}
|
||||
if a.index != b.index {
|
||||
return Some(a.index.cmp(&b.index));
|
||||
}
|
||||
Some(a.id.cmp(&b.id))
|
||||
}
|
||||
(Artifact::EdgeCut(a), Artifact::EdgeCut(b)) => {
|
||||
if a.code_ref.range != b.code_ref.range {
|
||||
return Some(a.code_ref.range.cmp(&b.code_ref.range));
|
||||
}
|
||||
Some(a.id.cmp(&b.id))
|
||||
}
|
||||
(Artifact::EdgeCutEdge(a), Artifact::EdgeCutEdge(b)) => Some(a.edge_cut_id.cmp(&b.edge_cut_id)),
|
||||
(Artifact::Sweep(a), Artifact::Sweep(b)) => {
|
||||
if a.code_ref.range != b.code_ref.range {
|
||||
return Some(a.code_ref.range.cmp(&b.code_ref.range));
|
||||
}
|
||||
Some(a.id.cmp(&b.id))
|
||||
}
|
||||
// Sort the planes by their code_ref range.
|
||||
(Artifact::Plane(a), Artifact::Plane(b)) => {
|
||||
if a.code_ref.range != b.code_ref.range {
|
||||
return Some(a.code_ref.range.cmp(&b.code_ref.range));
|
||||
}
|
||||
Some(a.id.cmp(&b.id))
|
||||
}
|
||||
// Sort the paths by their code_ref range.
|
||||
(Artifact::Path(a), Artifact::Path(b)) => {
|
||||
if a.code_ref.range != b.code_ref.range {
|
||||
return Some(a.code_ref.range.cmp(&b.code_ref.range));
|
||||
}
|
||||
Some(a.id.cmp(&b.id))
|
||||
}
|
||||
// Sort the segments by their code_ref range.
|
||||
(Artifact::Segment(a), Artifact::Segment(b)) => {
|
||||
if a.code_ref.range != b.code_ref.range {
|
||||
return Some(a.code_ref.range.cmp(&b.code_ref.range));
|
||||
}
|
||||
Some(a.id.cmp(&b.id))
|
||||
}
|
||||
// Sort the solid2d by their id.
|
||||
(Artifact::Solid2d(a), Artifact::Solid2d(b)) => {
|
||||
if a.path_id != b.path_id {
|
||||
return Some(a.path_id.cmp(&b.path_id));
|
||||
}
|
||||
Some(a.id.cmp(&b.id))
|
||||
}
|
||||
// Sort the walls by their code_ref range.
|
||||
(Artifact::Wall(a), Artifact::Wall(b)) => {
|
||||
if a.sweep_id != b.sweep_id {
|
||||
return Some(a.sweep_id.cmp(&b.sweep_id));
|
||||
}
|
||||
if a.cmd_id != b.cmd_id {
|
||||
return Some(a.cmd_id.cmp(&b.cmd_id));
|
||||
}
|
||||
if a.face_code_ref.range != b.face_code_ref.range {
|
||||
return Some(a.face_code_ref.range.cmp(&b.face_code_ref.range));
|
||||
}
|
||||
if a.seg_id != b.seg_id {
|
||||
return Some(a.seg_id.cmp(&b.seg_id));
|
||||
}
|
||||
Some(a.id.cmp(&b.id))
|
||||
}
|
||||
// Sort the caps by their code_ref range.
|
||||
(Artifact::Cap(a), Artifact::Cap(b)) => {
|
||||
if a.sub_type != b.sub_type {
|
||||
return Some(a.sub_type.cmp(&b.sub_type));
|
||||
}
|
||||
if a.cmd_id != b.cmd_id {
|
||||
return Some(a.cmd_id.cmp(&b.cmd_id));
|
||||
}
|
||||
if a.sweep_id != b.sweep_id {
|
||||
return Some(a.sweep_id.cmp(&b.sweep_id));
|
||||
}
|
||||
if a.face_code_ref.range != b.face_code_ref.range {
|
||||
return Some(a.face_code_ref.range.cmp(&b.face_code_ref.range));
|
||||
}
|
||||
Some(a.id.cmp(&b.id))
|
||||
}
|
||||
(Artifact::CompositeSolid(a), Artifact::CompositeSolid(b)) => Some(a.id.cmp(&b.id)),
|
||||
(Artifact::StartSketchOnFace(a), Artifact::StartSketchOnFace(b)) => Some(a.id.cmp(&b.id)),
|
||||
(Artifact::StartSketchOnPlane(a), Artifact::StartSketchOnPlane(b)) => Some(a.id.cmp(&b.id)),
|
||||
// Planes are first, then paths, then segments, then solids2ds, then sweeps, then
|
||||
// walls, then caps, then sweep edges, then edge cuts, then edge cut edges, then
|
||||
// helixes.
|
||||
_ => Some(self.rank().cmp(&other.rank())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Artifact {
|
||||
pub(crate) fn id(&self) -> ArtifactId {
|
||||
match self {
|
||||
@ -692,17 +544,15 @@ impl ArtifactGraph {
|
||||
self.map.values()
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.map.clear();
|
||||
self.item_count = 0;
|
||||
}
|
||||
|
||||
/// Consume the artifact graph and return the map of artifacts.
|
||||
fn into_map(self) -> IndexMap<ArtifactId, Artifact> {
|
||||
self.map
|
||||
}
|
||||
|
||||
/// Used to make the mermaid tests deterministic.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn sort(&mut self) {
|
||||
self.map
|
||||
.sort_by(|_ak, av, _bk, bv| av.partial_cmp(bv).unwrap_or(std::cmp::Ordering::Equal));
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the artifact graph from the artifact commands and the responses. The
|
||||
@ -1102,11 +952,13 @@ fn artifacts_to_update(
|
||||
return Ok(return_arr);
|
||||
}
|
||||
ModelingCmd::Extrude(kcmc::Extrude { target, .. })
|
||||
| ModelingCmd::TwistExtrude(kcmc::TwistExtrude { target, .. })
|
||||
| ModelingCmd::Revolve(kcmc::Revolve { target, .. })
|
||||
| ModelingCmd::RevolveAboutEdge(kcmc::RevolveAboutEdge { target, .. })
|
||||
| ModelingCmd::Sweep(kcmc::Sweep { target, .. }) => {
|
||||
let sub_type = match cmd {
|
||||
ModelingCmd::Extrude(_) => SweepSubType::Extrusion,
|
||||
ModelingCmd::TwistExtrude(_) => SweepSubType::ExtrusionTwist,
|
||||
ModelingCmd::Revolve(_) => SweepSubType::Revolve,
|
||||
ModelingCmd::RevolveAboutEdge(_) => SweepSubType::RevolveAboutEdge,
|
||||
ModelingCmd::Sweep(_) => SweepSubType::Sweep,
|
||||
|
||||
@ -109,9 +109,7 @@ impl GlobalState {
|
||||
variables: self.main.exec_state.variables(self.main.result_env),
|
||||
filenames: self.exec_state.filenames(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
operations: self.exec_state.artifacts.operations,
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifact_commands: self.exec_state.artifacts.commands,
|
||||
operations: self.exec_state.root_module_artifacts.operations,
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifact_graph: self.exec_state.artifacts.graph,
|
||||
errors: self.exec_state.errors,
|
||||
|
||||
@ -2,7 +2,9 @@ use indexmap::IndexMap;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::{types::NumericType, ArtifactId, KclValue};
|
||||
use crate::{ModuleId, NodePath, SourceRange};
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
use crate::parsing::ast::types::{Node, Program};
|
||||
use crate::{parsing::ast::types::ItemVisibility, ModuleId, NodePath, SourceRange};
|
||||
|
||||
/// A CAD modeling operation for display in the feature tree, AKA operations
|
||||
/// timeline.
|
||||
@ -26,6 +28,20 @@ pub enum Operation {
|
||||
is_error: bool,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
VariableDeclaration {
|
||||
/// The variable name.
|
||||
name: String,
|
||||
/// The value of the variable.
|
||||
value: OpKclValue,
|
||||
/// The visibility modifier of the variable, e.g. `export`. `Default`
|
||||
/// means there is no visibility modifier.
|
||||
visibility: ItemVisibility,
|
||||
/// The node path of the operation in the source code.
|
||||
node_path: NodePath,
|
||||
/// The source range of the operation in the source code.
|
||||
source_range: SourceRange,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
GroupBegin {
|
||||
/// The details of the group.
|
||||
group: Group,
|
||||
@ -37,32 +53,36 @@ pub enum Operation {
|
||||
GroupEnd,
|
||||
}
|
||||
|
||||
/// A way for sorting the operations in the timeline. This is used to sort
|
||||
/// operations in the timeline and to determine the order of operations.
|
||||
/// We use this for the multi-threaded snapshotting, so that we can have deterministic
|
||||
/// output.
|
||||
impl PartialOrd for Operation {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(match (self, other) {
|
||||
(Self::StdLibCall { source_range: a, .. }, Self::StdLibCall { source_range: b, .. }) => a.cmp(b),
|
||||
(Self::StdLibCall { source_range: a, .. }, Self::GroupBegin { source_range: b, .. }) => a.cmp(b),
|
||||
(Self::StdLibCall { .. }, Self::GroupEnd) => std::cmp::Ordering::Less,
|
||||
(Self::GroupBegin { source_range: a, .. }, Self::GroupBegin { source_range: b, .. }) => a.cmp(b),
|
||||
(Self::GroupBegin { source_range: a, .. }, Self::StdLibCall { source_range: b, .. }) => a.cmp(b),
|
||||
(Self::GroupBegin { .. }, Self::GroupEnd) => std::cmp::Ordering::Less,
|
||||
(Self::GroupEnd, Self::StdLibCall { .. }) => std::cmp::Ordering::Greater,
|
||||
(Self::GroupEnd, Self::GroupBegin { .. }) => std::cmp::Ordering::Greater,
|
||||
(Self::GroupEnd, Self::GroupEnd) => std::cmp::Ordering::Equal,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Operation {
|
||||
/// If the variant is `StdLibCall`, set the `is_error` field.
|
||||
pub(crate) fn set_std_lib_call_is_error(&mut self, is_err: bool) {
|
||||
match self {
|
||||
Self::StdLibCall { ref mut is_error, .. } => *is_error = is_err,
|
||||
Self::GroupBegin { .. } | Self::GroupEnd => {}
|
||||
Self::VariableDeclaration { .. } | Self::GroupBegin { .. } | Self::GroupEnd => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub(crate) fn fill_node_paths(&mut self, program: &Node<Program>, cached_body_items: usize) {
|
||||
match self {
|
||||
Operation::StdLibCall {
|
||||
node_path,
|
||||
source_range,
|
||||
..
|
||||
}
|
||||
| Operation::VariableDeclaration {
|
||||
node_path,
|
||||
source_range,
|
||||
..
|
||||
}
|
||||
| Operation::GroupBegin {
|
||||
node_path,
|
||||
source_range,
|
||||
..
|
||||
} => {
|
||||
node_path.fill_placeholder(program, cached_body_items, *source_range);
|
||||
}
|
||||
Operation::GroupEnd => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,13 +6,14 @@ use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{
|
||||
annotations,
|
||||
cad_op::OpKclValue,
|
||||
fn_call::Args,
|
||||
kcl_value::{FunctionSource, TypeDef},
|
||||
memory,
|
||||
state::ModuleState,
|
||||
types::{NumericType, PrimitiveType, RuntimeType},
|
||||
BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, PlaneType, StatementKind,
|
||||
TagIdentifier,
|
||||
BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, ModelingCmdMeta, ModuleArtifactState,
|
||||
Operation, PlaneType, StatementKind, TagIdentifier,
|
||||
},
|
||||
fmt,
|
||||
modules::{ModuleId, ModulePath, ModuleRepr},
|
||||
@ -24,7 +25,7 @@ use crate::{
|
||||
},
|
||||
source_range::SourceRange,
|
||||
std::args::TyF64,
|
||||
CompilationError,
|
||||
CompilationError, NodePath,
|
||||
};
|
||||
|
||||
impl<'a> StatementKind<'a> {
|
||||
@ -83,7 +84,10 @@ impl ExecutorContext {
|
||||
preserve_mem: bool,
|
||||
module_id: ModuleId,
|
||||
path: &ModulePath,
|
||||
) -> Result<(Option<KclValue>, EnvironmentRef, Vec<String>), KclError> {
|
||||
) -> Result<
|
||||
(Option<KclValue>, EnvironmentRef, Vec<String>, ModuleArtifactState),
|
||||
(KclError, Option<ModuleArtifactState>),
|
||||
> {
|
||||
crate::log::log(format!("enter module {path} {}", exec_state.stack()));
|
||||
|
||||
let mut local_state = ModuleState::new(path.clone(), exec_state.stack().memory.clone(), Some(module_id));
|
||||
@ -93,7 +97,8 @@ impl ExecutorContext {
|
||||
|
||||
let no_prelude = self
|
||||
.handle_annotations(program.inner_attrs.iter(), crate::execution::BodyType::Root, exec_state)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(|err| (err, None))?;
|
||||
|
||||
if !preserve_mem {
|
||||
exec_state.mut_stack().push_new_root_env(!no_prelude);
|
||||
@ -108,13 +113,18 @@ impl ExecutorContext {
|
||||
} else {
|
||||
exec_state.mut_stack().pop_env()
|
||||
};
|
||||
if !preserve_mem {
|
||||
let module_artifacts = if !preserve_mem {
|
||||
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
|
||||
}
|
||||
local_state.artifacts
|
||||
} else {
|
||||
std::mem::take(&mut exec_state.mod_local.artifacts)
|
||||
};
|
||||
|
||||
crate::log::log(format!("leave {path}"));
|
||||
|
||||
result.map(|result| (result, env_ref, local_state.module_exports))
|
||||
result
|
||||
.map_err(|err| (err, Some(module_artifacts.clone())))
|
||||
.map(|result| (result, env_ref, local_state.module_exports, module_artifacts))
|
||||
}
|
||||
|
||||
/// Execute an AST's program.
|
||||
@ -326,6 +336,16 @@ impl ExecutorContext {
|
||||
.mut_stack()
|
||||
.add(var_name.clone(), rhs.clone(), source_range)?;
|
||||
|
||||
if rhs.show_variable_in_feature_tree() {
|
||||
exec_state.push_op(Operation::VariableDeclaration {
|
||||
name: var_name.clone(),
|
||||
value: OpKclValue::from(&rhs),
|
||||
visibility: variable_declaration.visibility,
|
||||
node_path: NodePath::placeholder(),
|
||||
source_range,
|
||||
});
|
||||
}
|
||||
|
||||
// Track exports.
|
||||
if let ItemVisibility::Export = variable_declaration.visibility {
|
||||
if matches!(body_type, BodyType::Root) {
|
||||
@ -450,12 +470,12 @@ impl ExecutorContext {
|
||||
|
||||
if matches!(body_type, BodyType::Root) {
|
||||
// Flush the batch queue.
|
||||
self.engine
|
||||
exec_state
|
||||
.flush_batch(
|
||||
ModelingCmdMeta::new(self, SourceRange::new(program.end, program.end, program.module_id)),
|
||||
// True here tells the engine to flush all the end commands as well like fillets
|
||||
// and chamfers where the engine would otherwise eat the ID of the segments.
|
||||
true,
|
||||
SourceRange::new(program.end, program.end, program.module_id),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
@ -535,12 +555,12 @@ impl ExecutorContext {
|
||||
|
||||
let result = match &mut repr {
|
||||
ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
|
||||
ModuleRepr::Kcl(_, Some((_, env_ref, items))) => Ok((*env_ref, items.clone())),
|
||||
ModuleRepr::Kcl(_, Some((_, env_ref, items, _))) => Ok((*env_ref, items.clone())),
|
||||
ModuleRepr::Kcl(program, cache) => self
|
||||
.exec_module_from_ast(program, module_id, &path, exec_state, source_range, false)
|
||||
.await
|
||||
.map(|(val, er, items)| {
|
||||
*cache = Some((val, er, items.clone()));
|
||||
.map(|(val, er, items, module_artifacts)| {
|
||||
*cache = Some((val, er, items.clone(), module_artifacts.clone()));
|
||||
(er, items)
|
||||
}),
|
||||
ModuleRepr::Foreign(geom, _) => Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
@ -566,28 +586,28 @@ impl ExecutorContext {
|
||||
|
||||
let result = match &mut repr {
|
||||
ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
|
||||
ModuleRepr::Kcl(_, Some((val, _, _))) => Ok(val.clone()),
|
||||
ModuleRepr::Kcl(_, Some((val, _, _, _))) => Ok(val.clone()),
|
||||
ModuleRepr::Kcl(program, cached_items) => {
|
||||
let result = self
|
||||
.exec_module_from_ast(program, module_id, &path, exec_state, source_range, false)
|
||||
.await;
|
||||
match result {
|
||||
Ok((val, env, items)) => {
|
||||
*cached_items = Some((val.clone(), env, items));
|
||||
Ok((val, env, items, module_artifacts)) => {
|
||||
*cached_items = Some((val.clone(), env, items, module_artifacts));
|
||||
Ok(val)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
ModuleRepr::Foreign(_, Some(imported)) => Ok(Some(imported.clone())),
|
||||
ModuleRepr::Foreign(_, Some((imported, _))) => Ok(imported.clone()),
|
||||
ModuleRepr::Foreign(geom, cached) => {
|
||||
let result = super::import::send_to_engine(geom.clone(), self)
|
||||
let result = super::import::send_to_engine(geom.clone(), exec_state, self)
|
||||
.await
|
||||
.map(|geom| Some(KclValue::ImportedGeometry(geom)));
|
||||
|
||||
match result {
|
||||
Ok(val) => {
|
||||
*cached = val.clone();
|
||||
*cached = Some((val.clone(), exec_state.mod_local.artifacts.clone()));
|
||||
Ok(val)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
@ -609,14 +629,16 @@ impl ExecutorContext {
|
||||
exec_state: &mut ExecState,
|
||||
source_range: SourceRange,
|
||||
preserve_mem: bool,
|
||||
) -> Result<(Option<KclValue>, EnvironmentRef, Vec<String>), KclError> {
|
||||
) -> Result<(Option<KclValue>, EnvironmentRef, Vec<String>, ModuleArtifactState), KclError> {
|
||||
exec_state.global.mod_loader.enter_module(path);
|
||||
let result = self
|
||||
.exec_module_body(program, exec_state, preserve_mem, module_id, path)
|
||||
.await;
|
||||
exec_state.global.mod_loader.leave_module(path);
|
||||
|
||||
result.map_err(|err| {
|
||||
// TODO: ModuleArtifactState is getting dropped here when there's an
|
||||
// error. Should we propagate it for non-root modules?
|
||||
result.map_err(|(err, _)| {
|
||||
if let KclError::ImportCycle { .. } = err {
|
||||
// It was an import cycle. Keep the original message.
|
||||
err.override_source_ranges(vec![source_range])
|
||||
@ -798,6 +820,10 @@ fn apply_ascription(
|
||||
let ty = RuntimeType::from_parsed(ty.inner.clone(), exec_state, value.into())
|
||||
.map_err(|e| KclError::new_semantic(e.into()))?;
|
||||
|
||||
if matches!(&ty, &RuntimeType::Primitive(PrimitiveType::Number(..))) {
|
||||
exec_state.clear_units_warnings(&source_range);
|
||||
}
|
||||
|
||||
value.coerce(&ty, false, exec_state).map_err(|_| {
|
||||
let suggestion = if ty == RuntimeType::length() {
|
||||
", you might try coercing to a fully specified numeric type such as `number(mm)`"
|
||||
@ -806,9 +832,14 @@ fn apply_ascription(
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let ty_str = if let Some(ty) = value.principal_type() {
|
||||
format!("(with type `{ty}`) ")
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"could not coerce value of type {} to type {ty}{suggestion}",
|
||||
"could not coerce {} {ty_str}to type `{ty}`{suggestion}",
|
||||
value.human_friendly_type()
|
||||
),
|
||||
vec![source_range],
|
||||
@ -1018,14 +1049,13 @@ impl Node<MemberExpression> {
|
||||
.map(|(k, tag)| (k.to_owned(), KclValue::TagIdentifier(Box::new(tag.to_owned()))))
|
||||
.collect(),
|
||||
}),
|
||||
(being_indexed, _, _) => {
|
||||
let t = being_indexed.human_friendly_type();
|
||||
let article = article_for(&t);
|
||||
Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Only arrays can be indexed, but you're trying to index {article} {t}"),
|
||||
vec![self.clone().into()],
|
||||
)))
|
||||
}
|
||||
(being_indexed, _, _) => Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"Only arrays can be indexed, but you're trying to index {}",
|
||||
being_indexed.human_friendly_type()
|
||||
),
|
||||
vec![self.clone().into()],
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1153,7 +1183,7 @@ impl Node<BinaryExpression> {
|
||||
KclValue::Number { value: l / r, meta, ty }
|
||||
}
|
||||
BinaryOperator::Mod => {
|
||||
let (l, r, ty) = NumericType::combine_div(left, right);
|
||||
let (l, r, ty) = NumericType::combine_mod(left, right);
|
||||
self.warn_on_unknown(&ty, "Modulo of", exec_state);
|
||||
KclValue::Number { value: l % r, meta, ty }
|
||||
}
|
||||
@ -1200,11 +1230,14 @@ impl Node<BinaryExpression> {
|
||||
|
||||
fn warn_on_unknown(&self, ty: &NumericType, verb: &str, exec_state: &mut ExecState) {
|
||||
if ty == &NumericType::Unknown {
|
||||
// TODO suggest how to fix this
|
||||
exec_state.warn(CompilationError::err(
|
||||
self.as_source_range(),
|
||||
format!("{} numbers which have unknown or incompatible units.", verb),
|
||||
));
|
||||
let sr = self.as_source_range();
|
||||
exec_state.clear_units_warnings(&sr);
|
||||
let mut err = CompilationError::err(
|
||||
sr,
|
||||
format!("{} numbers which have unknown or incompatible units.\nYou can probably fix this error by specifying the units using type ascription, e.g., `len: number(mm)` or `(a * b): number(deg)`.", verb),
|
||||
);
|
||||
err.tag = crate::errors::Tag::UnknownNumericUnits;
|
||||
exec_state.warn(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1753,7 +1786,7 @@ a = 42: string
|
||||
let err = result.unwrap_err();
|
||||
assert!(
|
||||
err.to_string()
|
||||
.contains("could not coerce value of type number(default units) to type string"),
|
||||
.contains("could not coerce a number (with type `number`) to type `string`"),
|
||||
"Expected error but found {err:?}"
|
||||
);
|
||||
|
||||
@ -1764,7 +1797,7 @@ a = 42: Plane
|
||||
let err = result.unwrap_err();
|
||||
assert!(
|
||||
err.to_string()
|
||||
.contains("could not coerce value of type number(default units) to type Plane"),
|
||||
.contains("could not coerce a number (with type `number`) to type `Plane`"),
|
||||
"Expected error but found {err:?}"
|
||||
);
|
||||
|
||||
@ -1775,7 +1808,7 @@ arr = [0]: [string]
|
||||
let err = result.unwrap_err();
|
||||
assert!(
|
||||
err.to_string().contains(
|
||||
"could not coerce value of type array of number(default units) with 1 value to type [string]"
|
||||
"could not coerce an array of `number` with 1 value (with type `[any; 1]`) to type `[string]`"
|
||||
),
|
||||
"Expected error but found {err:?}"
|
||||
);
|
||||
@ -1786,8 +1819,9 @@ mixedArr = [0, "a"]: [number(mm)]
|
||||
let result = parse_execute(program).await;
|
||||
let err = result.unwrap_err();
|
||||
assert!(
|
||||
err.to_string()
|
||||
.contains("could not coerce value of type array of number(default units), string with 2 values to type [number(mm)]"),
|
||||
err.to_string().contains(
|
||||
"could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
|
||||
),
|
||||
"Expected error but found {err:?}"
|
||||
);
|
||||
}
|
||||
@ -2092,4 +2126,19 @@ y = x: number(Length)"#;
|
||||
assert_eq!(num.n, 2.0);
|
||||
assert_eq!(num.ty, NumericType::mm());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn one_warning_unknown() {
|
||||
let ast = r#"
|
||||
// Should warn once
|
||||
a = PI * 2
|
||||
// Should warn once
|
||||
b = (PI * 2) / 3
|
||||
// Should not warn
|
||||
c = ((PI * 2) / 3): number(deg)
|
||||
"#;
|
||||
|
||||
let result = parse_execute(ast).await.unwrap();
|
||||
assert_eq!(result.exec_state.errors().len(), 2);
|
||||
}
|
||||
}
|
||||
|
||||
@ -532,6 +532,44 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn type_err_str(expected: &Type, found: &KclValue, source_range: &SourceRange, exec_state: &mut ExecState) -> String {
|
||||
fn strip_backticks(s: &str) -> &str {
|
||||
let mut result = s;
|
||||
if s.starts_with('`') {
|
||||
result = &result[1..]
|
||||
}
|
||||
if s.ends_with('`') {
|
||||
result = &result[..result.len() - 1]
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
let expected_human = expected.human_friendly_type();
|
||||
let expected_ty = expected.to_string();
|
||||
let expected_str =
|
||||
if expected_human == expected_ty || expected_human == format!("a value with type `{expected_ty}`") {
|
||||
format!("a value with type `{expected_ty}`")
|
||||
} else {
|
||||
format!("{expected_human} (`{expected_ty}`)")
|
||||
};
|
||||
let found_human = found.human_friendly_type();
|
||||
let found_ty = found.principal_type_string();
|
||||
let found_str = if found_human == found_ty || found_human == format!("a {}", strip_backticks(&found_ty)) {
|
||||
format!("a value with type {}", found_ty)
|
||||
} else {
|
||||
format!("{found_human} (with type {})", found_ty)
|
||||
};
|
||||
|
||||
let mut result = format!("{expected_str}, but found {found_str}.");
|
||||
|
||||
if found.is_unknown_number() {
|
||||
exec_state.clear_units_warnings(source_range);
|
||||
result.push_str("\nThe found value is a number but has incomplete units information. You can probably fix this error by specifying the units using type ascription, e.g., `len: number(mm)` or `(a * b): number(deg)`.");
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn type_check_params_kw(
|
||||
fn_name: Option<&str>,
|
||||
fn_def: &FunctionDefinition<'_>,
|
||||
@ -556,18 +594,19 @@ fn type_check_params_kw(
|
||||
// For optional args, passing None should be the same as not passing an arg.
|
||||
if !(def.is_some() && matches!(arg.value, KclValue::KclNone { .. })) {
|
||||
if let Some(ty) = ty {
|
||||
let rty = RuntimeType::from_parsed(ty.clone(), exec_state, arg.source_range)
|
||||
.map_err(|e| KclError::new_semantic(e.into()))?;
|
||||
arg.value = arg
|
||||
.value
|
||||
.coerce(
|
||||
&RuntimeType::from_parsed(ty.clone(), exec_state, arg.source_range).map_err(|e| KclError::new_semantic(e.into()))?,
|
||||
&rty,
|
||||
true,
|
||||
exec_state,
|
||||
)
|
||||
.map_err(|e| {
|
||||
let mut message = format!(
|
||||
"{label} requires a value with type `{}`, but found {}",
|
||||
ty,
|
||||
arg.value.human_friendly_type(),
|
||||
"{label} requires {}",
|
||||
type_err_str(ty, &arg.value, &arg.source_range, exec_state),
|
||||
);
|
||||
if let Some(ty) = e.explicit_coercion {
|
||||
// TODO if we have access to the AST for the argument we could choose which example to suggest.
|
||||
@ -630,28 +669,20 @@ fn type_check_params_kw(
|
||||
|
||||
if let Some(arg) = &mut args.unlabeled {
|
||||
if let Some((_, Some(ty))) = &fn_def.input_arg {
|
||||
arg.1.value = arg
|
||||
.1
|
||||
.value
|
||||
.coerce(
|
||||
&RuntimeType::from_parsed(ty.clone(), exec_state, arg.1.source_range)
|
||||
.map_err(|e| KclError::new_semantic(e.into()))?,
|
||||
true,
|
||||
exec_state,
|
||||
)
|
||||
.map_err(|_| {
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"The input argument of {} requires a value with type `{}`, but found {}",
|
||||
fn_name
|
||||
.map(|n| format!("`{}`", n))
|
||||
.unwrap_or_else(|| "this function".to_owned()),
|
||||
ty,
|
||||
arg.1.value.human_friendly_type()
|
||||
),
|
||||
vec![arg.1.source_range],
|
||||
))
|
||||
})?;
|
||||
let rty = RuntimeType::from_parsed(ty.clone(), exec_state, arg.1.source_range)
|
||||
.map_err(|e| KclError::new_semantic(e.into()))?;
|
||||
arg.1.value = arg.1.value.coerce(&rty, true, exec_state).map_err(|_| {
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"The input argument of {} requires {}",
|
||||
fn_name
|
||||
.map(|n| format!("`{}`", n))
|
||||
.unwrap_or_else(|| "this function".to_owned()),
|
||||
type_err_str(ty, &arg.1.value, &arg.1.source_range, exec_state),
|
||||
),
|
||||
vec![arg.1.source_range],
|
||||
))
|
||||
})?;
|
||||
}
|
||||
} else if let Some((name, _)) = &fn_def.input_arg {
|
||||
if let Some(arg) = args.labeled.get(name) {
|
||||
@ -747,9 +778,8 @@ fn coerce_result_type(
|
||||
let val = val.coerce(&ty, true, exec_state).map_err(|_| {
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"This function requires its result to be of type `{}`, but found {}",
|
||||
ty.human_friendly_type(),
|
||||
val.human_friendly_type(),
|
||||
"This function requires its result to be {}",
|
||||
type_err_str(ret_ty, &val, &(&val).into(), exec_state)
|
||||
),
|
||||
ret_ty.as_source_ranges(),
|
||||
))
|
||||
@ -928,7 +958,7 @@ msg2 = makeMessage(prefix = 1, suffix = 3)"#;
|
||||
let err = parse_execute(program).await.unwrap_err();
|
||||
assert_eq!(
|
||||
err.message(),
|
||||
"prefix requires a value with type `string`, but found number(default units)"
|
||||
"prefix requires a value with type `string`, but found a value with type `number`.\nThe found value is a number but has incomplete units information. You can probably fix this error by specifying the units using type ascription, e.g., `len: number(mm)` or `(a * b): number(deg)`."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,7 +15,10 @@ use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{annotations, typed_path::TypedPath, types::UnitLen, ExecState, ExecutorContext, ImportedGeometry},
|
||||
execution::{
|
||||
annotations, typed_path::TypedPath, types::UnitLen, ExecState, ExecutorContext, ImportedGeometry,
|
||||
ModelingCmdMeta,
|
||||
},
|
||||
fs::FileSystem,
|
||||
parsing::ast::types::{Annotation, Node},
|
||||
source_range::SourceRange,
|
||||
@ -257,15 +260,22 @@ pub struct PreImportedGeometry {
|
||||
pub source_range: SourceRange,
|
||||
}
|
||||
|
||||
pub async fn send_to_engine(pre: PreImportedGeometry, ctxt: &ExecutorContext) -> Result<ImportedGeometry, KclError> {
|
||||
pub async fn send_to_engine(
|
||||
pre: PreImportedGeometry,
|
||||
exec_state: &mut ExecState,
|
||||
ctxt: &ExecutorContext,
|
||||
) -> Result<ImportedGeometry, KclError> {
|
||||
let imported_geometry = ImportedGeometry::new(
|
||||
pre.id,
|
||||
pre.command.files.iter().map(|f| f.path.to_string()).collect(),
|
||||
vec![pre.source_range.into()],
|
||||
);
|
||||
|
||||
ctxt.engine
|
||||
.async_modeling_cmd(pre.id, pre.source_range, &ModelingCmd::from(pre.command.clone()))
|
||||
exec_state
|
||||
.async_modeling_cmd(
|
||||
ModelingCmdMeta::with_id(ctxt, pre.source_range, pre.id),
|
||||
&ModelingCmd::from(pre.command.clone()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(imported_geometry)
|
||||
|
||||
@ -4,7 +4,6 @@ use anyhow::Result;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::types::UnitType;
|
||||
use crate::{
|
||||
errors::KclErrorDetails,
|
||||
execution::{
|
||||
@ -278,72 +277,85 @@ impl KclValue {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if we should generate an [`crate::execution::Operation`] to
|
||||
/// display in the Feature Tree for variable declarations initialized with
|
||||
/// this value.
|
||||
pub(crate) fn show_variable_in_feature_tree(&self) -> bool {
|
||||
match self {
|
||||
KclValue::Uuid { .. } => false,
|
||||
KclValue::Bool { .. } | KclValue::Number { .. } | KclValue::String { .. } => true,
|
||||
KclValue::Tuple { .. }
|
||||
| KclValue::HomArray { .. }
|
||||
| KclValue::Object { .. }
|
||||
| KclValue::TagIdentifier(_)
|
||||
| KclValue::TagDeclarator(_)
|
||||
| KclValue::Plane { .. }
|
||||
| KclValue::Face { .. }
|
||||
| KclValue::Sketch { .. }
|
||||
| KclValue::Solid { .. }
|
||||
| KclValue::Helix { .. }
|
||||
| KclValue::ImportedGeometry(_)
|
||||
| KclValue::Function { .. }
|
||||
| KclValue::Module { .. }
|
||||
| KclValue::Type { .. }
|
||||
| KclValue::KclNone { .. } => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Human readable type name used in error messages. Should not be relied
|
||||
/// on for program logic.
|
||||
pub(crate) fn human_friendly_type(&self) -> String {
|
||||
self.inner_human_friendly_type(1)
|
||||
}
|
||||
|
||||
fn inner_human_friendly_type(&self, max_depth: usize) -> String {
|
||||
if let Some(pt) = self.principal_type() {
|
||||
if max_depth > 0 {
|
||||
// The principal type of an array uses the array's element type,
|
||||
// which is oftentimes `any`, and that's not a helpful message. So
|
||||
// we show the actual elements.
|
||||
if let KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } = self {
|
||||
// If it's empty, we want to show the type of the array.
|
||||
if !value.is_empty() {
|
||||
// A max of 3 is good because it's common to use 3D points.
|
||||
let max = 3;
|
||||
let len = value.len();
|
||||
let ellipsis = if len > max { ", ..." } else { "" };
|
||||
let element_label = if len == 1 { "value" } else { "values" };
|
||||
let element_tys = value
|
||||
.iter()
|
||||
.take(max)
|
||||
.map(|elem| elem.inner_human_friendly_type(max_depth - 1))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
return format!("array of {element_tys}{ellipsis} with {len} {element_label}");
|
||||
}
|
||||
}
|
||||
}
|
||||
return pt.to_string();
|
||||
}
|
||||
match self {
|
||||
KclValue::Uuid { .. } => "Unique ID (uuid)",
|
||||
KclValue::TagDeclarator(_) => "TagDeclarator",
|
||||
KclValue::TagIdentifier(_) => "TagIdentifier",
|
||||
KclValue::Solid { .. } => "Solid",
|
||||
KclValue::Sketch { .. } => "Sketch",
|
||||
KclValue::Helix { .. } => "Helix",
|
||||
KclValue::ImportedGeometry(_) => "ImportedGeometry",
|
||||
KclValue::Function { .. } => "Function",
|
||||
KclValue::Plane { .. } => "Plane",
|
||||
KclValue::Face { .. } => "Face",
|
||||
KclValue::Bool { .. } => "boolean (true/false value)",
|
||||
KclValue::Uuid { .. } => "a unique ID (uuid)".to_owned(),
|
||||
KclValue::TagDeclarator(_) => "a tag declarator".to_owned(),
|
||||
KclValue::TagIdentifier(_) => "a tag identifier".to_owned(),
|
||||
KclValue::Solid { .. } => "a solid".to_owned(),
|
||||
KclValue::Sketch { .. } => "a sketch".to_owned(),
|
||||
KclValue::Helix { .. } => "a helix".to_owned(),
|
||||
KclValue::ImportedGeometry(_) => "an imported geometry".to_owned(),
|
||||
KclValue::Function { .. } => "a function".to_owned(),
|
||||
KclValue::Plane { .. } => "a plane".to_owned(),
|
||||
KclValue::Face { .. } => "a face".to_owned(),
|
||||
KclValue::Bool { .. } => "a boolean (`true` or `false`)".to_owned(),
|
||||
KclValue::Number {
|
||||
ty: NumericType::Unknown,
|
||||
..
|
||||
} => "number(unknown units)",
|
||||
} => "a number with unknown units".to_owned(),
|
||||
KclValue::Number {
|
||||
ty: NumericType::Known(UnitType::Length(_)),
|
||||
ty: NumericType::Known(units),
|
||||
..
|
||||
} => "number(Length)",
|
||||
KclValue::Number {
|
||||
ty: NumericType::Known(UnitType::Angle(_)),
|
||||
..
|
||||
} => "number(Angle)",
|
||||
KclValue::Number { .. } => "number",
|
||||
KclValue::String { .. } => "string (text)",
|
||||
KclValue::Tuple { .. } => "tuple (list)",
|
||||
KclValue::HomArray { .. } => "array (list)",
|
||||
KclValue::Object { .. } => "object",
|
||||
KclValue::Module { .. } => "module",
|
||||
KclValue::Type { .. } => "type",
|
||||
KclValue::KclNone { .. } => "None",
|
||||
} => format!("a number ({units})"),
|
||||
KclValue::Number { .. } => "a number".to_owned(),
|
||||
KclValue::String { .. } => "a string".to_owned(),
|
||||
KclValue::Object { .. } => "an object".to_owned(),
|
||||
KclValue::Module { .. } => "a module".to_owned(),
|
||||
KclValue::Type { .. } => "a type".to_owned(),
|
||||
KclValue::KclNone { .. } => "none".to_owned(),
|
||||
KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => {
|
||||
if value.is_empty() {
|
||||
"an empty array".to_owned()
|
||||
} else {
|
||||
// A max of 3 is good because it's common to use 3D points.
|
||||
const MAX: usize = 3;
|
||||
|
||||
let len = value.len();
|
||||
let element_tys = value
|
||||
.iter()
|
||||
.take(MAX)
|
||||
.map(|elem| elem.principal_type_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let mut result = format!("an array of {element_tys}");
|
||||
if len > MAX {
|
||||
result.push_str(&format!(", ... with {len} values"));
|
||||
}
|
||||
if len == 1 {
|
||||
result.push_str(" with 1 value");
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
.to_owned()
|
||||
}
|
||||
|
||||
pub(crate) fn from_literal(literal: Node<Literal>, exec_state: &mut ExecState) -> Self {
|
||||
@ -602,6 +614,13 @@ impl KclValue {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_unknown_number(&self) -> bool {
|
||||
match self {
|
||||
KclValue::Number { ty, .. } => !ty.is_fully_specified(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn value_str(&self) -> Option<String> {
|
||||
match self {
|
||||
KclValue::Bool { value, .. } => Some(format!("{value}")),
|
||||
@ -650,6 +669,7 @@ impl From<GeometryWithImportedGeometry> for KclValue {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::exec::UnitType;
|
||||
|
||||
#[test]
|
||||
fn test_human_friendly_type() {
|
||||
@ -658,21 +678,21 @@ mod tests {
|
||||
ty: NumericType::Known(UnitType::Length(UnitLen::Unknown)),
|
||||
meta: vec![],
|
||||
};
|
||||
assert_eq!(len.human_friendly_type(), "number(Length)".to_string());
|
||||
assert_eq!(len.human_friendly_type(), "a number (Length)".to_string());
|
||||
|
||||
let unknown = KclValue::Number {
|
||||
value: 1.0,
|
||||
ty: NumericType::Unknown,
|
||||
meta: vec![],
|
||||
};
|
||||
assert_eq!(unknown.human_friendly_type(), "number(unknown units)".to_string());
|
||||
assert_eq!(unknown.human_friendly_type(), "a number with unknown units".to_string());
|
||||
|
||||
let mm = KclValue::Number {
|
||||
value: 1.0,
|
||||
ty: NumericType::Known(UnitType::Length(UnitLen::Mm)),
|
||||
meta: vec![],
|
||||
};
|
||||
assert_eq!(mm.human_friendly_type(), "number(mm)".to_string());
|
||||
assert_eq!(mm.human_friendly_type(), "a number (mm)".to_string());
|
||||
|
||||
let array1_mm = KclValue::HomArray {
|
||||
value: vec![mm.clone()],
|
||||
@ -680,7 +700,7 @@ mod tests {
|
||||
};
|
||||
assert_eq!(
|
||||
array1_mm.human_friendly_type(),
|
||||
"array of number(mm) with 1 value".to_string()
|
||||
"an array of `number(mm)` with 1 value".to_string()
|
||||
);
|
||||
|
||||
let array2_mm = KclValue::HomArray {
|
||||
@ -689,7 +709,7 @@ mod tests {
|
||||
};
|
||||
assert_eq!(
|
||||
array2_mm.human_friendly_type(),
|
||||
"array of number(mm), number(mm) with 2 values".to_string()
|
||||
"an array of `number(mm)`, `number(mm)`".to_string()
|
||||
);
|
||||
|
||||
let array3_mm = KclValue::HomArray {
|
||||
@ -698,7 +718,7 @@ mod tests {
|
||||
};
|
||||
assert_eq!(
|
||||
array3_mm.human_friendly_type(),
|
||||
"array of number(mm), number(mm), number(mm) with 3 values".to_string()
|
||||
"an array of `number(mm)`, `number(mm)`, `number(mm)`".to_string()
|
||||
);
|
||||
|
||||
let inches = KclValue::Number {
|
||||
@ -712,14 +732,14 @@ mod tests {
|
||||
};
|
||||
assert_eq!(
|
||||
array4.human_friendly_type(),
|
||||
"array of number(mm), number(mm), number(in), ... with 4 values".to_string()
|
||||
"an array of `number(mm)`, `number(mm)`, `number(in)`, ... with 4 values".to_string()
|
||||
);
|
||||
|
||||
let empty_array = KclValue::HomArray {
|
||||
value: vec![],
|
||||
ty: RuntimeType::any(),
|
||||
};
|
||||
assert_eq!(empty_array.human_friendly_type(), "[any; 0]".to_string());
|
||||
assert_eq!(empty_array.human_friendly_type(), "an empty array".to_string());
|
||||
|
||||
let array_nested = KclValue::HomArray {
|
||||
value: vec![array2_mm.clone()],
|
||||
@ -727,7 +747,7 @@ mod tests {
|
||||
};
|
||||
assert_eq!(
|
||||
array_nested.human_friendly_type(),
|
||||
"array of [any; 2] with 1 value".to_string()
|
||||
"an array of `[any; 2]` with 1 value".to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,8 @@ pub use artifact::{Artifact, ArtifactCommand, ArtifactGraph, CodeRef, StartSketc
|
||||
use cache::GlobalState;
|
||||
pub use cache::{bust_cache, clear_mem_cache};
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub use cad_op::{Group, Operation};
|
||||
pub use cad_op::Group;
|
||||
pub use cad_op::Operation;
|
||||
pub use geometry::*;
|
||||
pub use id_generator::IdGenerator;
|
||||
pub(crate) use import::PreImportedGeometry;
|
||||
@ -22,8 +23,10 @@ use kcmc::{
|
||||
};
|
||||
use kittycad_modeling_cmds::{self as kcmc, id::ModelingCmdId};
|
||||
pub use memory::EnvironmentRef;
|
||||
pub(crate) use modeling::ModelingCmdMeta;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
pub(crate) use state::ModuleArtifactState;
|
||||
pub use state::{ExecState, MetaSettings};
|
||||
use uuid::Uuid;
|
||||
|
||||
@ -56,6 +59,7 @@ mod import;
|
||||
mod import_graph;
|
||||
pub(crate) mod kcl_value;
|
||||
mod memory;
|
||||
mod modeling;
|
||||
mod state;
|
||||
pub mod typed_path;
|
||||
pub(crate) mod types;
|
||||
@ -76,9 +80,6 @@ pub struct ExecOutcome {
|
||||
/// the Feature Tree.
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub operations: Vec<Operation>,
|
||||
/// Output commands to allow building the artifact graph by the caller.
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub artifact_commands: Vec<ArtifactCommand>,
|
||||
/// Output artifact graph.
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub artifact_graph: ArtifactGraph,
|
||||
@ -518,6 +519,12 @@ impl ExecutorContext {
|
||||
exec_state: &mut ExecState,
|
||||
source_range: crate::execution::SourceRange,
|
||||
) -> Result<(), KclError> {
|
||||
// Ensure artifacts are cleared so that we don't accumulate them across
|
||||
// runs.
|
||||
exec_state.mod_local.artifacts.clear();
|
||||
exec_state.global.root_module_artifacts.clear();
|
||||
exec_state.global.artifacts.clear();
|
||||
|
||||
self.engine
|
||||
.clear_scene(&mut exec_state.mod_local.id_generator, source_range)
|
||||
.await
|
||||
@ -575,7 +582,7 @@ impl ExecutorContext {
|
||||
|
||||
let mut mem = exec_state.stack().clone();
|
||||
let module_infos = exec_state.global.module_infos.clone();
|
||||
let outcome = exec_state.to_mock_exec_outcome(result.0, self).await;
|
||||
let outcome = exec_state.into_mock_exec_outcome(result.0, self).await;
|
||||
|
||||
mem.squash_env(result.0);
|
||||
cache::write_old_memory((mem, module_infos)).await;
|
||||
@ -649,8 +656,8 @@ impl ExecutorContext {
|
||||
let (new_universe, new_universe_map) =
|
||||
self.get_universe(&program, &mut new_exec_state).await?;
|
||||
|
||||
let clear_scene = new_universe.keys().any(|key| {
|
||||
let id = new_universe[key].1;
|
||||
let clear_scene = new_universe.values().any(|value| {
|
||||
let id = value.1;
|
||||
match (
|
||||
cached_state.exec_state.get_source(id),
|
||||
new_exec_state.global.get_source(id),
|
||||
@ -773,15 +780,12 @@ impl ExecutorContext {
|
||||
))
|
||||
.await;
|
||||
|
||||
let outcome = exec_state.to_exec_outcome(result.0, self).await;
|
||||
let outcome = exec_state.into_exec_outcome(result.0, self).await;
|
||||
Ok(outcome)
|
||||
}
|
||||
|
||||
/// Perform the execution of a program.
|
||||
///
|
||||
/// You can optionally pass in some initialization memory for partial
|
||||
/// execution.
|
||||
///
|
||||
/// To access non-fatal errors and warnings, extract them from the `ExecState`.
|
||||
pub async fn run(
|
||||
&self,
|
||||
@ -794,9 +798,6 @@ impl ExecutorContext {
|
||||
/// Perform the execution of a program using a concurrent
|
||||
/// execution model.
|
||||
///
|
||||
/// You can optionally pass in some initialization memory for partial
|
||||
/// execution.
|
||||
///
|
||||
/// To access non-fatal errors and warnings, extract them from the `ExecState`.
|
||||
pub async fn run_concurrent(
|
||||
&self,
|
||||
@ -842,6 +843,8 @@ impl ExecutorContext {
|
||||
let module_id = *module_id;
|
||||
let module_path = module_path.clone();
|
||||
let source_range = SourceRange::from(import_stmt);
|
||||
// Clone before mutating.
|
||||
let module_exec_state = exec_state.clone();
|
||||
|
||||
self.add_import_module_ops(
|
||||
exec_state,
|
||||
@ -853,7 +856,6 @@ impl ExecutorContext {
|
||||
);
|
||||
|
||||
let repr = repr.clone();
|
||||
let exec_state = exec_state.clone();
|
||||
let exec_ctxt = self.clone();
|
||||
let results_tx = results_tx.clone();
|
||||
|
||||
@ -873,11 +875,13 @@ impl ExecutorContext {
|
||||
result.map(|val| ModuleRepr::Kcl(program.clone(), Some(val)))
|
||||
}
|
||||
ModuleRepr::Foreign(geom, _) => {
|
||||
let result = crate::execution::import::send_to_engine(geom.clone(), exec_ctxt)
|
||||
let result = crate::execution::import::send_to_engine(geom.clone(), exec_state, exec_ctxt)
|
||||
.await
|
||||
.map(|geom| Some(KclValue::ImportedGeometry(geom)));
|
||||
|
||||
result.map(|val| ModuleRepr::Foreign(geom.clone(), val))
|
||||
result.map(|val| {
|
||||
ModuleRepr::Foreign(geom.clone(), Some((val, exec_state.mod_local.artifacts.clone())))
|
||||
})
|
||||
}
|
||||
ModuleRepr::Dummy | ModuleRepr::Root => Err(KclError::new_internal(KclErrorDetails::new(
|
||||
format!("Module {module_path} not found in universe"),
|
||||
@ -889,7 +893,7 @@ impl ExecutorContext {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
let mut exec_state = exec_state;
|
||||
let mut exec_state = module_exec_state;
|
||||
let exec_ctxt = exec_ctxt;
|
||||
|
||||
let result = exec_module(
|
||||
@ -911,7 +915,7 @@ impl ExecutorContext {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
set.spawn(async move {
|
||||
let mut exec_state = exec_state;
|
||||
let mut exec_state = module_exec_state;
|
||||
let exec_ctxt = exec_ctxt;
|
||||
|
||||
let result = exec_module(
|
||||
@ -964,6 +968,14 @@ impl ExecutorContext {
|
||||
}
|
||||
}
|
||||
|
||||
// Since we haven't technically started executing the root module yet,
|
||||
// the operations corresponding to the imports will be missing unless we
|
||||
// track them here.
|
||||
exec_state
|
||||
.global
|
||||
.root_module_artifacts
|
||||
.extend(std::mem::take(&mut exec_state.mod_local.artifacts));
|
||||
|
||||
self.inner_run(program, exec_state, preserve_mem).await
|
||||
}
|
||||
|
||||
@ -993,6 +1005,18 @@ impl ExecutorContext {
|
||||
Ok((universe, root_imports))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "artifact-graph"))]
|
||||
fn add_import_module_ops(
|
||||
&self,
|
||||
_exec_state: &mut ExecState,
|
||||
_program: &crate::Program,
|
||||
_module_id: ModuleId,
|
||||
_module_path: &ModulePath,
|
||||
_source_range: SourceRange,
|
||||
_universe_map: &UniverseMap,
|
||||
) {
|
||||
}
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
fn add_import_module_ops(
|
||||
&self,
|
||||
@ -1042,18 +1066,6 @@ impl ExecutorContext {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "artifact-graph"))]
|
||||
fn add_import_module_ops(
|
||||
&self,
|
||||
_exec_state: &mut ExecState,
|
||||
_program: &crate::Program,
|
||||
_module_id: ModuleId,
|
||||
_module_path: &ModulePath,
|
||||
_source_range: SourceRange,
|
||||
_universe_map: &UniverseMap,
|
||||
) {
|
||||
}
|
||||
|
||||
/// Perform the execution of a program. Accept all possible parameters and
|
||||
/// output everything.
|
||||
async fn inner_run(
|
||||
@ -1107,7 +1119,7 @@ impl ExecutorContext {
|
||||
// Because of execution caching, we may start with operations from a
|
||||
// previous run.
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
let start_op = exec_state.global.artifacts.operations.len();
|
||||
let start_op = exec_state.global.root_module_artifacts.operations.len();
|
||||
|
||||
self.eval_prelude(exec_state, SourceRange::from(program).start_as_range())
|
||||
.await?;
|
||||
@ -1120,27 +1132,40 @@ impl ExecutorContext {
|
||||
ModuleId::default(),
|
||||
&ModulePath::Main,
|
||||
)
|
||||
.await;
|
||||
.await
|
||||
.map(|(_, env_ref, _, module_artifacts)| {
|
||||
// We need to extend because it may already have operations from
|
||||
// imports.
|
||||
exec_state.global.root_module_artifacts.extend(module_artifacts);
|
||||
env_ref
|
||||
})
|
||||
.map_err(|(err, module_artifacts)| {
|
||||
if let Some(module_artifacts) = module_artifacts {
|
||||
// We need to extend because it may already have operations
|
||||
// from imports.
|
||||
exec_state.global.root_module_artifacts.extend(module_artifacts);
|
||||
}
|
||||
err
|
||||
});
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
{
|
||||
// Fill in NodePath for operations.
|
||||
let cached_body_items = exec_state.global.artifacts.cached_body_items();
|
||||
for op in exec_state.global.artifacts.operations.iter_mut().skip(start_op) {
|
||||
match op {
|
||||
Operation::StdLibCall {
|
||||
node_path,
|
||||
source_range,
|
||||
..
|
||||
for op in exec_state
|
||||
.global
|
||||
.root_module_artifacts
|
||||
.operations
|
||||
.iter_mut()
|
||||
.skip(start_op)
|
||||
{
|
||||
op.fill_node_paths(program, cached_body_items);
|
||||
}
|
||||
for module in exec_state.global.module_infos.values_mut() {
|
||||
if let ModuleRepr::Kcl(_, Some((_, _, _, module_artifacts))) = &mut module.repr {
|
||||
for op in &mut module_artifacts.operations {
|
||||
op.fill_node_paths(program, cached_body_items);
|
||||
}
|
||||
| Operation::GroupBegin {
|
||||
node_path,
|
||||
source_range,
|
||||
..
|
||||
} => {
|
||||
node_path.fill_placeholder(program, cached_body_items, *source_range);
|
||||
}
|
||||
Operation::GroupEnd => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1153,7 +1178,7 @@ impl ExecutorContext {
|
||||
self.engine.clear_queues().await;
|
||||
|
||||
match exec_state.build_artifact_graph(&self.engine, program).await {
|
||||
Ok(_) => exec_result.map(|(_, env_ref, _)| env_ref),
|
||||
Ok(_) => exec_result,
|
||||
Err(err) => exec_result.and(Err(err)),
|
||||
}
|
||||
}
|
||||
@ -1163,6 +1188,9 @@ impl ExecutorContext {
|
||||
/// SAFETY: the current thread must have sole access to the memory referenced in exec_state.
|
||||
async fn eval_prelude(&self, exec_state: &mut ExecState, source_range: SourceRange) -> Result<(), KclError> {
|
||||
if exec_state.stack().memory.requires_std() {
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
let initial_ops = exec_state.mod_local.artifacts.operations.len();
|
||||
|
||||
let path = vec!["std".to_owned(), "prelude".to_owned()];
|
||||
let resolved_path = ModulePath::from_std_import_path(&path)?;
|
||||
let id = self
|
||||
@ -1171,6 +1199,14 @@ impl ExecutorContext {
|
||||
let (module_memory, _) = self.exec_module_for_items(id, exec_state, source_range).await?;
|
||||
|
||||
exec_state.mut_stack().memory.set_std(module_memory);
|
||||
|
||||
// Operations generated by the prelude are not useful, so clear them
|
||||
// out.
|
||||
//
|
||||
// TODO: Should we also clear them out of each module so that they
|
||||
// don't appear in test output?
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
exec_state.mod_local.artifacts.operations.truncate(initial_ops);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -1919,13 +1955,13 @@ notNull = !myNull
|
||||
"#;
|
||||
assert_eq!(
|
||||
parse_execute(code1).await.unwrap_err().message(),
|
||||
"Cannot apply unary operator ! to non-boolean value: number(default units)",
|
||||
"Cannot apply unary operator ! to non-boolean value: a number",
|
||||
);
|
||||
|
||||
let code2 = "notZero = !0";
|
||||
assert_eq!(
|
||||
parse_execute(code2).await.unwrap_err().message(),
|
||||
"Cannot apply unary operator ! to non-boolean value: number(default units)",
|
||||
"Cannot apply unary operator ! to non-boolean value: a number",
|
||||
);
|
||||
|
||||
let code3 = r#"
|
||||
@ -1933,7 +1969,7 @@ notEmptyString = !""
|
||||
"#;
|
||||
assert_eq!(
|
||||
parse_execute(code3).await.unwrap_err().message(),
|
||||
"Cannot apply unary operator ! to non-boolean value: string",
|
||||
"Cannot apply unary operator ! to non-boolean value: a string",
|
||||
);
|
||||
|
||||
let code4 = r#"
|
||||
@ -1942,7 +1978,7 @@ notMember = !obj.a
|
||||
"#;
|
||||
assert_eq!(
|
||||
parse_execute(code4).await.unwrap_err().message(),
|
||||
"Cannot apply unary operator ! to non-boolean value: number(default units)",
|
||||
"Cannot apply unary operator ! to non-boolean value: a number",
|
||||
);
|
||||
|
||||
let code5 = "
|
||||
@ -1950,7 +1986,7 @@ a = []
|
||||
notArray = !a";
|
||||
assert_eq!(
|
||||
parse_execute(code5).await.unwrap_err().message(),
|
||||
"Cannot apply unary operator ! to non-boolean value: [any; 0]",
|
||||
"Cannot apply unary operator ! to non-boolean value: an empty array",
|
||||
);
|
||||
|
||||
let code6 = "
|
||||
@ -1958,7 +1994,7 @@ x = {}
|
||||
notObject = !x";
|
||||
assert_eq!(
|
||||
parse_execute(code6).await.unwrap_err().message(),
|
||||
"Cannot apply unary operator ! to non-boolean value: { }",
|
||||
"Cannot apply unary operator ! to non-boolean value: an object",
|
||||
);
|
||||
|
||||
let code7 = "
|
||||
@ -1984,7 +2020,7 @@ notTagDeclarator = !myTagDeclarator";
|
||||
assert!(
|
||||
tag_declarator_err
|
||||
.message()
|
||||
.starts_with("Cannot apply unary operator ! to non-boolean value: tag"),
|
||||
.starts_with("Cannot apply unary operator ! to non-boolean value: a tag declarator"),
|
||||
"Actual error: {:?}",
|
||||
tag_declarator_err
|
||||
);
|
||||
@ -1998,7 +2034,7 @@ notTagIdentifier = !myTag";
|
||||
assert!(
|
||||
tag_identifier_err
|
||||
.message()
|
||||
.starts_with("Cannot apply unary operator ! to non-boolean value: tag"),
|
||||
.starts_with("Cannot apply unary operator ! to non-boolean value: a tag identifier"),
|
||||
"Actual error: {:?}",
|
||||
tag_identifier_err
|
||||
);
|
||||
@ -2250,6 +2286,39 @@ w = f() + f()
|
||||
ctx2.close().await;
|
||||
}
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn sim_sketch_mode_real_mock_real() {
|
||||
let ctx = ExecutorContext::new_with_default_client().await.unwrap();
|
||||
let code = r#"sketch001 = startSketchOn(XY)
|
||||
profile001 = startProfile(sketch001, at = [0, 0])
|
||||
|> line(end = [10, 0])
|
||||
|> line(end = [0, 10])
|
||||
|> line(end = [-10, 0])
|
||||
|> line(end = [0, -10])
|
||||
|> close()
|
||||
"#;
|
||||
let program = crate::Program::parse_no_errs(code).unwrap();
|
||||
let result = ctx.run_with_caching(program).await.unwrap();
|
||||
assert_eq!(result.operations.len(), 1);
|
||||
|
||||
let mock_ctx = ExecutorContext::new_mock(None).await;
|
||||
let mock_program = crate::Program::parse_no_errs(code).unwrap();
|
||||
let mock_result = mock_ctx.run_mock(mock_program, true).await.unwrap();
|
||||
assert_eq!(mock_result.operations.len(), 0);
|
||||
|
||||
let code2 = code.to_owned()
|
||||
+ r#"
|
||||
extrude001 = extrude(profile001, length = 10)
|
||||
"#;
|
||||
let program2 = crate::Program::parse_no_errs(&code2).unwrap();
|
||||
let result = ctx.run_with_caching(program2).await.unwrap();
|
||||
assert_eq!(result.operations.len(), 2);
|
||||
|
||||
ctx.close().await;
|
||||
mock_ctx.close().await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn read_tag_version() {
|
||||
let ast = r#"fn bar(@t) {
|
||||
|
||||
224
rust/kcl-lib/src/execution/modeling.rs
Normal file
224
rust/kcl-lib/src/execution/modeling.rs
Normal file
@ -0,0 +1,224 @@
|
||||
use kcmc::ModelingCmd;
|
||||
use kittycad_modeling_cmds::{
|
||||
self as kcmc,
|
||||
websocket::{ModelingCmdReq, OkWebSocketResponseData},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
use crate::exec::ArtifactCommand;
|
||||
use crate::{
|
||||
exec::{IdGenerator, KclValue},
|
||||
execution::Solid,
|
||||
std::Args,
|
||||
ExecState, ExecutorContext, KclError, SourceRange,
|
||||
};
|
||||
|
||||
/// Context and metadata needed to send a single modeling command.
|
||||
///
|
||||
/// Many functions consume Self so that the command ID isn't accidentally reused
|
||||
/// among multiple modeling commands.
|
||||
pub(crate) struct ModelingCmdMeta<'a> {
|
||||
/// The executor context, which contains the engine.
|
||||
pub ctx: &'a ExecutorContext,
|
||||
/// The source range of the command, used for error reporting.
|
||||
pub source_range: SourceRange,
|
||||
/// The id of the command, if it has been set by the caller or generated.
|
||||
id: Option<Uuid>,
|
||||
}
|
||||
|
||||
impl<'a> ModelingCmdMeta<'a> {
|
||||
pub fn new(ctx: &'a ExecutorContext, source_range: SourceRange) -> Self {
|
||||
ModelingCmdMeta {
|
||||
ctx,
|
||||
source_range,
|
||||
id: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_id(ctx: &'a ExecutorContext, source_range: SourceRange, id: Uuid) -> Self {
|
||||
ModelingCmdMeta {
|
||||
ctx,
|
||||
source_range,
|
||||
id: Some(id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_args_id(args: &'a Args, id: Uuid) -> Self {
|
||||
ModelingCmdMeta {
|
||||
ctx: &args.ctx,
|
||||
source_range: args.source_range,
|
||||
id: Some(id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&mut self, id_generator: &mut IdGenerator) -> Uuid {
|
||||
if let Some(id) = self.id {
|
||||
return id;
|
||||
}
|
||||
let id = id_generator.next_uuid();
|
||||
self.id = Some(id);
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Args> for ModelingCmdMeta<'a> {
|
||||
fn from(args: &'a Args) -> Self {
|
||||
ModelingCmdMeta::new(&args.ctx, args.source_range)
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecState {
|
||||
/// Add a modeling command to the batch but don't fire it right away.
|
||||
pub(crate) async fn batch_modeling_cmd(
|
||||
&mut self,
|
||||
mut meta: ModelingCmdMeta<'_>,
|
||||
cmd: ModelingCmd,
|
||||
) -> Result<(), crate::errors::KclError> {
|
||||
let id = meta.id(self.id_generator());
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
self.push_command(ArtifactCommand {
|
||||
cmd_id: id,
|
||||
range: meta.source_range,
|
||||
command: cmd.clone(),
|
||||
});
|
||||
meta.ctx.engine.batch_modeling_cmd(id, meta.source_range, &cmd).await
|
||||
}
|
||||
|
||||
/// Add multiple modeling commands to the batch but don't fire them right
|
||||
/// away.
|
||||
pub(crate) async fn batch_modeling_cmds(
|
||||
&mut self,
|
||||
meta: ModelingCmdMeta<'_>,
|
||||
cmds: &[ModelingCmdReq],
|
||||
) -> Result<(), crate::errors::KclError> {
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
for cmd_req in cmds {
|
||||
self.push_command(ArtifactCommand {
|
||||
cmd_id: *cmd_req.cmd_id.as_ref(),
|
||||
range: meta.source_range,
|
||||
command: cmd_req.cmd.clone(),
|
||||
});
|
||||
}
|
||||
meta.ctx.engine.batch_modeling_cmds(meta.source_range, cmds).await
|
||||
}
|
||||
|
||||
/// Add a modeling command to the batch that gets executed at the end of the
|
||||
/// file. This is good for something like fillet or chamfer where the engine
|
||||
/// would eat the path id if we executed it right away.
|
||||
pub(crate) async fn batch_end_cmd(
|
||||
&mut self,
|
||||
mut meta: ModelingCmdMeta<'_>,
|
||||
cmd: ModelingCmd,
|
||||
) -> Result<(), crate::errors::KclError> {
|
||||
let id = meta.id(self.id_generator());
|
||||
// TODO: The order of the tracking of these doesn't match the order that
|
||||
// they're sent to the engine.
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
self.push_command(ArtifactCommand {
|
||||
cmd_id: id,
|
||||
range: meta.source_range,
|
||||
command: cmd.clone(),
|
||||
});
|
||||
meta.ctx.engine.batch_end_cmd(id, meta.source_range, &cmd).await
|
||||
}
|
||||
|
||||
/// Send the modeling cmd and wait for the response.
|
||||
pub(crate) async fn send_modeling_cmd(
|
||||
&mut self,
|
||||
mut meta: ModelingCmdMeta<'_>,
|
||||
cmd: ModelingCmd,
|
||||
) -> Result<OkWebSocketResponseData, KclError> {
|
||||
let id = meta.id(self.id_generator());
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
self.push_command(ArtifactCommand {
|
||||
cmd_id: id,
|
||||
range: meta.source_range,
|
||||
command: cmd.clone(),
|
||||
});
|
||||
meta.ctx.engine.send_modeling_cmd(id, meta.source_range, &cmd).await
|
||||
}
|
||||
|
||||
/// Send the modeling cmd async and don't wait for the response.
|
||||
/// Add it to our list of async commands.
|
||||
pub(crate) async fn async_modeling_cmd(
|
||||
&mut self,
|
||||
mut meta: ModelingCmdMeta<'_>,
|
||||
cmd: &ModelingCmd,
|
||||
) -> Result<(), crate::errors::KclError> {
|
||||
let id = meta.id(self.id_generator());
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
self.push_command(ArtifactCommand {
|
||||
cmd_id: id,
|
||||
range: meta.source_range,
|
||||
command: cmd.clone(),
|
||||
});
|
||||
meta.ctx.engine.async_modeling_cmd(id, meta.source_range, cmd).await
|
||||
}
|
||||
|
||||
/// Force flush the batch queue.
|
||||
pub(crate) async fn flush_batch(
|
||||
&mut self,
|
||||
meta: ModelingCmdMeta<'_>,
|
||||
// Whether or not to flush the end commands as well.
|
||||
// We only do this at the very end of the file.
|
||||
batch_end: bool,
|
||||
) -> Result<OkWebSocketResponseData, KclError> {
|
||||
meta.ctx.engine.flush_batch(batch_end, meta.source_range).await
|
||||
}
|
||||
|
||||
/// Flush just the fillets and chamfers for this specific SolidSet.
|
||||
pub(crate) async fn flush_batch_for_solids(
|
||||
&mut self,
|
||||
meta: ModelingCmdMeta<'_>,
|
||||
solids: &[Solid],
|
||||
) -> Result<(), KclError> {
|
||||
// Make sure we don't traverse sketches more than once.
|
||||
let mut traversed_sketches = Vec::new();
|
||||
|
||||
// Collect all the fillet/chamfer ids for the solids.
|
||||
let mut ids = Vec::new();
|
||||
for solid in solids {
|
||||
// We need to traverse the solids that share the same sketch.
|
||||
let sketch_id = solid.sketch.id;
|
||||
if !traversed_sketches.contains(&sketch_id) {
|
||||
// Find all the solids on the same shared sketch.
|
||||
ids.extend(
|
||||
self.stack()
|
||||
.walk_call_stack()
|
||||
.filter(|v| matches!(v, KclValue::Solid { value } if value.sketch.id == sketch_id))
|
||||
.flat_map(|v| match v {
|
||||
KclValue::Solid { value } => value.get_all_edge_cut_ids(),
|
||||
_ => unreachable!(),
|
||||
}),
|
||||
);
|
||||
traversed_sketches.push(sketch_id);
|
||||
}
|
||||
|
||||
ids.extend(solid.get_all_edge_cut_ids());
|
||||
}
|
||||
|
||||
// We can return early if there are no fillets or chamfers.
|
||||
if ids.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// We want to move these fillets and chamfers from batch_end to batch so they get executed
|
||||
// before what ever we call next.
|
||||
for id in ids {
|
||||
// Pop it off the batch_end and add it to the batch.
|
||||
let Some(item) = meta.ctx.engine.batch_end().write().await.shift_remove(&id) else {
|
||||
// It might be in the batch already.
|
||||
continue;
|
||||
};
|
||||
// Add it to the batch.
|
||||
meta.ctx.engine.batch().write().await.push(item);
|
||||
}
|
||||
|
||||
// Run flush.
|
||||
// Yes, we do need to actually flush the batch here, or references will fail later.
|
||||
self.flush_batch(meta, false).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -2,8 +2,6 @@ use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use indexmap::IndexMap;
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
use kittycad_modeling_cmds::websocket::WebSocketResponse;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
@ -48,34 +46,46 @@ pub(super) struct GlobalState {
|
||||
pub mod_loader: ModuleLoader,
|
||||
/// Errors and warnings.
|
||||
pub errors: Vec<CompilationError>,
|
||||
#[cfg_attr(not(feature = "artifact-graph"), allow(dead_code))]
|
||||
/// Global artifacts that represent the entire program.
|
||||
pub artifacts: ArtifactState,
|
||||
/// Artifacts for only the root module.
|
||||
pub root_module_artifacts: ModuleArtifactState,
|
||||
}
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(super) struct ArtifactState {
|
||||
/// Output map of UUIDs to artifacts.
|
||||
/// Internal map of UUIDs to exec artifacts. This needs to persist across
|
||||
/// executions to allow the graph building to refer to cached artifacts.
|
||||
pub artifacts: IndexMap<ArtifactId, Artifact>,
|
||||
/// Output commands to allow building the artifact graph by the caller.
|
||||
/// These are accumulated in the [`ExecutorContext`] but moved here for
|
||||
/// convenience of the execution cache.
|
||||
pub commands: Vec<ArtifactCommand>,
|
||||
/// Responses from the engine for `artifact_commands`. We need to cache
|
||||
/// this so that we can build the artifact graph. These are accumulated in
|
||||
/// the [`ExecutorContext`] but moved here for convenience of the execution
|
||||
/// cache.
|
||||
pub responses: IndexMap<Uuid, WebSocketResponse>,
|
||||
/// Output artifact graph.
|
||||
pub graph: ArtifactGraph,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "artifact-graph"))]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(super) struct ArtifactState {}
|
||||
|
||||
/// Artifact state for a single module.
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize)]
|
||||
pub struct ModuleArtifactState {
|
||||
/// Internal map of UUIDs to exec artifacts.
|
||||
pub artifacts: IndexMap<ArtifactId, Artifact>,
|
||||
/// Outgoing engine commands that have not yet been processed and integrated
|
||||
/// into the artifact graph.
|
||||
#[serde(skip)]
|
||||
pub unprocessed_commands: Vec<ArtifactCommand>,
|
||||
/// Outgoing engine commands.
|
||||
pub commands: Vec<ArtifactCommand>,
|
||||
/// Operations that have been performed in execution order, for display in
|
||||
/// the Feature Tree.
|
||||
pub operations: Vec<Operation>,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "artifact-graph"))]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(super) struct ArtifactState {}
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize)]
|
||||
pub struct ModuleArtifactState {}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct ModuleState {
|
||||
@ -96,6 +106,8 @@ pub(super) struct ModuleState {
|
||||
pub settings: MetaSettings,
|
||||
pub(super) explicit_length_units: bool,
|
||||
pub(super) path: ModulePath,
|
||||
/// Artifacts for only this module.
|
||||
pub artifacts: ModuleArtifactState,
|
||||
}
|
||||
|
||||
impl ExecState {
|
||||
@ -126,6 +138,17 @@ impl ExecState {
|
||||
self.global.errors.push(e);
|
||||
}
|
||||
|
||||
pub fn clear_units_warnings(&mut self, source_range: &SourceRange) {
|
||||
self.global.errors = std::mem::take(&mut self.global.errors)
|
||||
.into_iter()
|
||||
.filter(|e| {
|
||||
e.severity != Severity::Warning
|
||||
|| !source_range.contains_range(&e.source_range)
|
||||
|| e.tag != crate::errors::Tag::UnknownNumericUnits
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
pub fn errors(&self) -> &[CompilationError] {
|
||||
&self.global.errors
|
||||
}
|
||||
@ -133,16 +156,14 @@ impl ExecState {
|
||||
/// Convert to execution outcome when running in WebAssembly. We want to
|
||||
/// reduce the amount of data that crosses the WASM boundary as much as
|
||||
/// possible.
|
||||
pub async fn to_exec_outcome(self, main_ref: EnvironmentRef, ctx: &ExecutorContext) -> ExecOutcome {
|
||||
pub async fn into_exec_outcome(self, main_ref: EnvironmentRef, ctx: &ExecutorContext) -> ExecOutcome {
|
||||
// Fields are opt-in so that we don't accidentally leak private internal
|
||||
// state when we add more to ExecState.
|
||||
ExecOutcome {
|
||||
variables: self.mod_local.variables(main_ref),
|
||||
filenames: self.global.filenames(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
operations: self.global.artifacts.operations,
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifact_commands: self.global.artifacts.commands,
|
||||
operations: self.global.root_module_artifacts.operations,
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifact_graph: self.global.artifacts.graph,
|
||||
errors: self.global.errors,
|
||||
@ -150,14 +171,12 @@ impl ExecState {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn to_mock_exec_outcome(self, main_ref: EnvironmentRef, ctx: &ExecutorContext) -> ExecOutcome {
|
||||
pub async fn into_mock_exec_outcome(self, main_ref: EnvironmentRef, ctx: &ExecutorContext) -> ExecOutcome {
|
||||
ExecOutcome {
|
||||
variables: self.mod_local.variables(main_ref),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
operations: Default::default(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifact_commands: Default::default(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifact_graph: Default::default(),
|
||||
errors: self.global.errors,
|
||||
filenames: Default::default(),
|
||||
@ -184,16 +203,23 @@ impl ExecState {
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub(crate) fn add_artifact(&mut self, artifact: Artifact) {
|
||||
let id = artifact.id();
|
||||
self.global.artifacts.artifacts.insert(id, artifact);
|
||||
self.mod_local.artifacts.artifacts.insert(id, artifact);
|
||||
}
|
||||
|
||||
pub(crate) fn push_op(&mut self, op: Operation) {
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
self.global.artifacts.operations.push(op);
|
||||
self.mod_local.artifacts.operations.push(op.clone());
|
||||
#[cfg(not(feature = "artifact-graph"))]
|
||||
drop(op);
|
||||
}
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub(crate) fn push_command(&mut self, command: ArtifactCommand) {
|
||||
self.mod_local.artifacts.unprocessed_commands.push(command);
|
||||
#[cfg(not(feature = "artifact-graph"))]
|
||||
drop(command);
|
||||
}
|
||||
|
||||
pub(super) fn next_module_id(&self) -> ModuleId {
|
||||
ModuleId::from_usize(self.global.path_to_source_id.len())
|
||||
}
|
||||
@ -241,6 +267,16 @@ impl ExecState {
|
||||
self.global.module_infos.get(&id)
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "artifact-graph"))]
|
||||
pub(crate) fn modules(&self) -> &ModuleInfoMap {
|
||||
&self.global.module_infos
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "artifact-graph"))]
|
||||
pub(crate) fn root_module_artifact_state(&self) -> &ModuleArtifactState {
|
||||
&self.global.root_module_artifacts
|
||||
}
|
||||
|
||||
pub fn current_default_units(&self) -> NumericType {
|
||||
NumericType::Default {
|
||||
len: self.length_unit(),
|
||||
@ -293,9 +329,9 @@ impl ExecState {
|
||||
error,
|
||||
self.errors().to_vec(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
self.global.artifacts.operations.clone(),
|
||||
self.global.root_module_artifacts.operations.clone(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
self.global.artifacts.commands.clone(),
|
||||
Default::default(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
self.global.artifacts.graph.clone(),
|
||||
module_id_to_module_path,
|
||||
@ -310,8 +346,30 @@ impl ExecState {
|
||||
engine: &Arc<Box<dyn EngineManager>>,
|
||||
program: NodeRef<'_, crate::parsing::ast::types::Program>,
|
||||
) -> Result<(), KclError> {
|
||||
let new_commands = engine.take_artifact_commands().await;
|
||||
let mut new_commands = Vec::new();
|
||||
let mut new_exec_artifacts = IndexMap::new();
|
||||
for module in self.global.module_infos.values_mut() {
|
||||
match &mut module.repr {
|
||||
ModuleRepr::Kcl(_, Some((_, _, _, module_artifacts)))
|
||||
| ModuleRepr::Foreign(_, Some((_, module_artifacts))) => {
|
||||
new_commands.extend(module_artifacts.process_commands());
|
||||
new_exec_artifacts.extend(module_artifacts.artifacts.clone());
|
||||
}
|
||||
ModuleRepr::Root | ModuleRepr::Kcl(_, None) | ModuleRepr::Foreign(_, None) | ModuleRepr::Dummy => {}
|
||||
}
|
||||
}
|
||||
// Take from the module artifacts so that we don't try to process them
|
||||
// again next time due to execution caching.
|
||||
new_commands.extend(self.global.root_module_artifacts.process_commands());
|
||||
// Note: These will get re-processed, but since we're just adding them
|
||||
// to a map, it's fine.
|
||||
new_exec_artifacts.extend(self.global.root_module_artifacts.artifacts.clone());
|
||||
let new_responses = engine.take_responses().await;
|
||||
|
||||
// Move the artifacts into ExecState global to simplify cache
|
||||
// management.
|
||||
self.global.artifacts.artifacts.extend(new_exec_artifacts);
|
||||
|
||||
let initial_graph = self.global.artifacts.graph.clone();
|
||||
|
||||
// Build the artifact graph.
|
||||
@ -322,10 +380,6 @@ impl ExecState {
|
||||
&mut self.global.artifacts.artifacts,
|
||||
initial_graph,
|
||||
);
|
||||
// Move the artifact commands and responses into ExecState to
|
||||
// simplify cache management and error creation.
|
||||
self.global.artifacts.commands.extend(new_commands);
|
||||
self.global.artifacts.responses.extend(new_responses);
|
||||
|
||||
let artifact_graph = graph_result?;
|
||||
self.global.artifacts.graph = artifact_graph;
|
||||
@ -349,6 +403,7 @@ impl GlobalState {
|
||||
path_to_source_id: Default::default(),
|
||||
module_infos: Default::default(),
|
||||
artifacts: Default::default(),
|
||||
root_module_artifacts: Default::default(),
|
||||
mod_loader: Default::default(),
|
||||
errors: Default::default(),
|
||||
id_to_source: Default::default(),
|
||||
@ -381,11 +436,54 @@ impl GlobalState {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
impl ArtifactState {
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub fn cached_body_items(&self) -> usize {
|
||||
self.graph.item_count
|
||||
}
|
||||
|
||||
pub(crate) fn clear(&mut self) {
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
{
|
||||
self.artifacts.clear();
|
||||
self.graph.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleArtifactState {
|
||||
pub(crate) fn clear(&mut self) {
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
{
|
||||
self.artifacts.clear();
|
||||
self.unprocessed_commands.clear();
|
||||
self.commands.clear();
|
||||
self.operations.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "artifact-graph"))]
|
||||
pub(crate) fn extend(&mut self, _other: ModuleArtifactState) {}
|
||||
|
||||
/// When self is a cached state, extend it with new state.
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub(crate) fn extend(&mut self, other: ModuleArtifactState) {
|
||||
self.artifacts.extend(other.artifacts);
|
||||
self.unprocessed_commands.extend(other.unprocessed_commands);
|
||||
self.commands.extend(other.commands);
|
||||
self.operations.extend(other.operations);
|
||||
}
|
||||
|
||||
// Move unprocessed artifact commands so that we don't try to process them
|
||||
// again next time due to execution caching. Returns a clone of the
|
||||
// commands that were moved.
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub(crate) fn process_commands(&mut self) -> Vec<ArtifactCommand> {
|
||||
let unprocessed = std::mem::take(&mut self.unprocessed_commands);
|
||||
let new_module_commands = unprocessed.clone();
|
||||
self.commands.extend(unprocessed);
|
||||
new_module_commands
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleState {
|
||||
@ -403,6 +501,7 @@ impl ModuleState {
|
||||
default_angle_units: Default::default(),
|
||||
kcl_version: "0.1".to_owned(),
|
||||
},
|
||||
artifacts: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -104,6 +104,16 @@ impl TypedPath {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn strip_prefix(&self, base: impl AsRef<std::path::Path>) -> Result<Self, std::path::StripPrefixError> {
|
||||
self.0.strip_prefix(base).map(|p| TypedPath(p.to_path_buf()))
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn canonicalize(&self) -> Result<Self, std::io::Error> {
|
||||
self.0.canonicalize().map(|p| TypedPath(p.to_path_buf()))
|
||||
}
|
||||
|
||||
pub fn to_string_lossy(&self) -> String {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
|
||||
@ -84,16 +84,16 @@ impl RuntimeType {
|
||||
RuntimeType::Primitive(PrimitiveType::Face)
|
||||
}
|
||||
|
||||
pub fn tag() -> Self {
|
||||
RuntimeType::Primitive(PrimitiveType::Tag)
|
||||
}
|
||||
|
||||
pub fn tag_decl() -> Self {
|
||||
RuntimeType::Primitive(PrimitiveType::TagDecl)
|
||||
}
|
||||
|
||||
pub fn tag_identifier() -> Self {
|
||||
RuntimeType::Primitive(PrimitiveType::TagId)
|
||||
pub fn tagged_face() -> Self {
|
||||
RuntimeType::Primitive(PrimitiveType::TaggedFace)
|
||||
}
|
||||
|
||||
pub fn tagged_edge() -> Self {
|
||||
RuntimeType::Primitive(PrimitiveType::TaggedEdge)
|
||||
}
|
||||
|
||||
pub fn bool() -> Self {
|
||||
@ -196,7 +196,7 @@ impl RuntimeType {
|
||||
RuntimeType::Primitive(PrimitiveType::Number(ty))
|
||||
}
|
||||
AstPrimitiveType::Named { id } => Self::from_alias(&id.name, exec_state, source_range)?,
|
||||
AstPrimitiveType::Tag => RuntimeType::Primitive(PrimitiveType::Tag),
|
||||
AstPrimitiveType::TagDecl => RuntimeType::Primitive(PrimitiveType::TagDecl),
|
||||
AstPrimitiveType::ImportedGeometry => RuntimeType::Primitive(PrimitiveType::ImportedGeometry),
|
||||
AstPrimitiveType::Function(_) => RuntimeType::Primitive(PrimitiveType::Function),
|
||||
})
|
||||
@ -383,8 +383,8 @@ pub enum PrimitiveType {
|
||||
Number(NumericType),
|
||||
String,
|
||||
Boolean,
|
||||
Tag,
|
||||
TagId,
|
||||
TaggedEdge,
|
||||
TaggedFace,
|
||||
TagDecl,
|
||||
Sketch,
|
||||
Solid,
|
||||
@ -416,9 +416,9 @@ impl PrimitiveType {
|
||||
PrimitiveType::Axis3d => "3d axes".to_owned(),
|
||||
PrimitiveType::ImportedGeometry => "imported geometries".to_owned(),
|
||||
PrimitiveType::Function => "functions".to_owned(),
|
||||
PrimitiveType::Tag => "tags".to_owned(),
|
||||
PrimitiveType::TagDecl => "tag declarators".to_owned(),
|
||||
PrimitiveType::TagId => "tag identifiers".to_owned(),
|
||||
PrimitiveType::TaggedEdge => "tagged edges".to_owned(),
|
||||
PrimitiveType::TaggedFace => "tagged faces".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -426,7 +426,8 @@ impl PrimitiveType {
|
||||
match (self, other) {
|
||||
(_, PrimitiveType::Any) => true,
|
||||
(PrimitiveType::Number(n1), PrimitiveType::Number(n2)) => n1.subtype(n2),
|
||||
(PrimitiveType::TagId, PrimitiveType::Tag) | (PrimitiveType::TagDecl, PrimitiveType::Tag) => true,
|
||||
(PrimitiveType::TaggedEdge, PrimitiveType::TaggedFace)
|
||||
| (PrimitiveType::TaggedEdge, PrimitiveType::Edge) => true,
|
||||
(t1, t2) => t1 == t2,
|
||||
}
|
||||
}
|
||||
@ -438,13 +439,13 @@ impl fmt::Display for PrimitiveType {
|
||||
PrimitiveType::Any => write!(f, "any"),
|
||||
PrimitiveType::Number(NumericType::Known(unit)) => write!(f, "number({unit})"),
|
||||
PrimitiveType::Number(NumericType::Unknown) => write!(f, "number(unknown units)"),
|
||||
PrimitiveType::Number(NumericType::Default { .. }) => write!(f, "number(default units)"),
|
||||
PrimitiveType::Number(NumericType::Default { .. }) => write!(f, "number"),
|
||||
PrimitiveType::Number(NumericType::Any) => write!(f, "number(any units)"),
|
||||
PrimitiveType::String => write!(f, "string"),
|
||||
PrimitiveType::Boolean => write!(f, "bool"),
|
||||
PrimitiveType::Tag => write!(f, "tag"),
|
||||
PrimitiveType::TagDecl => write!(f, "tag declarator"),
|
||||
PrimitiveType::TagId => write!(f, "tag identifier"),
|
||||
PrimitiveType::TaggedEdge => write!(f, "tagged edge"),
|
||||
PrimitiveType::TaggedFace => write!(f, "tagged face"),
|
||||
PrimitiveType::Sketch => write!(f, "Sketch"),
|
||||
PrimitiveType::Solid => write!(f, "Solid"),
|
||||
PrimitiveType::Plane => write!(f, "Plane"),
|
||||
@ -453,8 +454,8 @@ impl fmt::Display for PrimitiveType {
|
||||
PrimitiveType::Axis2d => write!(f, "Axis2d"),
|
||||
PrimitiveType::Axis3d => write!(f, "Axis3d"),
|
||||
PrimitiveType::Helix => write!(f, "Helix"),
|
||||
PrimitiveType::ImportedGeometry => write!(f, "imported geometry"),
|
||||
PrimitiveType::Function => write!(f, "function"),
|
||||
PrimitiveType::ImportedGeometry => write!(f, "ImportedGeometry"),
|
||||
PrimitiveType::Function => write!(f, "fn"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -499,20 +500,6 @@ impl NumericType {
|
||||
NumericType::Known(UnitType::Angle(UnitAngle::Degrees))
|
||||
}
|
||||
|
||||
pub fn expect_default_length(&self) -> Self {
|
||||
match self {
|
||||
NumericType::Default { len, .. } => NumericType::Known(UnitType::Length(*len)),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_default_angle(&self) -> Self {
|
||||
match self {
|
||||
NumericType::Default { angle, .. } => NumericType::Known(UnitType::Angle(*angle)),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Combine two types when we expect them to be equal, erring on the side of less coercion. To be
|
||||
/// precise, only adjusting one number or the other when they are of known types.
|
||||
///
|
||||
@ -554,15 +541,10 @@ impl NumericType {
|
||||
(at, Any) => (a.n, b.n, at),
|
||||
(Any, bt) => (a.n, b.n, bt),
|
||||
|
||||
(Default { .. }, Default { .. }) | (_, Unknown) | (Unknown, _) => (a.n, b.n, Unknown),
|
||||
|
||||
// Known types and compatible, but needs adjustment.
|
||||
(t @ Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => (a.n, l2.adjust_to(b.n, l1).0, t),
|
||||
(t @ Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => (a.n, a2.adjust_to(b.n, a1).0, t),
|
||||
|
||||
// Known but incompatible.
|
||||
(Known(_), Known(_)) => (a.n, b.n, Unknown),
|
||||
|
||||
// Known and unknown => we assume the known one, possibly with adjustment
|
||||
(Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
|
||||
(a.n, b.n, Known(UnitType::Count))
|
||||
@ -570,9 +552,12 @@ impl NumericType {
|
||||
|
||||
(t @ Known(UnitType::Length(l1)), Default { len: l2, .. }) => (a.n, l2.adjust_to(b.n, l1).0, t),
|
||||
(Default { len: l1, .. }, t @ Known(UnitType::Length(l2))) => (l1.adjust_to(a.n, l2).0, b.n, t),
|
||||
|
||||
(t @ Known(UnitType::Angle(a1)), Default { angle: a2, .. }) => (a.n, a2.adjust_to(b.n, a1).0, t),
|
||||
(Default { angle: a1, .. }, t @ Known(UnitType::Angle(a2))) => (a1.adjust_to(a.n, a2).0, b.n, t),
|
||||
|
||||
(Known(_), Known(_)) | (Default { .. }, Default { .. }) | (_, Unknown) | (Unknown, _) => {
|
||||
(a.n, b.n, Unknown)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -647,6 +632,20 @@ impl NumericType {
|
||||
}
|
||||
}
|
||||
|
||||
/// Combine two types for modulo-like operations.
|
||||
pub fn combine_mod(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
|
||||
use NumericType::*;
|
||||
match (a.ty, b.ty) {
|
||||
(at @ Default { .. }, bt @ Default { .. }) if at == bt => (a.n, b.n, at),
|
||||
(at, bt) if at == bt => (a.n, b.n, at),
|
||||
(Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
|
||||
(at, Known(UnitType::Count) | Any) => (a.n, b.n, at),
|
||||
(at @ Known(_), Default { .. }) => (a.n, b.n, at),
|
||||
(Known(UnitType::Count), _) => (a.n, b.n, Known(UnitType::Count)),
|
||||
_ => (a.n, b.n, Unknown),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_parsed(suffix: NumericSuffix, settings: &super::MetaSettings) -> Self {
|
||||
match suffix {
|
||||
NumericSuffix::None => NumericType::Default {
|
||||
@ -851,7 +850,7 @@ impl std::fmt::Display for UnitType {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO called UnitLen so as not to clash with UnitLength in settings)
|
||||
// TODO called UnitLen so as not to clash with UnitLength in settings.
|
||||
/// A unit of length.
|
||||
#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
|
||||
#[ts(export)]
|
||||
@ -1209,6 +1208,17 @@ impl KclValue {
|
||||
KclValue::TagIdentifier { .. } => Ok(self.clone()),
|
||||
_ => Err(self.into()),
|
||||
},
|
||||
PrimitiveType::TaggedEdge => match self {
|
||||
KclValue::TagIdentifier { .. } => Ok(self.clone()),
|
||||
_ => Err(self.into()),
|
||||
},
|
||||
PrimitiveType::TaggedFace => match self {
|
||||
KclValue::TagIdentifier { .. } => Ok(self.clone()),
|
||||
s @ KclValue::String { value, .. } if ["start", "end", "START", "END"].contains(&&**value) => {
|
||||
Ok(s.clone())
|
||||
}
|
||||
_ => Err(self.into()),
|
||||
},
|
||||
PrimitiveType::Axis2d => match self {
|
||||
KclValue::Object { value: values, meta } => {
|
||||
if values
|
||||
@ -1297,23 +1307,10 @@ impl KclValue {
|
||||
KclValue::Function { .. } => Ok(self.clone()),
|
||||
_ => Err(self.into()),
|
||||
},
|
||||
PrimitiveType::TagId => match self {
|
||||
KclValue::TagIdentifier { .. } => Ok(self.clone()),
|
||||
_ => Err(self.into()),
|
||||
},
|
||||
PrimitiveType::TagDecl => match self {
|
||||
KclValue::TagDeclarator { .. } => Ok(self.clone()),
|
||||
_ => Err(self.into()),
|
||||
},
|
||||
PrimitiveType::Tag => match self {
|
||||
KclValue::TagDeclarator { .. } | KclValue::TagIdentifier { .. } | KclValue::Uuid { .. } => {
|
||||
Ok(self.clone())
|
||||
}
|
||||
s @ KclValue::String { value, .. } if ["start", "end", "START", "END"].contains(&&**value) => {
|
||||
Ok(s.clone())
|
||||
}
|
||||
_ => Err(self.into()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -1503,13 +1500,30 @@ impl KclValue {
|
||||
KclValue::HomArray { ty, value, .. } => {
|
||||
Some(RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(value.len())))
|
||||
}
|
||||
KclValue::TagIdentifier(_) => Some(RuntimeType::Primitive(PrimitiveType::TagId)),
|
||||
KclValue::TagIdentifier(_) => Some(RuntimeType::Primitive(PrimitiveType::TaggedEdge)),
|
||||
KclValue::TagDeclarator(_) => Some(RuntimeType::Primitive(PrimitiveType::TagDecl)),
|
||||
KclValue::Uuid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Tag)),
|
||||
KclValue::Uuid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Edge)),
|
||||
KclValue::Function { .. } => Some(RuntimeType::Primitive(PrimitiveType::Function)),
|
||||
KclValue::Module { .. } | KclValue::KclNone { .. } | KclValue::Type { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn principal_type_string(&self) -> String {
|
||||
if let Some(ty) = self.principal_type() {
|
||||
return format!("`{ty}`");
|
||||
}
|
||||
|
||||
match self {
|
||||
KclValue::Module { .. } => "module",
|
||||
KclValue::KclNone { .. } => "none",
|
||||
KclValue::Type { .. } => "type",
|
||||
_ => {
|
||||
debug_assert!(false);
|
||||
"<unexpected type>"
|
||||
}
|
||||
}
|
||||
.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -2342,10 +2356,10 @@ d = cos(30)
|
||||
let result = parse_execute(program).await.unwrap();
|
||||
assert!(result.exec_state.errors().is_empty());
|
||||
|
||||
assert_value_and_type("a", &result, 1.0, NumericType::count());
|
||||
assert_value_and_type("a", &result, 1.0, NumericType::default());
|
||||
assert_value_and_type("b", &result, 3.0, NumericType::default());
|
||||
assert_value_and_type("c", &result, 1.0, NumericType::count());
|
||||
assert_value_and_type("d", &result, 1.0, NumericType::count());
|
||||
assert_value_and_type("c", &result, 1.0, NumericType::default());
|
||||
assert_value_and_type("d", &result, 1.0, NumericType::default());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
|
||||
@ -109,12 +109,12 @@ pub use unparser::{recast_dir, walk_dir};
|
||||
// Rather than make executor public and make lots of it pub(crate), just re-export into a new module.
|
||||
// Ideally we wouldn't export these things at all, they should only be used for testing.
|
||||
pub mod exec {
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub use crate::execution::ArtifactCommand;
|
||||
pub use crate::execution::{
|
||||
types::{NumericType, UnitAngle, UnitLen, UnitType},
|
||||
DefaultPlanes, IdGenerator, KclValue, PlaneType, Sketch,
|
||||
};
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub use crate::execution::{ArtifactCommand, Operation};
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
||||
@ -47,7 +47,7 @@ impl Tag {
|
||||
match self {
|
||||
Tag::Deprecated => Some(vec![DiagnosticTag::DEPRECATED]),
|
||||
Tag::Unnecessary => Some(vec![DiagnosticTag::UNNECESSARY]),
|
||||
Tag::None => None,
|
||||
Tag::UnknownNumericUnits | Tag::None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -950,7 +950,7 @@ startSketchOn(XY)
|
||||
|
||||
match hover.unwrap().contents {
|
||||
tower_lsp::lsp_types::HoverContents::Markup(tower_lsp::lsp_types::MarkupContent { value, .. }) => {
|
||||
assert!(value.contains("foo: number(default units) = 42"));
|
||||
assert!(value.contains("foo: number = 42"));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
@ -3900,7 +3900,7 @@ startSketchOn(XY)
|
||||
|
||||
match hover.unwrap().contents {
|
||||
tower_lsp::lsp_types::HoverContents::Markup(tower_lsp::lsp_types::MarkupContent { value, .. }) => {
|
||||
assert!(value.contains("foo: number(default units) = 42"));
|
||||
assert!(value.contains("foo: number = 42"));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
exec::KclValue,
|
||||
execution::{typed_path::TypedPath, EnvironmentRef, PreImportedGeometry},
|
||||
execution::{typed_path::TypedPath, EnvironmentRef, ModuleArtifactState, PreImportedGeometry},
|
||||
fs::{FileManager, FileSystem},
|
||||
parsing::ast::types::{ImportPath, Node, Program},
|
||||
source_range::SourceRange,
|
||||
@ -131,8 +131,11 @@ impl ModuleInfo {
|
||||
pub enum ModuleRepr {
|
||||
Root,
|
||||
// AST, memory, exported names
|
||||
Kcl(Node<Program>, Option<(Option<KclValue>, EnvironmentRef, Vec<String>)>),
|
||||
Foreign(PreImportedGeometry, Option<KclValue>),
|
||||
Kcl(
|
||||
Node<Program>,
|
||||
Option<(Option<KclValue>, EnvironmentRef, Vec<String>, ModuleArtifactState)>,
|
||||
),
|
||||
Foreign(PreImportedGeometry, Option<(Option<KclValue>, ModuleArtifactState)>),
|
||||
Dummy,
|
||||
}
|
||||
|
||||
|
||||
@ -223,7 +223,7 @@ impl PrimitiveType {
|
||||
PrimitiveType::String => hasher.update(b"string"),
|
||||
PrimitiveType::Number(suffix) => hasher.update(suffix.digestable_id()),
|
||||
PrimitiveType::Boolean => hasher.update(b"bool"),
|
||||
PrimitiveType::Tag => hasher.update(b"tag"),
|
||||
PrimitiveType::TagDecl => hasher.update(b"TagDecl"),
|
||||
PrimitiveType::ImportedGeometry => hasher.update(b"ImportedGeometry"),
|
||||
PrimitiveType::Function(f) => hasher.update(f.compute_digest()),
|
||||
}
|
||||
|
||||
@ -3005,6 +3005,8 @@ impl BinaryOperator {
|
||||
}
|
||||
}
|
||||
|
||||
/// The operator associativity of the operator (as in the parsing sense, not the mathematical sense of associativity).
|
||||
///
|
||||
/// Follow JS definitions of each operator.
|
||||
/// Taken from <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_precedence#table>
|
||||
pub fn associativity(&self) -> Associativity {
|
||||
@ -3015,6 +3017,12 @@ impl BinaryOperator {
|
||||
Self::And | Self::Or => Associativity::Left,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether an operator is mathematically associative. If it is, then the operator associativity (given by the
|
||||
/// `associativity` method) is mostly irrelevant.
|
||||
pub fn associative(&self) -> bool {
|
||||
matches!(self, Self::Add | Self::Mul | Self::And | Self::Or)
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
@ -3151,8 +3159,8 @@ pub enum PrimitiveType {
|
||||
/// A boolean type.
|
||||
#[serde(rename = "bool")]
|
||||
Boolean,
|
||||
/// A tag.
|
||||
Tag,
|
||||
/// A tag declaration.
|
||||
TagDecl,
|
||||
/// Imported from other CAD system.
|
||||
ImportedGeometry,
|
||||
/// `fn`, type of functions.
|
||||
@ -3167,13 +3175,26 @@ impl PrimitiveType {
|
||||
("any", None) => Some(PrimitiveType::Any),
|
||||
("string", None) => Some(PrimitiveType::String),
|
||||
("bool", None) => Some(PrimitiveType::Boolean),
|
||||
("tag", None) => Some(PrimitiveType::Tag),
|
||||
("TagDecl", None) => Some(PrimitiveType::TagDecl),
|
||||
("number", None) => Some(PrimitiveType::Number(NumericSuffix::None)),
|
||||
("number", Some(s)) => Some(PrimitiveType::Number(s)),
|
||||
("ImportedGeometry", None) => Some(PrimitiveType::ImportedGeometry),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn display_multiple(&self) -> String {
|
||||
match self {
|
||||
PrimitiveType::Any => "values".to_owned(),
|
||||
PrimitiveType::Number(_) => "numbers".to_owned(),
|
||||
PrimitiveType::String => "strings".to_owned(),
|
||||
PrimitiveType::Boolean => "bools".to_owned(),
|
||||
PrimitiveType::ImportedGeometry => "imported geometries".to_owned(),
|
||||
PrimitiveType::Function(_) => "functions".to_owned(),
|
||||
PrimitiveType::Named { id } => format!("`{}`s", id.name),
|
||||
PrimitiveType::TagDecl => "tag declarations".to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PrimitiveType {
|
||||
@ -3189,7 +3210,7 @@ impl fmt::Display for PrimitiveType {
|
||||
}
|
||||
PrimitiveType::String => write!(f, "string"),
|
||||
PrimitiveType::Boolean => write!(f, "bool"),
|
||||
PrimitiveType::Tag => write!(f, "tag"),
|
||||
PrimitiveType::TagDecl => write!(f, "TagDecl"),
|
||||
PrimitiveType::ImportedGeometry => write!(f, "ImportedGeometry"),
|
||||
PrimitiveType::Function(t) => {
|
||||
write!(f, "fn")?;
|
||||
@ -3264,6 +3285,53 @@ pub enum Type {
|
||||
},
|
||||
}
|
||||
|
||||
impl Type {
|
||||
pub fn human_friendly_type(&self) -> String {
|
||||
match self {
|
||||
Type::Primitive(ty) => format!("a value with type `{ty}`"),
|
||||
Type::Array {
|
||||
ty,
|
||||
len: ArrayLen::None | ArrayLen::Minimum(0),
|
||||
} => {
|
||||
format!("an array of {}", ty.display_multiple())
|
||||
}
|
||||
Type::Array {
|
||||
ty,
|
||||
len: ArrayLen::Minimum(1),
|
||||
} => format!("one or more {}", ty.display_multiple()),
|
||||
Type::Array {
|
||||
ty,
|
||||
len: ArrayLen::Minimum(n),
|
||||
} => {
|
||||
format!("an array of {n} or more {}", ty.display_multiple())
|
||||
}
|
||||
Type::Array {
|
||||
ty,
|
||||
len: ArrayLen::Known(n),
|
||||
} => format!("an array of {n} {}", ty.display_multiple()),
|
||||
Type::Union { tys } => tys
|
||||
.iter()
|
||||
.map(|t| t.human_friendly_type())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" or "),
|
||||
Type::Object { .. } => format!("an object with fields `{}`", self),
|
||||
}
|
||||
}
|
||||
|
||||
fn display_multiple(&self) -> String {
|
||||
match self {
|
||||
Type::Primitive(ty) => ty.display_multiple(),
|
||||
Type::Array { .. } => "arrays".to_owned(),
|
||||
Type::Union { tys } => tys
|
||||
.iter()
|
||||
.map(|t| t.display_multiple())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" or "),
|
||||
Type::Object { .. } => format!("objects with fields `{self}`"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Type {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
|
||||
@ -4,8 +4,6 @@ excerpt: "{{{description}}}"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
# {{title}}
|
||||
|
||||
{{{description}}}
|
||||
|
||||
## {{config_type}} Structure
|
||||
@ -64,4 +62,4 @@ This setting has further nested options. See the schema for full details.
|
||||
|
||||
```toml
|
||||
{{{example}}}
|
||||
```
|
||||
```
|
||||
|
||||
@ -3,13 +3,17 @@ use std::{
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use insta::rounded_redaction;
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use crate::{errors::KclError, ModuleId};
|
||||
use crate::{
|
||||
errors::KclError,
|
||||
execution::{EnvironmentRef, ModuleArtifactState},
|
||||
ExecOutcome, ExecState, ExecutorContext, ModuleId,
|
||||
};
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
use crate::{
|
||||
exec::ArtifactCommand,
|
||||
execution::{ArtifactGraph, Operation},
|
||||
execution::ArtifactGraph,
|
||||
modules::{ModulePath, ModuleRepr},
|
||||
};
|
||||
|
||||
mod kcl_samples;
|
||||
@ -19,8 +23,7 @@ mod kcl_samples;
|
||||
struct Test {
|
||||
/// The name of the test.
|
||||
name: String,
|
||||
/// The name of the KCL file that's the entry point, e.g. "main.kcl", in the
|
||||
/// `input_dir`.
|
||||
/// The KCL file that's the entry point, e.g. "main.kcl", in the `input_dir`.
|
||||
entry_point: PathBuf,
|
||||
/// Input KCL files are in this directory.
|
||||
input_dir: PathBuf,
|
||||
@ -34,6 +37,9 @@ struct Test {
|
||||
|
||||
pub(crate) const RENDERED_MODEL_NAME: &str = "rendered_model.png";
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
const REPO_ROOT: &str = "../..";
|
||||
|
||||
impl Test {
|
||||
fn new(name: &str) -> Self {
|
||||
Self {
|
||||
@ -52,6 +58,75 @@ impl Test {
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecState {
|
||||
/// Same as [`Self::into_exec_outcome`], but also returns the module state.
|
||||
async fn into_test_exec_outcome(
|
||||
self,
|
||||
main_ref: EnvironmentRef,
|
||||
ctx: &ExecutorContext,
|
||||
project_directory: &Path,
|
||||
) -> (ExecOutcome, IndexMap<String, ModuleArtifactState>) {
|
||||
let module_state = self.to_module_state(project_directory);
|
||||
let outcome = self.into_exec_outcome(main_ref, ctx).await;
|
||||
(outcome, module_state)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "artifact-graph"))]
|
||||
fn to_module_state(&self, _project_directory: &Path) -> IndexMap<String, ModuleArtifactState> {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// The keys of the map are the module paths. Can't use `ModulePath` since
|
||||
/// it needs to be converted to a string to be a JSON object key. The paths
|
||||
/// need to be relative so that generating locally works in CI.
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
fn to_module_state(&self, _project_directory: &Path) -> IndexMap<String, ModuleArtifactState> {
|
||||
let project_directory = std::path::Path::new(REPO_ROOT)
|
||||
.canonicalize()
|
||||
.unwrap_or_else(|_| panic!("Failed to canonicalize project directory: {REPO_ROOT}"));
|
||||
let mut module_state = IndexMap::new();
|
||||
for info in self.modules().values() {
|
||||
let relative_path = relative_module_path(&info.path, &project_directory).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Failed to get relative module path for {:?} in {:?}; caused by {err:?}",
|
||||
&info.path, project_directory
|
||||
)
|
||||
});
|
||||
match &info.repr {
|
||||
ModuleRepr::Root => {
|
||||
module_state.insert(relative_path, self.root_module_artifact_state().clone());
|
||||
}
|
||||
ModuleRepr::Kcl(_, None) => {
|
||||
module_state.insert(relative_path, Default::default());
|
||||
}
|
||||
ModuleRepr::Kcl(_, Some((_, _, _, module_artifacts))) => {
|
||||
module_state.insert(relative_path, module_artifacts.clone());
|
||||
}
|
||||
ModuleRepr::Foreign(_, Some((_, module_artifacts))) => {
|
||||
module_state.insert(relative_path, module_artifacts.clone());
|
||||
}
|
||||
ModuleRepr::Foreign(_, None) | ModuleRepr::Dummy => {}
|
||||
}
|
||||
}
|
||||
module_state
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
fn relative_module_path(module_path: &ModulePath, abs_project_directory: &Path) -> Result<String, std::io::Error> {
|
||||
match module_path {
|
||||
ModulePath::Main => Ok("main".to_owned()),
|
||||
ModulePath::Local { value: path } => {
|
||||
let abs_path = path.canonicalize()?;
|
||||
abs_path
|
||||
.strip_prefix(abs_project_directory)
|
||||
.map(|p| p.to_string_lossy())
|
||||
.map_err(|_| std::io::Error::other(format!("Failed to strip prefix from module path {abs_path:?}")))
|
||||
}
|
||||
ModulePath::Std { value } => Ok(format!("std::{value}")),
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_snapshot<F, R>(test: &Test, operation: &str, f: F)
|
||||
where
|
||||
F: FnOnce() -> R,
|
||||
@ -181,34 +256,20 @@ async fn execute_test(test: &Test, render_to_png: bool, export_step: bool) {
|
||||
panic!("Step data was empty");
|
||||
}
|
||||
}
|
||||
let outcome = exec_state.to_exec_outcome(env_ref, &ctx).await;
|
||||
let (outcome, module_state) = exec_state.into_test_exec_outcome(env_ref, &ctx, &test.input_dir).await;
|
||||
|
||||
let mem_result = catch_unwind(AssertUnwindSafe(|| {
|
||||
assert_snapshot(test, "Variables in memory after executing", || {
|
||||
insta::assert_json_snapshot!("program_memory", outcome.variables, {
|
||||
".**.value" => rounded_redaction(3),
|
||||
".**[].value" => rounded_redaction(3),
|
||||
".**.from[]" => rounded_redaction(3),
|
||||
".**.to[]" => rounded_redaction(3),
|
||||
".**.center[]" => rounded_redaction(3),
|
||||
".**[].x[]" => rounded_redaction(3),
|
||||
".**[].y[]" => rounded_redaction(3),
|
||||
".**[].z[]" => rounded_redaction(3),
|
||||
".**.x" => rounded_redaction(3),
|
||||
".**.y" => rounded_redaction(3),
|
||||
".**.z" => rounded_redaction(3),
|
||||
".**.sourceRange" => Vec::new(),
|
||||
})
|
||||
})
|
||||
}));
|
||||
|
||||
#[cfg(not(feature = "artifact-graph"))]
|
||||
drop(module_state);
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
assert_common_snapshots(
|
||||
test,
|
||||
outcome.operations,
|
||||
outcome.artifact_commands,
|
||||
outcome.artifact_graph,
|
||||
);
|
||||
assert_artifact_snapshots(test, module_state, outcome.artifact_graph);
|
||||
mem_result.unwrap();
|
||||
}
|
||||
Err(e) => {
|
||||
@ -238,7 +299,13 @@ async fn execute_test(test: &Test, render_to_png: bool, export_step: bool) {
|
||||
}));
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
assert_common_snapshots(test, error.operations, error.artifact_commands, error.artifact_graph);
|
||||
{
|
||||
let module_state = e
|
||||
.exec_state
|
||||
.map(|e| e.to_module_state(&test.input_dir))
|
||||
.unwrap_or_default();
|
||||
assert_artifact_snapshots(test, module_state, error.artifact_graph);
|
||||
}
|
||||
err_result.unwrap();
|
||||
}
|
||||
e => {
|
||||
@ -252,56 +319,34 @@ async fn execute_test(test: &Test, render_to_png: bool, export_step: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Assert snapshots that should happen both when KCL execution succeeds and
|
||||
/// when it results in an error.
|
||||
/// Assert snapshots for artifacts that should happen both when KCL execution
|
||||
/// succeeds and when it results in an error.
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
fn assert_common_snapshots(
|
||||
fn assert_artifact_snapshots(
|
||||
test: &Test,
|
||||
operations: Vec<Operation>,
|
||||
artifact_commands: Vec<ArtifactCommand>,
|
||||
module_state: IndexMap<String, ModuleArtifactState>,
|
||||
artifact_graph: ArtifactGraph,
|
||||
) {
|
||||
let operations = {
|
||||
// Make the operations deterministic by sorting them by their module ID,
|
||||
// then by their range.
|
||||
let mut operations = operations.clone();
|
||||
operations.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
||||
operations
|
||||
};
|
||||
let artifact_commands = {
|
||||
// Due to our newfound concurrency, we're going to mess with the
|
||||
// artifact_commands a bit -- we're going to maintain the order,
|
||||
// but only for a given module ID. This means the artifact_commands
|
||||
// is no longer meaningful, but it is deterministic and will hopefully
|
||||
// catch meaningful changes in behavior.
|
||||
// We sort by the source range, like we do for the operations.
|
||||
|
||||
let mut artifact_commands = artifact_commands.clone();
|
||||
artifact_commands.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
||||
artifact_commands
|
||||
};
|
||||
|
||||
let module_operations = module_state
|
||||
.iter()
|
||||
.map(|(path, s)| (path, &s.operations))
|
||||
.collect::<IndexMap<_, _>>();
|
||||
let result1 = catch_unwind(AssertUnwindSafe(|| {
|
||||
assert_snapshot(test, "Operations executed", || {
|
||||
insta::assert_json_snapshot!("ops", operations, {
|
||||
"[].*.unlabeledArg.*.value.**[].from[]" => rounded_redaction(3),
|
||||
"[].*.unlabeledArg.*.value.**[].to[]" => rounded_redaction(3),
|
||||
"[].**.value.value" => rounded_redaction(3),
|
||||
"[].*.labeledArgs.*.value.**[].from[]" => rounded_redaction(3),
|
||||
"[].*.labeledArgs.*.value.**[].to[]" => rounded_redaction(3),
|
||||
insta::assert_json_snapshot!("ops", module_operations, {
|
||||
".**.sourceRange" => Vec::new(),
|
||||
".**.functionSourceRange" => Vec::new(),
|
||||
".**.moduleId" => 0,
|
||||
});
|
||||
})
|
||||
}));
|
||||
let module_commands = module_state
|
||||
.iter()
|
||||
.map(|(path, s)| (path, &s.commands))
|
||||
.collect::<IndexMap<_, _>>();
|
||||
let result2 = catch_unwind(AssertUnwindSafe(|| {
|
||||
assert_snapshot(test, "Artifact commands", || {
|
||||
insta::assert_json_snapshot!("artifact_commands", artifact_commands, {
|
||||
"[].command.**.value" => rounded_redaction(3),
|
||||
"[].command.**.x" => rounded_redaction(3),
|
||||
"[].command.**.y" => rounded_redaction(3),
|
||||
"[].command.**.z" => rounded_redaction(3),
|
||||
insta::assert_json_snapshot!("artifact_commands", module_commands, {
|
||||
".**.range" => Vec::new(),
|
||||
});
|
||||
})
|
||||
@ -314,22 +359,12 @@ fn assert_common_snapshots(
|
||||
let is_writing = matches!(std::env::var("ZOO_SIM_UPDATE").as_deref(), Ok("always"));
|
||||
if !test.skip_assert_artifact_graph || is_writing {
|
||||
assert_snapshot(test, "Artifact graph flowchart", || {
|
||||
let mut artifact_graph = artifact_graph.clone();
|
||||
// Sort the map by artifact where we can.
|
||||
artifact_graph.sort();
|
||||
|
||||
let flowchart = artifact_graph
|
||||
.to_mermaid_flowchart()
|
||||
.unwrap_or_else(|e| format!("Failed to convert artifact graph to flowchart: {e}"));
|
||||
// Change the snapshot suffix so that it is rendered as a Markdown file
|
||||
// in GitHub.
|
||||
// Ignore the cpu cooler for now because its being a little bitch.
|
||||
if test.name != "cpu-cooler"
|
||||
&& test.name != "subtract_regression08"
|
||||
&& test.name != "subtract_regression10"
|
||||
{
|
||||
insta::assert_binary_snapshot!("artifact_graph_flowchart.md", flowchart.as_bytes().to_owned());
|
||||
}
|
||||
insta::assert_binary_snapshot!("artifact_graph_flowchart.md", flowchart.as_bytes().to_owned());
|
||||
})
|
||||
}
|
||||
}));
|
||||
@ -402,8 +437,8 @@ mod any_type {
|
||||
super::execute(TEST_NAME, false).await
|
||||
}
|
||||
}
|
||||
mod error_with_point_shows_numeric_units {
|
||||
const TEST_NAME: &str = "error_with_point_shows_numeric_units";
|
||||
mod coerce_from_trig_to_point {
|
||||
const TEST_NAME: &str = "coerce_from_trig_to_point";
|
||||
|
||||
/// Test parsing KCL.
|
||||
#[test]
|
||||
@ -3546,3 +3581,27 @@ mod var_ref_in_own_def_decl {
|
||||
super::execute(TEST_NAME, true).await
|
||||
}
|
||||
}
|
||||
mod user_reported_union_2_bug {
|
||||
// TODO IF THIS TEST START PASSING, CLOSE THE FOLLOWING ISSUE
|
||||
// https://github.com/KittyCAD/modeling-app/issues/7310
|
||||
// and https://github.com/KittyCAD/engine/issues/3539
|
||||
const TEST_NAME: &str = "user_reported_union_2_bug";
|
||||
|
||||
/// Test parsing KCL.
|
||||
#[test]
|
||||
fn parse() {
|
||||
super::parse(TEST_NAME)
|
||||
}
|
||||
|
||||
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn unparse() {
|
||||
super::unparse(TEST_NAME).await
|
||||
}
|
||||
|
||||
/// Test that KCL is executed correctly.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn kcl_test_execute() {
|
||||
super::execute(TEST_NAME, false).await
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,17 +106,18 @@ async fn inner_appearance(
|
||||
a: 100.0,
|
||||
};
|
||||
|
||||
args.batch_modeling_cmd(
|
||||
exec_state.next_uuid(),
|
||||
ModelingCmd::from(mcmd::ObjectSetMaterialParamsPbr {
|
||||
object_id: solid_id,
|
||||
color,
|
||||
metalness: metalness.unwrap_or_default() as f32 / 100.0,
|
||||
roughness: roughness.unwrap_or_default() as f32 / 100.0,
|
||||
ambient_occlusion: 0.0,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
(&args).into(),
|
||||
ModelingCmd::from(mcmd::ObjectSetMaterialParamsPbr {
|
||||
object_id: solid_id,
|
||||
color,
|
||||
metalness: metalness.unwrap_or_default() as f32 / 100.0,
|
||||
roughness: roughness.unwrap_or_default() as f32 / 100.0,
|
||||
ambient_occlusion: 0.0,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Idk if we want to actually modify the memory for the colors, but I'm not right now since
|
||||
// I can't think of a use case for it.
|
||||
|
||||
@ -1,14 +1,10 @@
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
use anyhow::Result;
|
||||
use kcmc::{
|
||||
websocket::{ModelingCmdReq, OkWebSocketResponseData},
|
||||
ModelingCmd,
|
||||
};
|
||||
use kittycad_modeling_cmds as kcmc;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::fillet::EdgeReference;
|
||||
pub use crate::execution::fn_call::Args;
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
@ -28,8 +24,6 @@ use crate::{
|
||||
ModuleId,
|
||||
};
|
||||
|
||||
use super::fillet::EdgeReference;
|
||||
|
||||
const ERROR_STRING_SKETCH_TO_SOLID_HELPER: &str =
|
||||
"You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`";
|
||||
|
||||
@ -166,14 +160,18 @@ impl Args {
|
||||
None => msg_base,
|
||||
Some(sugg) => format!("{msg_base}. {sugg}"),
|
||||
};
|
||||
if message.contains("one or more Solids or imported geometry but it's actually of type Sketch") {
|
||||
if message.contains("one or more Solids or ImportedGeometry but it's actually of type Sketch") {
|
||||
message = format!("{message}. {ERROR_STRING_SKETCH_TO_SOLID_HELPER}");
|
||||
}
|
||||
KclError::new_semantic(KclErrorDetails::new(message, arg.source_ranges()))
|
||||
})?;
|
||||
|
||||
// TODO unnecessary cloning
|
||||
Ok(T::from_kcl_val(&arg).unwrap())
|
||||
T::from_kcl_val(&arg).ok_or_else(|| {
|
||||
KclError::new_internal(KclErrorDetails::new(
|
||||
format!("Mismatch between type coercion and value extraction (this isn't your fault).\nTo assist in bug-reporting, expected type: {ty:?}; actual value: {arg:?}"),
|
||||
vec![self.source_range],
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a labelled keyword arg, check it's an array, and return all items in the array
|
||||
@ -263,7 +261,7 @@ impl Args {
|
||||
Some(sugg) => format!("{msg_base}. {sugg}"),
|
||||
};
|
||||
|
||||
if message.contains("one or more Solids or imported geometry but it's actually of type Sketch") {
|
||||
if message.contains("one or more Solids or ImportedGeometry but it's actually of type Sketch") {
|
||||
message = format!("{message}. {ERROR_STRING_SKETCH_TO_SOLID_HELPER}");
|
||||
}
|
||||
KclError::new_semantic(KclErrorDetails::new(message, arg.source_ranges()))
|
||||
@ -277,36 +275,7 @@ impl Args {
|
||||
})
|
||||
}
|
||||
|
||||
// Add a modeling command to the batch but don't fire it right away.
|
||||
pub(crate) async fn batch_modeling_cmd(
|
||||
&self,
|
||||
id: uuid::Uuid,
|
||||
cmd: ModelingCmd,
|
||||
) -> Result<(), crate::errors::KclError> {
|
||||
self.ctx.engine.batch_modeling_cmd(id, self.source_range, &cmd).await
|
||||
}
|
||||
|
||||
// Add multiple modeling commands to the batch but don't fire them right away.
|
||||
pub(crate) async fn batch_modeling_cmds(&self, cmds: &[ModelingCmdReq]) -> Result<(), crate::errors::KclError> {
|
||||
self.ctx.engine.batch_modeling_cmds(self.source_range, cmds).await
|
||||
}
|
||||
|
||||
// Add a modeling commandSolid> to the batch that gets executed at the end of the file.
|
||||
// This is good for something like fillet or chamfer where the engine would
|
||||
// eat the path id if we executed it right away.
|
||||
pub(crate) async fn batch_end_cmd(&self, id: uuid::Uuid, cmd: ModelingCmd) -> Result<(), crate::errors::KclError> {
|
||||
self.ctx.engine.batch_end_cmd(id, self.source_range, &cmd).await
|
||||
}
|
||||
|
||||
/// Send the modeling cmd and wait for the response.
|
||||
pub(crate) async fn send_modeling_cmd(
|
||||
&self,
|
||||
id: uuid::Uuid,
|
||||
cmd: ModelingCmd,
|
||||
) -> Result<OkWebSocketResponseData, KclError> {
|
||||
self.ctx.engine.send_modeling_cmd(id, self.source_range, &cmd).await
|
||||
}
|
||||
|
||||
// TODO: Move this to the modeling module.
|
||||
fn get_tag_info_from_memory<'a, 'e>(
|
||||
&'a self,
|
||||
exec_state: &'e mut ExecState,
|
||||
@ -330,6 +299,7 @@ impl Args {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Move this to the modeling module.
|
||||
pub(crate) fn get_tag_engine_info<'a, 'e>(
|
||||
&'a self,
|
||||
exec_state: &'e mut ExecState,
|
||||
@ -345,6 +315,7 @@ impl Args {
|
||||
self.get_tag_info_from_memory(exec_state, tag)
|
||||
}
|
||||
|
||||
// TODO: Move this to the modeling module.
|
||||
fn get_tag_engine_info_check_surface<'a, 'e>(
|
||||
&'a self,
|
||||
exec_state: &'e mut ExecState,
|
||||
@ -362,63 +333,6 @@ impl Args {
|
||||
self.get_tag_info_from_memory(exec_state, tag)
|
||||
}
|
||||
|
||||
/// Flush just the fillets and chamfers for this specific SolidSet.
|
||||
#[allow(clippy::vec_box)]
|
||||
pub(crate) async fn flush_batch_for_solids(
|
||||
&self,
|
||||
exec_state: &mut ExecState,
|
||||
solids: &[Solid],
|
||||
) -> Result<(), KclError> {
|
||||
// Make sure we don't traverse sketches more than once.
|
||||
let mut traversed_sketches = Vec::new();
|
||||
|
||||
// Collect all the fillet/chamfer ids for the solids.
|
||||
let mut ids = Vec::new();
|
||||
for solid in solids {
|
||||
// We need to traverse the solids that share the same sketch.
|
||||
let sketch_id = solid.sketch.id;
|
||||
if !traversed_sketches.contains(&sketch_id) {
|
||||
// Find all the solids on the same shared sketch.
|
||||
ids.extend(
|
||||
exec_state
|
||||
.stack()
|
||||
.walk_call_stack()
|
||||
.filter(|v| matches!(v, KclValue::Solid { value } if value.sketch.id == sketch_id))
|
||||
.flat_map(|v| match v {
|
||||
KclValue::Solid { value } => value.get_all_edge_cut_ids(),
|
||||
_ => unreachable!(),
|
||||
}),
|
||||
);
|
||||
traversed_sketches.push(sketch_id);
|
||||
}
|
||||
|
||||
ids.extend(solid.get_all_edge_cut_ids());
|
||||
}
|
||||
|
||||
// We can return early if there are no fillets or chamfers.
|
||||
if ids.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// We want to move these fillets and chamfers from batch_end to batch so they get executed
|
||||
// before what ever we call next.
|
||||
for id in ids {
|
||||
// Pop it off the batch_end and add it to the batch.
|
||||
let Some(item) = self.ctx.engine.batch_end().write().await.shift_remove(&id) else {
|
||||
// It might be in the batch already.
|
||||
continue;
|
||||
};
|
||||
// Add it to the batch.
|
||||
self.ctx.engine.batch().write().await.push(item);
|
||||
}
|
||||
|
||||
// Run flush.
|
||||
// Yes, we do need to actually flush the batch here, or references will fail later.
|
||||
self.ctx.engine.flush_batch(false, self.source_range).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn make_kcl_val_from_point(&self, p: [f64; 2], ty: NumericType) -> Result<KclValue, KclError> {
|
||||
let meta = Metadata {
|
||||
source_range: self.source_range,
|
||||
@ -448,6 +362,7 @@ impl Args {
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: Move this to the modeling module.
|
||||
pub(crate) async fn get_adjacent_face_to_tag(
|
||||
&self,
|
||||
exec_state: &mut ExecState,
|
||||
@ -537,107 +452,12 @@ impl Args {
|
||||
}
|
||||
}
|
||||
|
||||
/// Types which impl this trait can be read out of the `Args` passed into a KCL function.
|
||||
pub trait FromArgs<'a>: Sized {
|
||||
/// Get this type from the args passed into a KCL function, at the given index in the argument list.
|
||||
fn from_args(args: &'a Args, index: usize) -> Result<Self, KclError>;
|
||||
}
|
||||
|
||||
/// Types which impl this trait can be extracted from a `KclValue`.
|
||||
pub trait FromKclValue<'a>: Sized {
|
||||
/// Try to convert a KclValue into this type.
|
||||
fn from_kcl_val(arg: &'a KclValue) -> Option<Self>;
|
||||
}
|
||||
|
||||
impl<'a, T> FromArgs<'a> for T
|
||||
where
|
||||
T: FromKclValue<'a> + Sized,
|
||||
{
|
||||
fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
|
||||
let Some(arg) = args.args.get(i) else {
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Expected an argument at index {i}"),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
};
|
||||
let Some(val) = T::from_kcl_val(&arg.value) else {
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"Argument at index {i} was supposed to be type {} but found {}",
|
||||
tynm::type_name::<T>(),
|
||||
arg.value.human_friendly_type(),
|
||||
),
|
||||
arg.source_ranges(),
|
||||
)));
|
||||
};
|
||||
Ok(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> FromArgs<'a> for Option<T>
|
||||
where
|
||||
T: FromKclValue<'a> + Sized,
|
||||
{
|
||||
fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
|
||||
let Some(arg) = args.args.get(i) else { return Ok(None) };
|
||||
if crate::parsing::ast::types::KclNone::from_kcl_val(&arg.value).is_some() {
|
||||
return Ok(None);
|
||||
}
|
||||
let Some(val) = T::from_kcl_val(&arg.value) else {
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"Argument at index {i} was supposed to be type Option<{}> but found {}",
|
||||
tynm::type_name::<T>(),
|
||||
arg.value.human_friendly_type()
|
||||
),
|
||||
arg.source_ranges(),
|
||||
)));
|
||||
};
|
||||
Ok(Some(val))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, A, B> FromArgs<'a> for (A, B)
|
||||
where
|
||||
A: FromArgs<'a>,
|
||||
B: FromArgs<'a>,
|
||||
{
|
||||
fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
|
||||
let a = A::from_args(args, i)?;
|
||||
let b = B::from_args(args, i + 1)?;
|
||||
Ok((a, b))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, A, B, C> FromArgs<'a> for (A, B, C)
|
||||
where
|
||||
A: FromArgs<'a>,
|
||||
B: FromArgs<'a>,
|
||||
C: FromArgs<'a>,
|
||||
{
|
||||
fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
|
||||
let a = A::from_args(args, i)?;
|
||||
let b = B::from_args(args, i + 1)?;
|
||||
let c = C::from_args(args, i + 2)?;
|
||||
Ok((a, b, c))
|
||||
}
|
||||
}
|
||||
impl<'a, A, B, C, D> FromArgs<'a> for (A, B, C, D)
|
||||
where
|
||||
A: FromArgs<'a>,
|
||||
B: FromArgs<'a>,
|
||||
C: FromArgs<'a>,
|
||||
D: FromArgs<'a>,
|
||||
{
|
||||
fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
|
||||
let a = A::from_args(args, i)?;
|
||||
let b = B::from_args(args, i + 1)?;
|
||||
let c = C::from_args(args, i + 2)?;
|
||||
let d = D::from_args(args, i + 3)?;
|
||||
Ok((a, b, c, d))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromKclValue<'a> for TagNode {
|
||||
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
||||
arg.get_tag_declarator().ok()
|
||||
|
||||
@ -7,7 +7,10 @@ use kittycad_modeling_cmds as kcmc;
|
||||
use super::args::TyF64;
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{types::RuntimeType, ChamferSurface, EdgeCut, ExecState, ExtrudeSurface, GeoMeta, KclValue, Solid},
|
||||
execution::{
|
||||
types::RuntimeType, ChamferSurface, EdgeCut, ExecState, ExtrudeSurface, GeoMeta, KclValue, ModelingCmdMeta,
|
||||
Solid,
|
||||
},
|
||||
parsing::ast::types::TagNode,
|
||||
std::{fillet::EdgeReference, Args},
|
||||
};
|
||||
@ -52,20 +55,21 @@ async fn inner_chamfer(
|
||||
};
|
||||
|
||||
let id = exec_state.next_uuid();
|
||||
args.batch_end_cmd(
|
||||
id,
|
||||
ModelingCmd::from(mcmd::Solid3dFilletEdge {
|
||||
edge_id: None,
|
||||
edge_ids: vec![edge_id],
|
||||
extra_face_ids: vec![],
|
||||
strategy: Default::default(),
|
||||
object_id: solid.id,
|
||||
radius: LengthUnit(length.to_mm()),
|
||||
tolerance: LengthUnit(DEFAULT_TOLERANCE), // We can let the user set this in the future.
|
||||
cut_type: CutType::Chamfer,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_end_cmd(
|
||||
ModelingCmdMeta::from_args_id(&args, id),
|
||||
ModelingCmd::from(mcmd::Solid3dFilletEdge {
|
||||
edge_id: None,
|
||||
edge_ids: vec![edge_id],
|
||||
extra_face_ids: vec![],
|
||||
strategy: Default::default(),
|
||||
object_id: solid.id,
|
||||
radius: LengthUnit(length.to_mm()),
|
||||
tolerance: LengthUnit(DEFAULT_TOLERANCE), // We can let the user set this in the future.
|
||||
cut_type: CutType::Chamfer,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
solid.edge_cuts.push(EdgeCut::Chamfer {
|
||||
id,
|
||||
|
||||
@ -16,7 +16,7 @@ use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{
|
||||
types::{NumericType, PrimitiveType, RuntimeType},
|
||||
ExecState, GeometryWithImportedGeometry, KclValue, Sketch, Solid,
|
||||
ExecState, GeometryWithImportedGeometry, KclValue, ModelingCmdMeta, Sketch, Solid,
|
||||
},
|
||||
parsing::ast::types::TagNode,
|
||||
std::{extrude::NamedCapTags, Args},
|
||||
@ -64,7 +64,9 @@ async fn inner_clone(
|
||||
}
|
||||
GeometryWithImportedGeometry::Solid(solid) => {
|
||||
// We flush before the clone so all the shit exists.
|
||||
args.flush_batch_for_solids(exec_state, &[solid.clone()]).await?;
|
||||
exec_state
|
||||
.flush_batch_for_solids((&args).into(), &[solid.clone()])
|
||||
.await?;
|
||||
|
||||
let mut new_solid = solid.clone();
|
||||
new_solid.id = new_id;
|
||||
@ -78,7 +80,11 @@ async fn inner_clone(
|
||||
return Ok(new_geometry);
|
||||
}
|
||||
|
||||
args.batch_modeling_cmd(new_id, ModelingCmd::from(mcmd::EntityClone { entity_id: old_id }))
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
ModelingCmdMeta::from_args_id(&args, new_id),
|
||||
ModelingCmd::from(mcmd::EntityClone { entity_id: old_id }),
|
||||
)
|
||||
.await?;
|
||||
|
||||
fix_tags_and_references(&mut new_geometry, old_id, exec_state, &args)
|
||||
@ -169,9 +175,9 @@ async fn get_old_new_child_map(
|
||||
args: &Args,
|
||||
) -> Result<HashMap<uuid::Uuid, uuid::Uuid>> {
|
||||
// Get the old geometries entity ids.
|
||||
let response = args
|
||||
let response = exec_state
|
||||
.send_modeling_cmd(
|
||||
exec_state.next_uuid(),
|
||||
args.into(),
|
||||
ModelingCmd::from(mcmd::EntityGetAllChildUuids {
|
||||
entity_id: old_geometry_id,
|
||||
}),
|
||||
@ -188,9 +194,9 @@ async fn get_old_new_child_map(
|
||||
};
|
||||
|
||||
// Get the new geometries entity ids.
|
||||
let response = args
|
||||
let response = exec_state
|
||||
.send_modeling_cmd(
|
||||
exec_state.next_uuid(),
|
||||
args.into(),
|
||||
ModelingCmd::from(mcmd::EntityGetAllChildUuids {
|
||||
entity_id: new_geometry_id,
|
||||
}),
|
||||
|
||||
@ -12,7 +12,7 @@ use kittycad_modeling_cmds::{
|
||||
use super::{args::TyF64, DEFAULT_TOLERANCE};
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{types::RuntimeType, ExecState, KclValue, Solid},
|
||||
execution::{types::RuntimeType, ExecState, KclValue, ModelingCmdMeta, Solid},
|
||||
std::{patterns::GeometryTrait, Args},
|
||||
};
|
||||
|
||||
@ -50,11 +50,11 @@ pub(crate) async fn inner_union(
|
||||
}
|
||||
|
||||
// Flush the fillets for the solids.
|
||||
args.flush_batch_for_solids(exec_state, &solids).await?;
|
||||
exec_state.flush_batch_for_solids((&args).into(), &solids).await?;
|
||||
|
||||
let result = args
|
||||
let result = exec_state
|
||||
.send_modeling_cmd(
|
||||
solid_out_id,
|
||||
ModelingCmdMeta::from_args_id(&args, solid_out_id),
|
||||
ModelingCmd::from(mcmd::BooleanUnion {
|
||||
solid_ids: solids.iter().map(|s| s.id).collect(),
|
||||
tolerance: LengthUnit(tolerance.map(|t| t.n).unwrap_or(DEFAULT_TOLERANCE)),
|
||||
@ -115,11 +115,11 @@ pub(crate) async fn inner_intersect(
|
||||
}
|
||||
|
||||
// Flush the fillets for the solids.
|
||||
args.flush_batch_for_solids(exec_state, &solids).await?;
|
||||
exec_state.flush_batch_for_solids((&args).into(), &solids).await?;
|
||||
|
||||
let result = args
|
||||
let result = exec_state
|
||||
.send_modeling_cmd(
|
||||
solid_out_id,
|
||||
ModelingCmdMeta::from_args_id(&args, solid_out_id),
|
||||
ModelingCmd::from(mcmd::BooleanIntersection {
|
||||
solid_ids: solids.iter().map(|s| s.id).collect(),
|
||||
tolerance: LengthUnit(tolerance.map(|t| t.n).unwrap_or(DEFAULT_TOLERANCE)),
|
||||
@ -176,11 +176,13 @@ pub(crate) async fn inner_subtract(
|
||||
|
||||
// Flush the fillets for the solids and the tools.
|
||||
let combined_solids = solids.iter().chain(tools.iter()).cloned().collect::<Vec<Solid>>();
|
||||
args.flush_batch_for_solids(exec_state, &combined_solids).await?;
|
||||
exec_state
|
||||
.flush_batch_for_solids((&args).into(), &combined_solids)
|
||||
.await?;
|
||||
|
||||
let result = args
|
||||
let result = exec_state
|
||||
.send_modeling_cmd(
|
||||
solid_out_id,
|
||||
ModelingCmdMeta::from_args_id(&args, solid_out_id),
|
||||
ModelingCmd::from(mcmd::BooleanSubtract {
|
||||
target_ids: solids.iter().map(|s| s.id).collect(),
|
||||
tool_ids: tools.iter().map(|s| s.id).collect(),
|
||||
|
||||
@ -9,14 +9,15 @@ use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{
|
||||
types::{ArrayLen, RuntimeType},
|
||||
ExecState, ExtrudeSurface, KclValue, TagIdentifier,
|
||||
ExecState, ExtrudeSurface, KclValue, ModelingCmdMeta, TagIdentifier,
|
||||
},
|
||||
std::Args,
|
||||
std::{sketch::FaceTag, Args},
|
||||
SourceRange,
|
||||
};
|
||||
|
||||
/// Get the opposite edge to the edge given.
|
||||
pub async fn get_opposite_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let input_edge = args.get_unlabeled_kw_arg("edge", &RuntimeType::tag_identifier(), exec_state)?;
|
||||
let input_edge = args.get_unlabeled_kw_arg("edge", &RuntimeType::tagged_edge(), exec_state)?;
|
||||
|
||||
let edge = inner_get_opposite_edge(input_edge, exec_state, args.clone()).await?;
|
||||
Ok(KclValue::Uuid {
|
||||
@ -35,15 +36,16 @@ async fn inner_get_opposite_edge(
|
||||
}
|
||||
let face_id = args.get_adjacent_face_to_tag(exec_state, &edge, false).await?;
|
||||
|
||||
let id = exec_state.next_uuid();
|
||||
let tagged_path = args.get_tag_engine_info(exec_state, &edge)?;
|
||||
let tagged_path_id = tagged_path.id;
|
||||
let sketch_id = tagged_path.sketch;
|
||||
|
||||
let resp = args
|
||||
let resp = exec_state
|
||||
.send_modeling_cmd(
|
||||
id,
|
||||
(&args).into(),
|
||||
ModelingCmd::from(mcmd::Solid3dGetOppositeEdge {
|
||||
edge_id: tagged_path.id,
|
||||
object_id: tagged_path.sketch,
|
||||
edge_id: tagged_path_id,
|
||||
object_id: sketch_id,
|
||||
face_id,
|
||||
}),
|
||||
)
|
||||
@ -63,7 +65,7 @@ async fn inner_get_opposite_edge(
|
||||
|
||||
/// Get the next adjacent edge to the edge given.
|
||||
pub async fn get_next_adjacent_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let input_edge = args.get_unlabeled_kw_arg("edge", &RuntimeType::tag_identifier(), exec_state)?;
|
||||
let input_edge = args.get_unlabeled_kw_arg("edge", &RuntimeType::tagged_edge(), exec_state)?;
|
||||
|
||||
let edge = inner_get_next_adjacent_edge(input_edge, exec_state, args.clone()).await?;
|
||||
Ok(KclValue::Uuid {
|
||||
@ -82,15 +84,16 @@ async fn inner_get_next_adjacent_edge(
|
||||
}
|
||||
let face_id = args.get_adjacent_face_to_tag(exec_state, &edge, false).await?;
|
||||
|
||||
let id = exec_state.next_uuid();
|
||||
let tagged_path = args.get_tag_engine_info(exec_state, &edge)?;
|
||||
let tagged_path_id = tagged_path.id;
|
||||
let sketch_id = tagged_path.sketch;
|
||||
|
||||
let resp = args
|
||||
let resp = exec_state
|
||||
.send_modeling_cmd(
|
||||
id,
|
||||
(&args).into(),
|
||||
ModelingCmd::from(mcmd::Solid3dGetNextAdjacentEdge {
|
||||
edge_id: tagged_path.id,
|
||||
object_id: tagged_path.sketch,
|
||||
edge_id: tagged_path_id,
|
||||
object_id: sketch_id,
|
||||
face_id,
|
||||
}),
|
||||
)
|
||||
@ -119,7 +122,7 @@ async fn inner_get_next_adjacent_edge(
|
||||
|
||||
/// Get the previous adjacent edge to the edge given.
|
||||
pub async fn get_previous_adjacent_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let input_edge = args.get_unlabeled_kw_arg("edge", &RuntimeType::tag_identifier(), exec_state)?;
|
||||
let input_edge = args.get_unlabeled_kw_arg("edge", &RuntimeType::tagged_edge(), exec_state)?;
|
||||
|
||||
let edge = inner_get_previous_adjacent_edge(input_edge, exec_state, args.clone()).await?;
|
||||
Ok(KclValue::Uuid {
|
||||
@ -138,15 +141,16 @@ async fn inner_get_previous_adjacent_edge(
|
||||
}
|
||||
let face_id = args.get_adjacent_face_to_tag(exec_state, &edge, false).await?;
|
||||
|
||||
let id = exec_state.next_uuid();
|
||||
let tagged_path = args.get_tag_engine_info(exec_state, &edge)?;
|
||||
let tagged_path_id = tagged_path.id;
|
||||
let sketch_id = tagged_path.sketch;
|
||||
|
||||
let resp = args
|
||||
let resp = exec_state
|
||||
.send_modeling_cmd(
|
||||
id,
|
||||
(&args).into(),
|
||||
ModelingCmd::from(mcmd::Solid3dGetPrevAdjacentEdge {
|
||||
edge_id: tagged_path.id,
|
||||
object_id: tagged_path.sketch,
|
||||
edge_id: tagged_path_id,
|
||||
object_id: sketch_id,
|
||||
face_id,
|
||||
}),
|
||||
)
|
||||
@ -174,13 +178,33 @@ async fn inner_get_previous_adjacent_edge(
|
||||
|
||||
/// Get the shared edge between two faces.
|
||||
pub async fn get_common_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let faces: Vec<TagIdentifier> = args.get_kw_arg(
|
||||
let mut faces: Vec<FaceTag> = args.get_kw_arg(
|
||||
"faces",
|
||||
&RuntimeType::Array(Box::new(RuntimeType::tag_identifier()), ArrayLen::Known(2)),
|
||||
&RuntimeType::Array(Box::new(RuntimeType::tagged_face()), ArrayLen::Known(2)),
|
||||
exec_state,
|
||||
)?;
|
||||
|
||||
let edge = inner_get_common_edge(faces, exec_state, args.clone()).await?;
|
||||
if faces.len() != 2 {
|
||||
return Err(KclError::new_type(KclErrorDetails::new(
|
||||
"getCommonEdge requires exactly two tags for faces".to_owned(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
}
|
||||
|
||||
fn into_tag(face: FaceTag, source_range: SourceRange) -> Result<TagIdentifier, KclError> {
|
||||
match face {
|
||||
FaceTag::StartOrEnd(_) => Err(KclError::new_type(KclErrorDetails::new(
|
||||
"getCommonEdge requires a tagged face, it cannot use `START` or `END` faces".to_owned(),
|
||||
vec![source_range],
|
||||
))),
|
||||
FaceTag::Tag(tag_identifier) => Ok(*tag_identifier),
|
||||
}
|
||||
}
|
||||
|
||||
let face2 = into_tag(faces.pop().unwrap(), args.source_range)?;
|
||||
let face1 = into_tag(faces.pop().unwrap(), args.source_range)?;
|
||||
|
||||
let edge = inner_get_common_edge(face1, face2, exec_state, args.clone()).await?;
|
||||
Ok(KclValue::Uuid {
|
||||
value: edge,
|
||||
meta: vec![args.source_range.into()],
|
||||
@ -188,7 +212,8 @@ pub async fn get_common_edge(exec_state: &mut ExecState, args: Args) -> Result<K
|
||||
}
|
||||
|
||||
async fn inner_get_common_edge(
|
||||
faces: Vec<TagIdentifier>,
|
||||
face1: TagIdentifier,
|
||||
face2: TagIdentifier,
|
||||
exec_state: &mut ExecState,
|
||||
args: Args,
|
||||
) -> Result<Uuid, KclError> {
|
||||
@ -197,17 +222,11 @@ async fn inner_get_common_edge(
|
||||
return Ok(id);
|
||||
}
|
||||
|
||||
if faces.len() != 2 {
|
||||
return Err(KclError::new_type(KclErrorDetails::new(
|
||||
"getCommonEdge requires exactly two tags for faces".to_string(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
}
|
||||
let first_face_id = args.get_adjacent_face_to_tag(exec_state, &faces[0], false).await?;
|
||||
let second_face_id = args.get_adjacent_face_to_tag(exec_state, &faces[1], false).await?;
|
||||
let first_face_id = args.get_adjacent_face_to_tag(exec_state, &face1, false).await?;
|
||||
let second_face_id = args.get_adjacent_face_to_tag(exec_state, &face2, false).await?;
|
||||
|
||||
let first_tagged_path = args.get_tag_engine_info(exec_state, &faces[0])?.clone();
|
||||
let second_tagged_path = args.get_tag_engine_info(exec_state, &faces[1])?;
|
||||
let first_tagged_path = args.get_tag_engine_info(exec_state, &face1)?.clone();
|
||||
let second_tagged_path = args.get_tag_engine_info(exec_state, &face2)?;
|
||||
|
||||
if first_tagged_path.sketch != second_tagged_path.sketch {
|
||||
return Err(KclError::new_type(KclErrorDetails::new(
|
||||
@ -221,14 +240,14 @@ async fn inner_get_common_edge(
|
||||
// TODO: we likely want to be a lot more persnickety _which_ fillets we are flushing
|
||||
// but for now, we'll just flush everything.
|
||||
if let Some(ExtrudeSurface::Chamfer { .. } | ExtrudeSurface::Fillet { .. }) = first_tagged_path.surface {
|
||||
args.ctx.engine.flush_batch(true, args.source_range).await?;
|
||||
exec_state.flush_batch((&args).into(), true).await?;
|
||||
} else if let Some(ExtrudeSurface::Chamfer { .. } | ExtrudeSurface::Fillet { .. }) = second_tagged_path.surface {
|
||||
args.ctx.engine.flush_batch(true, args.source_range).await?;
|
||||
exec_state.flush_batch((&args).into(), true).await?;
|
||||
}
|
||||
|
||||
let resp = args
|
||||
let resp = exec_state
|
||||
.send_modeling_cmd(
|
||||
id,
|
||||
ModelingCmdMeta::from_args_id(&args, id),
|
||||
ModelingCmd::from(mcmd::Solid3dGetCommonEdge {
|
||||
object_id: first_tagged_path.sketch,
|
||||
face_ids: [first_face_id, second_face_id],
|
||||
@ -249,7 +268,7 @@ async fn inner_get_common_edge(
|
||||
KclError::new_type(KclErrorDetails::new(
|
||||
format!(
|
||||
"No common edge was found between `{}` and `{}`",
|
||||
faces[0].value, faces[1].value
|
||||
face1.value, face2.value
|
||||
),
|
||||
vec![args.source_range],
|
||||
))
|
||||
|
||||
@ -12,15 +12,18 @@ use kcmc::{
|
||||
websocket::{ModelingCmdReq, OkWebSocketResponseData},
|
||||
ModelingCmd,
|
||||
};
|
||||
use kittycad_modeling_cmds::{self as kcmc};
|
||||
use kittycad_modeling_cmds::{
|
||||
self as kcmc,
|
||||
shared::{Angle, Point2d},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::args::TyF64;
|
||||
use super::{args::TyF64, utils::point_to_mm, DEFAULT_TOLERANCE};
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{
|
||||
types::RuntimeType, ArtifactId, ExecState, ExtrudeSurface, GeoMeta, KclValue, Path, Sketch, SketchSurface,
|
||||
Solid,
|
||||
types::RuntimeType, ArtifactId, ExecState, ExtrudeSurface, GeoMeta, KclValue, ModelingCmdMeta, Path, Sketch,
|
||||
SketchSurface, Solid,
|
||||
},
|
||||
parsing::ast::types::TagNode,
|
||||
std::Args,
|
||||
@ -35,6 +38,10 @@ pub async fn extrude(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
||||
args.get_kw_arg_opt("bidirectionalLength", &RuntimeType::length(), exec_state)?;
|
||||
let tag_start = args.get_kw_arg_opt("tagStart", &RuntimeType::tag_decl(), exec_state)?;
|
||||
let tag_end = args.get_kw_arg_opt("tagEnd", &RuntimeType::tag_decl(), exec_state)?;
|
||||
let twist_angle: Option<TyF64> = args.get_kw_arg_opt("twistAngle", &RuntimeType::degrees(), exec_state)?;
|
||||
let twist_angle_step: Option<TyF64> = args.get_kw_arg_opt("twistAngleStep", &RuntimeType::degrees(), exec_state)?;
|
||||
let twist_center: Option<[TyF64; 2]> = args.get_kw_arg_opt("twistCenter", &RuntimeType::point2d(), exec_state)?;
|
||||
let tolerance: Option<TyF64> = args.get_kw_arg_opt("tolerance", &RuntimeType::length(), exec_state)?;
|
||||
|
||||
let result = inner_extrude(
|
||||
sketches,
|
||||
@ -43,6 +50,10 @@ pub async fn extrude(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
||||
bidirectional_length,
|
||||
tag_start,
|
||||
tag_end,
|
||||
twist_angle,
|
||||
twist_angle_step,
|
||||
twist_center,
|
||||
tolerance,
|
||||
exec_state,
|
||||
args,
|
||||
)
|
||||
@ -59,11 +70,16 @@ async fn inner_extrude(
|
||||
bidirectional_length: Option<TyF64>,
|
||||
tag_start: Option<TagNode>,
|
||||
tag_end: Option<TagNode>,
|
||||
twist_angle: Option<TyF64>,
|
||||
twist_angle_step: Option<TyF64>,
|
||||
twist_center: Option<[TyF64; 2]>,
|
||||
tolerance: Option<TyF64>,
|
||||
exec_state: &mut ExecState,
|
||||
args: Args,
|
||||
) -> Result<Vec<Solid>, KclError> {
|
||||
// Extrude the element(s).
|
||||
let mut solids = Vec::new();
|
||||
let tolerance = LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE));
|
||||
|
||||
if symmetric.unwrap_or(false) && bidirectional_length.is_some() {
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
@ -85,19 +101,32 @@ async fn inner_extrude(
|
||||
|
||||
for sketch in &sketches {
|
||||
let id = exec_state.next_uuid();
|
||||
args.batch_modeling_cmds(&sketch.build_sketch_mode_cmds(
|
||||
exec_state,
|
||||
ModelingCmdReq {
|
||||
cmd_id: id.into(),
|
||||
cmd: ModelingCmd::from(mcmd::Extrude {
|
||||
let cmd = match (&twist_angle, &twist_angle_step, &twist_center) {
|
||||
(Some(angle), angle_step, center) => {
|
||||
let center = center.clone().map(point_to_mm).map(Point2d::from).unwrap_or_default();
|
||||
let total_rotation_angle = Angle::from_degrees(angle.to_degrees());
|
||||
let angle_step_size = Angle::from_degrees(angle_step.clone().map(|a| a.to_degrees()).unwrap_or(15.0));
|
||||
ModelingCmd::from(mcmd::TwistExtrude {
|
||||
target: sketch.id.into(),
|
||||
distance: LengthUnit(length.to_mm()),
|
||||
faces: Default::default(),
|
||||
opposite: opposite.clone(),
|
||||
}),
|
||||
},
|
||||
))
|
||||
.await?;
|
||||
center_2d: center,
|
||||
total_rotation_angle,
|
||||
angle_step_size,
|
||||
tolerance,
|
||||
})
|
||||
}
|
||||
(None, _, _) => ModelingCmd::from(mcmd::Extrude {
|
||||
target: sketch.id.into(),
|
||||
distance: LengthUnit(length.to_mm()),
|
||||
faces: Default::default(),
|
||||
opposite: opposite.clone(),
|
||||
}),
|
||||
};
|
||||
let cmds = sketch.build_sketch_mode_cmds(exec_state, ModelingCmdReq { cmd_id: id.into(), cmd });
|
||||
exec_state
|
||||
.batch_modeling_cmds(ModelingCmdMeta::from_args_id(&args, id), &cmds)
|
||||
.await?;
|
||||
|
||||
solids.push(
|
||||
do_post_extrude(
|
||||
@ -139,11 +168,12 @@ pub(crate) async fn do_post_extrude<'a>(
|
||||
) -> Result<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?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
args.into(),
|
||||
ModelingCmd::from(mcmd::ObjectBringToFront { object_id: sketch.id }),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let any_edge_id = if let Some(id) = edge_id {
|
||||
id
|
||||
@ -168,9 +198,9 @@ pub(crate) async fn do_post_extrude<'a>(
|
||||
sketch.id = face.solid.sketch.id;
|
||||
}
|
||||
|
||||
let solid3d_info = args
|
||||
let solid3d_info = exec_state
|
||||
.send_modeling_cmd(
|
||||
exec_state.next_uuid(),
|
||||
args.into(),
|
||||
ModelingCmd::from(mcmd::Solid3dGetExtrusionFaceInfo {
|
||||
edge_id: any_edge_id,
|
||||
object_id: sketch.id,
|
||||
@ -193,14 +223,15 @@ pub(crate) async fn do_post_extrude<'a>(
|
||||
// Getting the ids of a sectional sweep does not work well and we cannot guarantee that
|
||||
// any of these call will not just fail.
|
||||
if !sectional {
|
||||
args.batch_modeling_cmd(
|
||||
exec_state.next_uuid(),
|
||||
ModelingCmd::from(mcmd::Solid3dGetAdjacencyInfo {
|
||||
object_id: sketch.id,
|
||||
edge_id: any_edge_id,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
args.into(),
|
||||
ModelingCmd::from(mcmd::Solid3dGetAdjacencyInfo {
|
||||
object_id: sketch.id,
|
||||
edge_id: any_edge_id,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -10,7 +10,8 @@ use super::{args::TyF64, DEFAULT_TOLERANCE};
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{
|
||||
types::RuntimeType, EdgeCut, ExecState, ExtrudeSurface, FilletSurface, GeoMeta, KclValue, Solid, TagIdentifier,
|
||||
types::RuntimeType, EdgeCut, ExecState, ExtrudeSurface, FilletSurface, GeoMeta, KclValue, ModelingCmdMeta,
|
||||
Solid, TagIdentifier,
|
||||
},
|
||||
parsing::ast::types::TagNode,
|
||||
std::Args,
|
||||
@ -111,20 +112,21 @@ async fn inner_fillet(
|
||||
for _ in 0..num_extra_ids {
|
||||
extra_face_ids.push(exec_state.next_uuid());
|
||||
}
|
||||
args.batch_end_cmd(
|
||||
id,
|
||||
ModelingCmd::from(mcmd::Solid3dFilletEdge {
|
||||
edge_id: None,
|
||||
edge_ids: edge_ids.clone(),
|
||||
extra_face_ids,
|
||||
strategy: Default::default(),
|
||||
object_id: solid.id,
|
||||
radius: LengthUnit(radius.to_mm()),
|
||||
tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)),
|
||||
cut_type: CutType::Fillet,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_end_cmd(
|
||||
ModelingCmdMeta::from_args_id(&args, id),
|
||||
ModelingCmd::from(mcmd::Solid3dFilletEdge {
|
||||
edge_id: None,
|
||||
edge_ids: edge_ids.clone(),
|
||||
extra_face_ids,
|
||||
strategy: Default::default(),
|
||||
object_id: solid.id,
|
||||
radius: LengthUnit(radius.to_mm()),
|
||||
tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)),
|
||||
cut_type: CutType::Fillet,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let new_edge_cuts = edge_ids.into_iter().map(|edge_id| EdgeCut::Fillet {
|
||||
id,
|
||||
|
||||
@ -9,7 +9,7 @@ use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{
|
||||
types::{PrimitiveType, RuntimeType},
|
||||
ExecState, Helix as HelixValue, KclValue, Solid,
|
||||
ExecState, Helix as HelixValue, KclValue, ModelingCmdMeta, Solid,
|
||||
},
|
||||
std::{axis_or_reference::Axis3dOrEdgeReference, Args},
|
||||
};
|
||||
@ -124,17 +124,18 @@ async fn inner_helix(
|
||||
}
|
||||
|
||||
if let Some(cylinder) = cylinder {
|
||||
args.batch_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::from(mcmd::EntityMakeHelix {
|
||||
cylinder_id: cylinder.id,
|
||||
is_clockwise: !helix_result.ccw,
|
||||
length: LengthUnit(length.as_ref().map(|t| t.to_mm()).unwrap_or(cylinder.height_in_mm())),
|
||||
revolutions,
|
||||
start_angle: Angle::from_degrees(angle_start),
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
ModelingCmdMeta::from_args_id(&args, id),
|
||||
ModelingCmd::from(mcmd::EntityMakeHelix {
|
||||
cylinder_id: cylinder.id,
|
||||
is_clockwise: !helix_result.ccw,
|
||||
length: LengthUnit(length.as_ref().map(|t| t.to_mm()).unwrap_or(cylinder.height_in_mm())),
|
||||
revolutions,
|
||||
start_angle: Angle::from_degrees(angle_start),
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
} else if let (Some(axis), Some(radius)) = (axis, radius) {
|
||||
match axis {
|
||||
Axis3dOrEdgeReference::Axis { direction, origin } => {
|
||||
@ -146,43 +147,45 @@ async fn inner_helix(
|
||||
)));
|
||||
};
|
||||
|
||||
args.batch_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::from(mcmd::EntityMakeHelixFromParams {
|
||||
radius: LengthUnit(radius.to_mm()),
|
||||
is_clockwise: !helix_result.ccw,
|
||||
length: LengthUnit(length.to_mm()),
|
||||
revolutions,
|
||||
start_angle: Angle::from_degrees(angle_start),
|
||||
axis: Point3d {
|
||||
x: direction[0].to_mm(),
|
||||
y: direction[1].to_mm(),
|
||||
z: direction[2].to_mm(),
|
||||
},
|
||||
center: Point3d {
|
||||
x: LengthUnit(origin[0].to_mm()),
|
||||
y: LengthUnit(origin[1].to_mm()),
|
||||
z: LengthUnit(origin[2].to_mm()),
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
ModelingCmdMeta::from_args_id(&args, id),
|
||||
ModelingCmd::from(mcmd::EntityMakeHelixFromParams {
|
||||
radius: LengthUnit(radius.to_mm()),
|
||||
is_clockwise: !helix_result.ccw,
|
||||
length: LengthUnit(length.to_mm()),
|
||||
revolutions,
|
||||
start_angle: Angle::from_degrees(angle_start),
|
||||
axis: Point3d {
|
||||
x: direction[0].to_mm(),
|
||||
y: direction[1].to_mm(),
|
||||
z: direction[2].to_mm(),
|
||||
},
|
||||
center: Point3d {
|
||||
x: LengthUnit(origin[0].to_mm()),
|
||||
y: LengthUnit(origin[1].to_mm()),
|
||||
z: LengthUnit(origin[2].to_mm()),
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Axis3dOrEdgeReference::Edge(edge) => {
|
||||
let edge_id = edge.get_engine_id(exec_state, &args)?;
|
||||
|
||||
args.batch_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::from(mcmd::EntityMakeHelixFromEdge {
|
||||
radius: LengthUnit(radius.to_mm()),
|
||||
is_clockwise: !helix_result.ccw,
|
||||
length: length.map(|t| LengthUnit(t.to_mm())),
|
||||
revolutions,
|
||||
start_angle: Angle::from_degrees(angle_start),
|
||||
edge_id,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
ModelingCmdMeta::from_args_id(&args, id),
|
||||
ModelingCmd::from(mcmd::EntityMakeHelixFromEdge {
|
||||
radius: LengthUnit(radius.to_mm()),
|
||||
is_clockwise: !helix_result.ccw,
|
||||
length: length.map(|t| LengthUnit(t.to_mm())),
|
||||
revolutions,
|
||||
start_angle: Angle::from_degrees(angle_start),
|
||||
edge_id,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{
|
||||
types::{NumericType, RuntimeType},
|
||||
ExecState, KclValue, Sketch, Solid,
|
||||
ExecState, KclValue, ModelingCmdMeta, Sketch, Solid,
|
||||
},
|
||||
parsing::ast::types::TagNode,
|
||||
std::{extrude::do_post_extrude, Args},
|
||||
@ -77,17 +77,18 @@ async fn inner_loft(
|
||||
}
|
||||
|
||||
let id = exec_state.next_uuid();
|
||||
args.batch_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::from(mcmd::Loft {
|
||||
section_ids: sketches.iter().map(|group| group.id).collect(),
|
||||
base_curve_index,
|
||||
bez_approximate_rational,
|
||||
tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)),
|
||||
v_degree,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
ModelingCmdMeta::from_args_id(&args, id),
|
||||
ModelingCmd::from(mcmd::Loft {
|
||||
section_ids: sketches.iter().map(|group| group.id).collect(),
|
||||
base_curve_index,
|
||||
bez_approximate_rational,
|
||||
tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)),
|
||||
v_degree,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Using the first sketch as the base curve, idk we might want to change this later.
|
||||
let mut sketch = sketches[0].clone();
|
||||
|
||||
@ -18,7 +18,7 @@ pub async fn rem(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
|
||||
let n: TyF64 = args.get_unlabeled_kw_arg("number to divide", &RuntimeType::num_any(), exec_state)?;
|
||||
let d: TyF64 = args.get_kw_arg("divisor", &RuntimeType::num_any(), exec_state)?;
|
||||
|
||||
let (n, d, ty) = NumericType::combine_div(n, d);
|
||||
let (n, d, ty) = NumericType::combine_mod(n, d);
|
||||
if ty == NumericType::Unknown {
|
||||
exec_state.err(CompilationError::err(
|
||||
args.source_range,
|
||||
@ -34,21 +34,21 @@ pub async fn rem(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
|
||||
pub async fn cos(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let num: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::angle(), exec_state)?;
|
||||
let num = num.to_radians();
|
||||
Ok(args.make_user_val_from_f64_with_type(TyF64::count(num.cos())))
|
||||
Ok(args.make_user_val_from_f64_with_type(TyF64::new(libm::cos(num), exec_state.current_default_units())))
|
||||
}
|
||||
|
||||
/// Compute the sine of a number (in radians).
|
||||
pub async fn sin(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let num: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::angle(), exec_state)?;
|
||||
let num = num.to_radians();
|
||||
Ok(args.make_user_val_from_f64_with_type(TyF64::count(num.sin())))
|
||||
Ok(args.make_user_val_from_f64_with_type(TyF64::new(libm::sin(num), exec_state.current_default_units())))
|
||||
}
|
||||
|
||||
/// Compute the tangent of a number (in radians).
|
||||
pub async fn tan(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let num: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::angle(), exec_state)?;
|
||||
let num = num.to_radians();
|
||||
Ok(args.make_user_val_from_f64_with_type(TyF64::count(num.tan())))
|
||||
Ok(args.make_user_val_from_f64_with_type(TyF64::new(libm::tan(num), exec_state.current_default_units())))
|
||||
}
|
||||
|
||||
/// Compute the square root of a number.
|
||||
@ -164,7 +164,7 @@ pub async fn pow(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
|
||||
/// Compute the arccosine of a number (in radians).
|
||||
pub async fn acos(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::count(), exec_state)?;
|
||||
let result = input.n.acos();
|
||||
let result = libm::acos(input.n);
|
||||
|
||||
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
|
||||
}
|
||||
@ -172,7 +172,7 @@ pub async fn acos(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
||||
/// Compute the arcsine of a number (in radians).
|
||||
pub async fn asin(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::count(), exec_state)?;
|
||||
let result = input.n.asin();
|
||||
let result = libm::asin(input.n);
|
||||
|
||||
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
|
||||
}
|
||||
@ -180,7 +180,7 @@ pub async fn asin(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
||||
/// Compute the arctangent of a number (in radians).
|
||||
pub async fn atan(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::count(), exec_state)?;
|
||||
let result = input.n.atan();
|
||||
let result = libm::atan(input.n);
|
||||
|
||||
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
|
||||
}
|
||||
@ -190,7 +190,7 @@ pub async fn atan2(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
let y = args.get_kw_arg("y", &RuntimeType::length(), exec_state)?;
|
||||
let x = args.get_kw_arg("x", &RuntimeType::length(), exec_state)?;
|
||||
let (y, x, _) = NumericType::combine_eq_coerce(y, x);
|
||||
let result = y.atan2(x);
|
||||
let result = libm::atan2(y, x);
|
||||
|
||||
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
|
||||
}
|
||||
@ -246,7 +246,7 @@ pub async fn leg_angle_x(exec_state: &mut ExecState, args: Args) -> Result<KclVa
|
||||
let hypotenuse: TyF64 = args.get_kw_arg("hypotenuse", &RuntimeType::length(), exec_state)?;
|
||||
let leg: TyF64 = args.get_kw_arg("leg", &RuntimeType::length(), exec_state)?;
|
||||
let (hypotenuse, leg, _ty) = NumericType::combine_eq_coerce(hypotenuse, leg);
|
||||
let result = (leg.min(hypotenuse) / hypotenuse).acos().to_degrees();
|
||||
let result = libm::acos(leg.min(hypotenuse) / hypotenuse).to_degrees();
|
||||
Ok(KclValue::from_number_with_type(
|
||||
result,
|
||||
NumericType::degrees(),
|
||||
@ -259,7 +259,7 @@ pub async fn leg_angle_y(exec_state: &mut ExecState, args: Args) -> Result<KclVa
|
||||
let hypotenuse: TyF64 = args.get_kw_arg("hypotenuse", &RuntimeType::length(), exec_state)?;
|
||||
let leg: TyF64 = args.get_kw_arg("leg", &RuntimeType::length(), exec_state)?;
|
||||
let (hypotenuse, leg, _ty) = NumericType::combine_eq_coerce(hypotenuse, leg);
|
||||
let result = (leg.min(hypotenuse) / hypotenuse).asin().to_degrees();
|
||||
let result = libm::asin(leg.min(hypotenuse) / hypotenuse).to_degrees();
|
||||
Ok(KclValue::from_number_with_type(
|
||||
result,
|
||||
NumericType::degrees(),
|
||||
|
||||
@ -52,35 +52,37 @@ async fn inner_mirror_2d(
|
||||
|
||||
match axis {
|
||||
Axis2dOrEdgeReference::Axis { direction, origin } => {
|
||||
args.batch_modeling_cmd(
|
||||
exec_state.next_uuid(),
|
||||
ModelingCmd::from(mcmd::EntityMirror {
|
||||
ids: starting_sketches.iter().map(|sketch| sketch.id).collect(),
|
||||
axis: Point3d {
|
||||
x: direction[0].to_mm(),
|
||||
y: direction[1].to_mm(),
|
||||
z: 0.0,
|
||||
},
|
||||
point: Point3d {
|
||||
x: LengthUnit(origin[0].to_mm()),
|
||||
y: LengthUnit(origin[1].to_mm()),
|
||||
z: LengthUnit(0.0),
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
(&args).into(),
|
||||
ModelingCmd::from(mcmd::EntityMirror {
|
||||
ids: starting_sketches.iter().map(|sketch| sketch.id).collect(),
|
||||
axis: Point3d {
|
||||
x: direction[0].to_mm(),
|
||||
y: direction[1].to_mm(),
|
||||
z: 0.0,
|
||||
},
|
||||
point: Point3d {
|
||||
x: LengthUnit(origin[0].to_mm()),
|
||||
y: LengthUnit(origin[1].to_mm()),
|
||||
z: LengthUnit(0.0),
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Axis2dOrEdgeReference::Edge(edge) => {
|
||||
let edge_id = edge.get_engine_id(exec_state, &args)?;
|
||||
|
||||
args.batch_modeling_cmd(
|
||||
exec_state.next_uuid(),
|
||||
ModelingCmd::from(mcmd::EntityMirrorAcrossEdge {
|
||||
ids: starting_sketches.iter().map(|sketch| sketch.id).collect(),
|
||||
edge_id,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
(&args).into(),
|
||||
ModelingCmd::from(mcmd::EntityMirrorAcrossEdge {
|
||||
ids: starting_sketches.iter().map(|sketch| sketch.id).collect(),
|
||||
edge_id,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
};
|
||||
|
||||
@ -90,9 +92,9 @@ async fn inner_mirror_2d(
|
||||
// using the IDs we already have.
|
||||
// We only do this with mirrors because otherwise it is a waste of a websocket call.
|
||||
for sketch in &mut starting_sketches {
|
||||
let response = args
|
||||
let response = exec_state
|
||||
.send_modeling_cmd(
|
||||
exec_state.next_uuid(),
|
||||
(&args).into(),
|
||||
ModelingCmd::from(mcmd::EntityGetAllChildUuids { entity_id: sketch.id }),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@ -468,6 +468,8 @@ pub(crate) fn std_ty(path: &str, fn_name: &str) -> (PrimitiveType, StdFnProps) {
|
||||
("types", "Edge") => (PrimitiveType::Edge, StdFnProps::default("std::types::Edge")),
|
||||
("types", "Axis2d") => (PrimitiveType::Axis2d, StdFnProps::default("std::types::Axis2d")),
|
||||
("types", "Axis3d") => (PrimitiveType::Axis3d, StdFnProps::default("std::types::Axis3d")),
|
||||
("types", "TaggedEdge") => (PrimitiveType::TaggedEdge, StdFnProps::default("std::types::TaggedEdge")),
|
||||
("types", "TaggedFace") => (PrimitiveType::TaggedFace, StdFnProps::default("std::types::TaggedFace")),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,12 +149,11 @@ async fn send_pattern_transform<T: GeometryTrait>(
|
||||
exec_state: &mut ExecState,
|
||||
args: &Args,
|
||||
) -> Result<Vec<T>, KclError> {
|
||||
let id = exec_state.next_uuid();
|
||||
let extra_instances = transforms.len();
|
||||
|
||||
let resp = args
|
||||
let resp = exec_state
|
||||
.send_modeling_cmd(
|
||||
id,
|
||||
args.into(),
|
||||
ModelingCmd::from(mcmd::EntityLinearPatternTransform {
|
||||
entity_id: if use_original { solid.original_id() } else { solid.id() },
|
||||
transform: Default::default(),
|
||||
@ -443,7 +442,7 @@ impl GeometryTrait for Solid {
|
||||
}
|
||||
|
||||
async fn flush_batch(args: &Args, exec_state: &mut ExecState, solid_set: &Self::Set) -> Result<(), KclError> {
|
||||
args.flush_batch_for_solids(exec_state, solid_set).await
|
||||
exec_state.flush_batch_for_solids(args.into(), solid_set).await
|
||||
}
|
||||
}
|
||||
|
||||
@ -874,7 +873,7 @@ async fn inner_pattern_circular_3d(
|
||||
// Flush the batch for our fillets/chamfers if there are any.
|
||||
// If we do not flush these, then you won't be able to pattern something with fillets.
|
||||
// Flush just the fillets/chamfers that apply to these solids.
|
||||
args.flush_batch_for_solids(exec_state, &solids).await?;
|
||||
exec_state.flush_batch_for_solids((&args).into(), &solids).await?;
|
||||
|
||||
let starting_solids = solids;
|
||||
|
||||
@ -919,7 +918,6 @@ async fn pattern_circular(
|
||||
exec_state: &mut ExecState,
|
||||
args: Args,
|
||||
) -> Result<Geometries, KclError> {
|
||||
let id = exec_state.next_uuid();
|
||||
let num_repetitions = match data.repetitions() {
|
||||
RepetitionsNeeded::More(n) => n,
|
||||
RepetitionsNeeded::None => {
|
||||
@ -934,9 +932,9 @@ async fn pattern_circular(
|
||||
};
|
||||
|
||||
let center = data.center_mm();
|
||||
let resp = args
|
||||
let resp = exec_state
|
||||
.send_modeling_cmd(
|
||||
id,
|
||||
(&args).into(),
|
||||
ModelingCmd::from(mcmd::EntityCircularPattern {
|
||||
axis: kcmc::shared::Point3d::from(data.axis()),
|
||||
entity_id: if data.use_original() {
|
||||
|
||||
@ -6,7 +6,7 @@ use kittycad_modeling_cmds as kcmc;
|
||||
use super::{args::TyF64, sketch::PlaneData};
|
||||
use crate::{
|
||||
errors::KclError,
|
||||
execution::{types::RuntimeType, ExecState, KclValue, Plane, PlaneType},
|
||||
execution::{types::RuntimeType, ExecState, KclValue, ModelingCmdMeta, Plane, PlaneType},
|
||||
std::Args,
|
||||
};
|
||||
|
||||
@ -49,28 +49,31 @@ async fn make_offset_plane_in_engine(plane: &Plane, exec_state: &mut ExecState,
|
||||
a: 0.3,
|
||||
};
|
||||
|
||||
args.batch_modeling_cmd(
|
||||
plane.id,
|
||||
ModelingCmd::from(mcmd::MakePlane {
|
||||
clobber: false,
|
||||
origin: plane.info.origin.into(),
|
||||
size: LengthUnit(default_size),
|
||||
x_axis: plane.info.x_axis.into(),
|
||||
y_axis: plane.info.y_axis.into(),
|
||||
hide: Some(false),
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
let meta = ModelingCmdMeta::from_args_id(args, plane.id);
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
meta,
|
||||
ModelingCmd::from(mcmd::MakePlane {
|
||||
clobber: false,
|
||||
origin: plane.info.origin.into(),
|
||||
size: LengthUnit(default_size),
|
||||
x_axis: plane.info.x_axis.into(),
|
||||
y_axis: plane.info.y_axis.into(),
|
||||
hide: Some(false),
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Set the color.
|
||||
args.batch_modeling_cmd(
|
||||
exec_state.next_uuid(),
|
||||
ModelingCmd::from(mcmd::PlaneSetColor {
|
||||
color,
|
||||
plane_id: plane.id,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
args.into(),
|
||||
ModelingCmd::from(mcmd::PlaneSetColor {
|
||||
color,
|
||||
plane_id: plane.id,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{
|
||||
types::{NumericType, PrimitiveType, RuntimeType},
|
||||
ExecState, KclValue, Sketch, Solid,
|
||||
ExecState, KclValue, ModelingCmdMeta, Sketch, Solid,
|
||||
},
|
||||
parsing::ast::types::TagNode,
|
||||
std::{axis_or_reference::Axis2dOrEdgeReference, extrude::do_post_extrude, Args},
|
||||
@ -137,42 +137,44 @@ async fn inner_revolve(
|
||||
|
||||
let direction = match &axis {
|
||||
Axis2dOrEdgeReference::Axis { direction, origin } => {
|
||||
args.batch_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::from(mcmd::Revolve {
|
||||
angle,
|
||||
target: sketch.id.into(),
|
||||
axis: Point3d {
|
||||
x: direction[0].to_mm(),
|
||||
y: direction[1].to_mm(),
|
||||
z: 0.0,
|
||||
},
|
||||
origin: Point3d {
|
||||
x: LengthUnit(origin[0].to_mm()),
|
||||
y: LengthUnit(origin[1].to_mm()),
|
||||
z: LengthUnit(0.0),
|
||||
},
|
||||
tolerance: LengthUnit(tolerance),
|
||||
axis_is_2d: true,
|
||||
opposite: opposite.clone(),
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
ModelingCmdMeta::from_args_id(&args, id),
|
||||
ModelingCmd::from(mcmd::Revolve {
|
||||
angle,
|
||||
target: sketch.id.into(),
|
||||
axis: Point3d {
|
||||
x: direction[0].to_mm(),
|
||||
y: direction[1].to_mm(),
|
||||
z: 0.0,
|
||||
},
|
||||
origin: Point3d {
|
||||
x: LengthUnit(origin[0].to_mm()),
|
||||
y: LengthUnit(origin[1].to_mm()),
|
||||
z: LengthUnit(0.0),
|
||||
},
|
||||
tolerance: LengthUnit(tolerance),
|
||||
axis_is_2d: true,
|
||||
opposite: opposite.clone(),
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
glm::DVec2::new(direction[0].to_mm(), direction[1].to_mm())
|
||||
}
|
||||
Axis2dOrEdgeReference::Edge(edge) => {
|
||||
let edge_id = edge.get_engine_id(exec_state, &args)?;
|
||||
args.batch_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::from(mcmd::RevolveAboutEdge {
|
||||
angle,
|
||||
target: sketch.id.into(),
|
||||
edge_id,
|
||||
tolerance: LengthUnit(tolerance),
|
||||
opposite: opposite.clone(),
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
ModelingCmdMeta::from_args_id(&args, id),
|
||||
ModelingCmd::from(mcmd::RevolveAboutEdge {
|
||||
angle,
|
||||
target: sketch.id.into(),
|
||||
edge_id,
|
||||
tolerance: LengthUnit(tolerance),
|
||||
opposite: opposite.clone(),
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
//TODO: fix me! Need to be able to calculate this to ensure the path isn't colinear
|
||||
glm::DVec2::new(0.0, 1.0)
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ use crate::{
|
||||
|
||||
/// Returns the point at the end of the given segment.
|
||||
pub async fn segment_end(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag", &RuntimeType::tag_identifier(), exec_state)?;
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag", &RuntimeType::tagged_edge(), exec_state)?;
|
||||
let pt = inner_segment_end(&tag, exec_state, args.clone())?;
|
||||
|
||||
args.make_kcl_val_from_point([pt[0].n, pt[1].n], pt[0].ty.clone())
|
||||
@ -38,7 +38,7 @@ fn inner_segment_end(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args
|
||||
|
||||
/// Returns the segment end of x.
|
||||
pub async fn segment_end_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag", &RuntimeType::tag_identifier(), exec_state)?;
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag", &RuntimeType::tagged_edge(), exec_state)?;
|
||||
let result = inner_segment_end_x(&tag, exec_state, args.clone())?;
|
||||
|
||||
Ok(args.make_user_val_from_f64_with_type(result))
|
||||
@ -58,7 +58,7 @@ fn inner_segment_end_x(tag: &TagIdentifier, exec_state: &mut ExecState, args: Ar
|
||||
|
||||
/// Returns the segment end of y.
|
||||
pub async fn segment_end_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag", &RuntimeType::tag_identifier(), exec_state)?;
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag", &RuntimeType::tagged_edge(), exec_state)?;
|
||||
let result = inner_segment_end_y(&tag, exec_state, args.clone())?;
|
||||
|
||||
Ok(args.make_user_val_from_f64_with_type(result))
|
||||
@ -78,7 +78,7 @@ fn inner_segment_end_y(tag: &TagIdentifier, exec_state: &mut ExecState, args: Ar
|
||||
|
||||
/// Returns the point at the start of the given segment.
|
||||
pub async fn segment_start(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag", &RuntimeType::tag_identifier(), exec_state)?;
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag", &RuntimeType::tagged_edge(), exec_state)?;
|
||||
let pt = inner_segment_start(&tag, exec_state, args.clone())?;
|
||||
|
||||
args.make_kcl_val_from_point([pt[0].n, pt[1].n], pt[0].ty.clone())
|
||||
@ -101,7 +101,7 @@ fn inner_segment_start(tag: &TagIdentifier, exec_state: &mut ExecState, args: Ar
|
||||
|
||||
/// Returns the segment start of x.
|
||||
pub async fn segment_start_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag", &RuntimeType::tag_identifier(), exec_state)?;
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag", &RuntimeType::tagged_edge(), exec_state)?;
|
||||
let result = inner_segment_start_x(&tag, exec_state, args.clone())?;
|
||||
|
||||
Ok(args.make_user_val_from_f64_with_type(result))
|
||||
@ -121,7 +121,7 @@ fn inner_segment_start_x(tag: &TagIdentifier, exec_state: &mut ExecState, args:
|
||||
|
||||
/// Returns the segment start of y.
|
||||
pub async fn segment_start_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag", &RuntimeType::tag_identifier(), exec_state)?;
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag", &RuntimeType::tagged_edge(), exec_state)?;
|
||||
let result = inner_segment_start_y(&tag, exec_state, args.clone())?;
|
||||
|
||||
Ok(args.make_user_val_from_f64_with_type(result))
|
||||
@ -186,7 +186,7 @@ fn inner_last_segment_y(sketch: Sketch, args: Args) -> Result<TyF64, KclError> {
|
||||
|
||||
/// Returns the length of the segment.
|
||||
pub async fn segment_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag", &RuntimeType::tag_identifier(), exec_state)?;
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag", &RuntimeType::tagged_edge(), exec_state)?;
|
||||
let result = inner_segment_length(&tag, exec_state, args.clone())?;
|
||||
Ok(args.make_user_val_from_f64_with_type(result))
|
||||
}
|
||||
@ -205,7 +205,7 @@ fn inner_segment_length(tag: &TagIdentifier, exec_state: &mut ExecState, args: A
|
||||
|
||||
/// Returns the angle of the segment.
|
||||
pub async fn segment_angle(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag", &RuntimeType::tag_identifier(), exec_state)?;
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag", &RuntimeType::tagged_edge(), exec_state)?;
|
||||
|
||||
let result = inner_segment_angle(&tag, exec_state, args.clone())?;
|
||||
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::degrees())))
|
||||
@ -227,7 +227,7 @@ fn inner_segment_angle(tag: &TagIdentifier, exec_state: &mut ExecState, args: Ar
|
||||
|
||||
/// Returns the angle coming out of the end of the segment in degrees.
|
||||
pub async fn tangent_to_end(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag", &RuntimeType::tag_identifier(), exec_state)?;
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag", &RuntimeType::tagged_edge(), exec_state)?;
|
||||
|
||||
let result = inner_tangent_to_end(&tag, exec_state, args.clone()).await?;
|
||||
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::degrees())))
|
||||
@ -250,7 +250,7 @@ async fn inner_tangent_to_end(tag: &TagIdentifier, exec_state: &mut ExecState, a
|
||||
|
||||
// Calculate the end point from the angle and radius.
|
||||
// atan2 outputs radians.
|
||||
let previous_end_tangent = Angle::from_radians(f64::atan2(
|
||||
let previous_end_tangent = Angle::from_radians(libm::atan2(
|
||||
from[1] - tan_previous_point[1],
|
||||
from[0] - tan_previous_point[0],
|
||||
));
|
||||
|
||||
@ -20,7 +20,7 @@ use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{
|
||||
types::{RuntimeType, UnitLen},
|
||||
BasePath, ExecState, GeoMeta, KclValue, Path, Sketch, SketchSurface,
|
||||
BasePath, ExecState, GeoMeta, KclValue, ModelingCmdMeta, Path, Sketch, SketchSurface,
|
||||
},
|
||||
parsing::ast::types::TagNode,
|
||||
std::{
|
||||
@ -82,20 +82,21 @@ async fn inner_circle(
|
||||
|
||||
let id = exec_state.next_uuid();
|
||||
|
||||
args.batch_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::from(mcmd::ExtendPath {
|
||||
path: sketch.id.into(),
|
||||
segment: PathSegment::Arc {
|
||||
start: angle_start,
|
||||
end: angle_end,
|
||||
center: KPoint2d::from(point_to_mm(center)).map(LengthUnit),
|
||||
radius: LengthUnit(radius.to_mm()),
|
||||
relative: false,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
ModelingCmdMeta::from_args_id(&args, id),
|
||||
ModelingCmd::from(mcmd::ExtendPath {
|
||||
path: sketch.id.into(),
|
||||
segment: PathSegment::Arc {
|
||||
start: angle_start,
|
||||
end: angle_end,
|
||||
center: KPoint2d::from(point_to_mm(center)).map(LengthUnit),
|
||||
radius: LengthUnit(radius.to_mm()),
|
||||
relative: false,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let current_path = Path::Circle {
|
||||
base: BasePath {
|
||||
@ -120,7 +121,11 @@ async fn inner_circle(
|
||||
|
||||
new_sketch.paths.push(current_path);
|
||||
|
||||
args.batch_modeling_cmd(id, ModelingCmd::from(mcmd::ClosePath { path_id: new_sketch.id }))
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
ModelingCmdMeta::from_args_id(&args, id),
|
||||
ModelingCmd::from(mcmd::ClosePath { path_id: new_sketch.id }),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(new_sketch)
|
||||
@ -180,20 +185,21 @@ async fn inner_circle_three_point(
|
||||
|
||||
let id = exec_state.next_uuid();
|
||||
|
||||
args.batch_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::from(mcmd::ExtendPath {
|
||||
path: sketch.id.into(),
|
||||
segment: PathSegment::Arc {
|
||||
start: angle_start,
|
||||
end: angle_end,
|
||||
center: KPoint2d::from(untyped_point_to_mm(center, units)).map(LengthUnit),
|
||||
radius: units.adjust_to(radius, UnitLen::Mm).0.into(),
|
||||
relative: false,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
ModelingCmdMeta::from_args_id(&args, id),
|
||||
ModelingCmd::from(mcmd::ExtendPath {
|
||||
path: sketch.id.into(),
|
||||
segment: PathSegment::Arc {
|
||||
start: angle_start,
|
||||
end: angle_end,
|
||||
center: KPoint2d::from(untyped_point_to_mm(center, units)).map(LengthUnit),
|
||||
radius: units.adjust_to(radius, UnitLen::Mm).0.into(),
|
||||
relative: false,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let current_path = Path::CircleThreePoint {
|
||||
base: BasePath {
|
||||
@ -219,7 +225,11 @@ async fn inner_circle_three_point(
|
||||
|
||||
new_sketch.paths.push(current_path);
|
||||
|
||||
args.batch_modeling_cmd(id, ModelingCmd::from(mcmd::ClosePath { path_id: new_sketch.id }))
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
ModelingCmdMeta::from_args_id(&args, id),
|
||||
ModelingCmd::from(mcmd::ClosePath { path_id: new_sketch.id }),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(new_sketch)
|
||||
@ -295,7 +305,7 @@ async fn inner_polygon(
|
||||
radius.n
|
||||
} else {
|
||||
// circumscribed
|
||||
radius.n / half_angle.cos()
|
||||
radius.n / libm::cos(half_angle)
|
||||
};
|
||||
|
||||
let angle_step = std::f64::consts::TAU / num_sides as f64;
|
||||
@ -306,8 +316,8 @@ async fn inner_polygon(
|
||||
.map(|i| {
|
||||
let angle = angle_step * i as f64;
|
||||
[
|
||||
center_u[0] + radius_to_vertices * angle.cos(),
|
||||
center_u[1] + radius_to_vertices * angle.sin(),
|
||||
center_u[0] + radius_to_vertices * libm::cos(angle),
|
||||
center_u[1] + radius_to_vertices * libm::sin(angle),
|
||||
]
|
||||
})
|
||||
.collect();
|
||||
@ -326,19 +336,20 @@ async fn inner_polygon(
|
||||
let from = sketch.current_pen_position()?;
|
||||
let id = exec_state.next_uuid();
|
||||
|
||||
args.batch_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::from(mcmd::ExtendPath {
|
||||
path: sketch.id.into(),
|
||||
segment: PathSegment::Line {
|
||||
end: KPoint2d::from(untyped_point_to_mm(*vertex, units))
|
||||
.with_z(0.0)
|
||||
.map(LengthUnit),
|
||||
relative: false,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
ModelingCmdMeta::from_args_id(&args, id),
|
||||
ModelingCmd::from(mcmd::ExtendPath {
|
||||
path: sketch.id.into(),
|
||||
segment: PathSegment::Line {
|
||||
end: KPoint2d::from(untyped_point_to_mm(*vertex, units))
|
||||
.with_z(0.0)
|
||||
.map(LengthUnit),
|
||||
relative: false,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let current_path = Path::ToPoint {
|
||||
base: BasePath {
|
||||
@ -360,19 +371,20 @@ async fn inner_polygon(
|
||||
let from = sketch.current_pen_position()?;
|
||||
let close_id = exec_state.next_uuid();
|
||||
|
||||
args.batch_modeling_cmd(
|
||||
close_id,
|
||||
ModelingCmd::from(mcmd::ExtendPath {
|
||||
path: sketch.id.into(),
|
||||
segment: PathSegment::Line {
|
||||
end: KPoint2d::from(untyped_point_to_mm(vertices[0], units))
|
||||
.with_z(0.0)
|
||||
.map(LengthUnit),
|
||||
relative: false,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
ModelingCmdMeta::from_args_id(&args, close_id),
|
||||
ModelingCmd::from(mcmd::ExtendPath {
|
||||
path: sketch.id.into(),
|
||||
segment: PathSegment::Line {
|
||||
end: KPoint2d::from(untyped_point_to_mm(vertices[0], units))
|
||||
.with_z(0.0)
|
||||
.map(LengthUnit),
|
||||
relative: false,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let current_path = Path::ToPoint {
|
||||
base: BasePath {
|
||||
@ -389,11 +401,12 @@ async fn inner_polygon(
|
||||
|
||||
sketch.paths.push(current_path);
|
||||
|
||||
args.batch_modeling_cmd(
|
||||
exec_state.next_uuid(),
|
||||
ModelingCmd::from(mcmd::ClosePath { path_id: sketch.id }),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
(&args).into(),
|
||||
ModelingCmd::from(mcmd::ClosePath { path_id: sketch.id }),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(sketch)
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ pub async fn shell(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
let thickness: TyF64 = args.get_kw_arg("thickness", &RuntimeType::length(), exec_state)?;
|
||||
let faces = args.get_kw_arg(
|
||||
"faces",
|
||||
&RuntimeType::Array(Box::new(RuntimeType::tag()), ArrayLen::Minimum(1)),
|
||||
&RuntimeType::Array(Box::new(RuntimeType::tagged_face()), ArrayLen::Minimum(1)),
|
||||
exec_state,
|
||||
)?;
|
||||
|
||||
@ -53,7 +53,9 @@ async fn inner_shell(
|
||||
for solid in &solids {
|
||||
// Flush the batch for our fillets/chamfers if there are any.
|
||||
// If we do not do these for sketch on face, things will fail with face does not exist.
|
||||
args.flush_batch_for_solids(exec_state, &[solid.clone()]).await?;
|
||||
exec_state
|
||||
.flush_batch_for_solids((&args).into(), &[solid.clone()])
|
||||
.await?;
|
||||
|
||||
for tag in &faces {
|
||||
let extrude_plane_id = tag.get_face_id(solid, exec_state, &args, false).await?;
|
||||
@ -78,16 +80,17 @@ async fn inner_shell(
|
||||
)));
|
||||
}
|
||||
|
||||
args.batch_modeling_cmd(
|
||||
exec_state.next_uuid(),
|
||||
ModelingCmd::from(mcmd::Solid3dShellFace {
|
||||
hollow: false,
|
||||
face_ids,
|
||||
object_id: solids[0].id,
|
||||
shell_thickness: LengthUnit(thickness.to_mm()),
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
(&args).into(),
|
||||
ModelingCmd::from(mcmd::Solid3dShellFace {
|
||||
hollow: false,
|
||||
face_ids,
|
||||
object_id: solids[0].id,
|
||||
shell_thickness: LengthUnit(thickness.to_mm()),
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(solids)
|
||||
}
|
||||
@ -109,18 +112,21 @@ async fn inner_hollow(
|
||||
) -> Result<Box<Solid>, KclError> {
|
||||
// Flush the batch for our fillets/chamfers if there are any.
|
||||
// If we do not do these for sketch on face, things will fail with face does not exist.
|
||||
args.flush_batch_for_solids(exec_state, &[(*solid).clone()]).await?;
|
||||
exec_state
|
||||
.flush_batch_for_solids((&args).into(), &[(*solid).clone()])
|
||||
.await?;
|
||||
|
||||
args.batch_modeling_cmd(
|
||||
exec_state.next_uuid(),
|
||||
ModelingCmd::from(mcmd::Solid3dShellFace {
|
||||
hollow: true,
|
||||
face_ids: Vec::new(), // This is empty because we want to hollow the entire object.
|
||||
object_id: solid.id,
|
||||
shell_thickness: LengthUnit(thickness.to_mm()),
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
(&args).into(),
|
||||
ModelingCmd::from(mcmd::Solid3dShellFace {
|
||||
hollow: true,
|
||||
face_ids: Vec::new(), // This is empty because we want to hollow the entire object.
|
||||
object_id: solid.id,
|
||||
shell_thickness: LengthUnit(thickness.to_mm()),
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(solid)
|
||||
}
|
||||
|
||||
@ -18,8 +18,8 @@ use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{
|
||||
types::{ArrayLen, NumericType, PrimitiveType, RuntimeType, UnitLen},
|
||||
BasePath, ExecState, Face, GeoMeta, KclValue, Path, Plane, PlaneInfo, Point2d, Sketch, SketchSurface, Solid,
|
||||
TagEngineInfo, TagIdentifier,
|
||||
BasePath, ExecState, Face, GeoMeta, KclValue, ModelingCmdMeta, Path, Plane, PlaneInfo, Point2d, Sketch,
|
||||
SketchSurface, Solid, TagEngineInfo, TagIdentifier,
|
||||
},
|
||||
parsing::ast::types::TagNode,
|
||||
std::{
|
||||
@ -118,8 +118,8 @@ pub async fn involute_circular(exec_state: &mut ExecState, args: Args) -> Result
|
||||
|
||||
fn involute_curve(radius: f64, angle: f64) -> (f64, f64) {
|
||||
(
|
||||
radius * (angle.cos() + angle * angle.sin()),
|
||||
radius * (angle.sin() - angle * angle.cos()),
|
||||
radius * (libm::cos(angle) + angle * libm::sin(angle)),
|
||||
radius * (libm::sin(angle) - angle * libm::cos(angle)),
|
||||
)
|
||||
}
|
||||
|
||||
@ -136,19 +136,20 @@ async fn inner_involute_circular(
|
||||
) -> Result<Sketch, KclError> {
|
||||
let id = exec_state.next_uuid();
|
||||
|
||||
args.batch_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::from(mcmd::ExtendPath {
|
||||
path: sketch.id.into(),
|
||||
segment: PathSegment::CircularInvolute {
|
||||
start_radius: LengthUnit(start_radius.to_mm()),
|
||||
end_radius: LengthUnit(end_radius.to_mm()),
|
||||
angle: Angle::from_degrees(angle.to_degrees()),
|
||||
reverse: reverse.unwrap_or_default(),
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
ModelingCmdMeta::from_args_id(&args, id),
|
||||
ModelingCmd::from(mcmd::ExtendPath {
|
||||
path: sketch.id.into(),
|
||||
segment: PathSegment::CircularInvolute {
|
||||
start_radius: LengthUnit(start_radius.to_mm()),
|
||||
end_radius: LengthUnit(end_radius.to_mm()),
|
||||
angle: Angle::from_degrees(angle.to_degrees()),
|
||||
reverse: reverse.unwrap_or_default(),
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let from = sketch.current_pen_position()?;
|
||||
|
||||
@ -159,11 +160,11 @@ async fn inner_involute_circular(
|
||||
let theta = f64::sqrt(end_radius * end_radius - start_radius * start_radius) / start_radius;
|
||||
let (x, y) = involute_curve(start_radius, theta);
|
||||
|
||||
end.x = x * angle.to_radians().cos() - y * angle.to_radians().sin();
|
||||
end.y = x * angle.to_radians().sin() + y * angle.to_radians().cos();
|
||||
end.x = x * libm::cos(angle.to_radians()) - y * libm::sin(angle.to_radians());
|
||||
end.y = x * libm::sin(angle.to_radians()) + y * libm::cos(angle.to_radians());
|
||||
|
||||
end.x -= start_radius * angle.to_radians().cos();
|
||||
end.y -= start_radius * angle.to_radians().sin();
|
||||
end.x -= start_radius * libm::cos(angle.to_radians());
|
||||
end.y -= start_radius * libm::sin(angle.to_radians());
|
||||
|
||||
if reverse.unwrap_or_default() {
|
||||
end.x = -end.x;
|
||||
@ -287,17 +288,18 @@ async fn straight_line(
|
||||
};
|
||||
|
||||
let id = exec_state.next_uuid();
|
||||
args.batch_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::from(mcmd::ExtendPath {
|
||||
path: sketch.id.into(),
|
||||
segment: PathSegment::Line {
|
||||
end: KPoint2d::from(point_to_mm(point.clone())).with_z(0.0).map(LengthUnit),
|
||||
relative: !is_absolute,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
ModelingCmdMeta::from_args_id(&args, id),
|
||||
ModelingCmd::from(mcmd::ExtendPath {
|
||||
path: sketch.id.into(),
|
||||
segment: PathSegment::Line {
|
||||
end: KPoint2d::from(point_to_mm(point.clone())).with_z(0.0).map(LengthUnit),
|
||||
relative: !is_absolute,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let end = if is_absolute {
|
||||
point_to_len_unit(point, from.units)
|
||||
@ -501,8 +503,8 @@ async fn inner_angled_line_length(
|
||||
|
||||
//double check me on this one - mike
|
||||
let delta: [f64; 2] = [
|
||||
length * f64::cos(angle_degrees.to_radians()),
|
||||
length * f64::sin(angle_degrees.to_radians()),
|
||||
length * libm::cos(angle_degrees.to_radians()),
|
||||
length * libm::sin(angle_degrees.to_radians()),
|
||||
];
|
||||
let relative = true;
|
||||
|
||||
@ -510,19 +512,20 @@ async fn inner_angled_line_length(
|
||||
|
||||
let id = exec_state.next_uuid();
|
||||
|
||||
args.batch_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::from(mcmd::ExtendPath {
|
||||
path: sketch.id.into(),
|
||||
segment: PathSegment::Line {
|
||||
end: KPoint2d::from(untyped_point_to_mm(delta, from.units))
|
||||
.with_z(0.0)
|
||||
.map(LengthUnit),
|
||||
relative,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
ModelingCmdMeta::from_args_id(&args, id),
|
||||
ModelingCmd::from(mcmd::ExtendPath {
|
||||
path: sketch.id.into(),
|
||||
segment: PathSegment::Line {
|
||||
end: KPoint2d::from(untyped_point_to_mm(delta, from.units))
|
||||
.with_z(0.0)
|
||||
.map(LengthUnit),
|
||||
relative,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let current_path = Path::ToPoint {
|
||||
base: BasePath {
|
||||
@ -601,7 +604,7 @@ async fn inner_angled_line_to_x(
|
||||
}
|
||||
|
||||
let x_component = x_to.to_length_units(from.units) - from.x;
|
||||
let y_component = x_component * f64::tan(angle_degrees.to_radians());
|
||||
let y_component = x_component * libm::tan(angle_degrees.to_radians());
|
||||
let y_to = from.y + y_component;
|
||||
|
||||
let new_sketch = straight_line(
|
||||
@ -668,7 +671,7 @@ async fn inner_angled_line_to_y(
|
||||
}
|
||||
|
||||
let y_component = y_to.to_length_units(from.units) - from.y;
|
||||
let x_component = y_component / f64::tan(angle_degrees.to_radians());
|
||||
let x_component = y_component / libm::tan(angle_degrees.to_radians());
|
||||
let x_to = from.x + x_component;
|
||||
|
||||
let new_sketch = straight_line(
|
||||
@ -684,7 +687,7 @@ async fn inner_angled_line_to_y(
|
||||
pub async fn angled_line_that_intersects(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let sketch = args.get_unlabeled_kw_arg("sketch", &RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)?;
|
||||
let angle: TyF64 = args.get_kw_arg("angle", &RuntimeType::angle(), exec_state)?;
|
||||
let intersect_tag: TagIdentifier = args.get_kw_arg("intersectTag", &RuntimeType::tag_identifier(), exec_state)?;
|
||||
let intersect_tag: TagIdentifier = args.get_kw_arg("intersectTag", &RuntimeType::tagged_edge(), exec_state)?;
|
||||
let offset = args.get_kw_arg_opt("offset", &RuntimeType::length(), exec_state)?;
|
||||
let tag: Option<TagNode> = args.get_kw_arg_opt("tag", &RuntimeType::tag_decl(), exec_state)?;
|
||||
let new_sketch =
|
||||
@ -776,7 +779,7 @@ pub async fn start_sketch_on(exec_state: &mut ExecState, args: Args) -> Result<K
|
||||
&RuntimeType::Union(vec![RuntimeType::solid(), RuntimeType::plane()]),
|
||||
exec_state,
|
||||
)?;
|
||||
let face = args.get_kw_arg_opt("face", &RuntimeType::tag(), exec_state)?;
|
||||
let face = args.get_kw_arg_opt("face", &RuntimeType::tagged_face(), exec_state)?;
|
||||
|
||||
match inner_start_sketch_on(data, face, exec_state, &args).await? {
|
||||
SketchSurface::Plane(value) => Ok(KclValue::Plane { value }),
|
||||
@ -877,18 +880,19 @@ async fn make_sketch_plane_from_orientation(
|
||||
let clobber = false;
|
||||
let size = LengthUnit(60.0);
|
||||
let hide = Some(true);
|
||||
args.batch_modeling_cmd(
|
||||
plane.id,
|
||||
ModelingCmd::from(mcmd::MakePlane {
|
||||
clobber,
|
||||
origin: plane.info.origin.into(),
|
||||
size,
|
||||
x_axis: plane.info.x_axis.into(),
|
||||
y_axis: plane.info.y_axis.into(),
|
||||
hide,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
ModelingCmdMeta::from_args_id(args, plane.id),
|
||||
ModelingCmd::from(mcmd::MakePlane {
|
||||
clobber,
|
||||
origin: plane.info.origin.into(),
|
||||
size,
|
||||
x_axis: plane.info.x_axis.into(),
|
||||
y_axis: plane.info.y_axis.into(),
|
||||
hide,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Box::new(plane))
|
||||
}
|
||||
@ -920,20 +924,22 @@ pub(crate) async fn inner_start_profile(
|
||||
SketchSurface::Face(face) => {
|
||||
// Flush the batch for our fillets/chamfers if there are any.
|
||||
// If we do not do these for sketch on face, things will fail with face does not exist.
|
||||
args.flush_batch_for_solids(exec_state, &[(*face.solid).clone()])
|
||||
exec_state
|
||||
.flush_batch_for_solids((&args).into(), &[(*face.solid).clone()])
|
||||
.await?;
|
||||
}
|
||||
SketchSurface::Plane(plane) if !plane.is_standard() => {
|
||||
// Hide whatever plane we are sketching on.
|
||||
// This is especially helpful for offset planes, which would be visible otherwise.
|
||||
args.batch_end_cmd(
|
||||
exec_state.next_uuid(),
|
||||
ModelingCmd::from(mcmd::ObjectVisible {
|
||||
object_id: plane.id,
|
||||
hidden: true,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_end_cmd(
|
||||
(&args).into(),
|
||||
ModelingCmd::from(mcmd::ObjectVisible {
|
||||
object_id: plane.id,
|
||||
hidden: true,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@ -941,42 +947,47 @@ pub(crate) async fn inner_start_profile(
|
||||
let enable_sketch_id = exec_state.next_uuid();
|
||||
let path_id = exec_state.next_uuid();
|
||||
let move_pen_id = exec_state.next_uuid();
|
||||
args.batch_modeling_cmds(&[
|
||||
// Enter sketch mode on the surface.
|
||||
// We call this here so you can reuse the sketch surface for multiple sketches.
|
||||
ModelingCmdReq {
|
||||
cmd: ModelingCmd::from(mcmd::EnableSketchMode {
|
||||
animated: false,
|
||||
ortho: false,
|
||||
entity_id: sketch_surface.id(),
|
||||
adjust_camera: false,
|
||||
planar_normal: if let SketchSurface::Plane(plane) = &sketch_surface {
|
||||
// We pass in the normal for the plane here.
|
||||
let normal = plane.info.x_axis.axes_cross_product(&plane.info.y_axis);
|
||||
Some(normal.into())
|
||||
} else {
|
||||
None
|
||||
let disable_sketch_id = exec_state.next_uuid();
|
||||
exec_state
|
||||
.batch_modeling_cmds(
|
||||
(&args).into(),
|
||||
&[
|
||||
// Enter sketch mode on the surface.
|
||||
// We call this here so you can reuse the sketch surface for multiple sketches.
|
||||
ModelingCmdReq {
|
||||
cmd: ModelingCmd::from(mcmd::EnableSketchMode {
|
||||
animated: false,
|
||||
ortho: false,
|
||||
entity_id: sketch_surface.id(),
|
||||
adjust_camera: false,
|
||||
planar_normal: if let SketchSurface::Plane(plane) = &sketch_surface {
|
||||
// We pass in the normal for the plane here.
|
||||
let normal = plane.info.x_axis.axes_cross_product(&plane.info.y_axis);
|
||||
Some(normal.into())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}),
|
||||
cmd_id: enable_sketch_id.into(),
|
||||
},
|
||||
}),
|
||||
cmd_id: enable_sketch_id.into(),
|
||||
},
|
||||
ModelingCmdReq {
|
||||
cmd: ModelingCmd::from(mcmd::StartPath::default()),
|
||||
cmd_id: path_id.into(),
|
||||
},
|
||||
ModelingCmdReq {
|
||||
cmd: ModelingCmd::from(mcmd::MovePathPen {
|
||||
path: path_id.into(),
|
||||
to: KPoint2d::from(point_to_mm(at.clone())).with_z(0.0).map(LengthUnit),
|
||||
}),
|
||||
cmd_id: move_pen_id.into(),
|
||||
},
|
||||
ModelingCmdReq {
|
||||
cmd: ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable::default()),
|
||||
cmd_id: exec_state.next_uuid().into(),
|
||||
},
|
||||
])
|
||||
.await?;
|
||||
ModelingCmdReq {
|
||||
cmd: ModelingCmd::from(mcmd::StartPath::default()),
|
||||
cmd_id: path_id.into(),
|
||||
},
|
||||
ModelingCmdReq {
|
||||
cmd: ModelingCmd::from(mcmd::MovePathPen {
|
||||
path: path_id.into(),
|
||||
to: KPoint2d::from(point_to_mm(at.clone())).with_z(0.0).map(LengthUnit),
|
||||
}),
|
||||
cmd_id: move_pen_id.into(),
|
||||
},
|
||||
ModelingCmdReq {
|
||||
cmd: ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable::default()),
|
||||
cmd_id: disable_sketch_id.into(),
|
||||
},
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Convert to the units of the module. This is what the frontend expects.
|
||||
let units = exec_state.length_unit();
|
||||
@ -1080,7 +1091,11 @@ pub(crate) async fn inner_close(
|
||||
|
||||
let id = exec_state.next_uuid();
|
||||
|
||||
args.batch_modeling_cmd(id, ModelingCmd::from(mcmd::ClosePath { path_id: sketch.id }))
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
ModelingCmdMeta::from_args_id(&args, id),
|
||||
ModelingCmd::from(mcmd::ClosePath { path_id: sketch.id }),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let current_path = Path::ToPoint {
|
||||
@ -1181,26 +1196,27 @@ pub async fn absolute_arc(
|
||||
tag: Option<TagNode>,
|
||||
) -> Result<Sketch, KclError> {
|
||||
// The start point is taken from the path you are extending.
|
||||
args.batch_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::from(mcmd::ExtendPath {
|
||||
path: sketch.id.into(),
|
||||
segment: PathSegment::ArcTo {
|
||||
end: kcmc::shared::Point3d {
|
||||
x: LengthUnit(end_absolute[0].to_mm()),
|
||||
y: LengthUnit(end_absolute[1].to_mm()),
|
||||
z: LengthUnit(0.0),
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
ModelingCmdMeta::from_args_id(args, id),
|
||||
ModelingCmd::from(mcmd::ExtendPath {
|
||||
path: sketch.id.into(),
|
||||
segment: PathSegment::ArcTo {
|
||||
end: kcmc::shared::Point3d {
|
||||
x: LengthUnit(end_absolute[0].to_mm()),
|
||||
y: LengthUnit(end_absolute[1].to_mm()),
|
||||
z: LengthUnit(0.0),
|
||||
},
|
||||
interior: kcmc::shared::Point3d {
|
||||
x: LengthUnit(interior_absolute[0].to_mm()),
|
||||
y: LengthUnit(interior_absolute[1].to_mm()),
|
||||
z: LengthUnit(0.0),
|
||||
},
|
||||
relative: false,
|
||||
},
|
||||
interior: kcmc::shared::Point3d {
|
||||
x: LengthUnit(interior_absolute[0].to_mm()),
|
||||
y: LengthUnit(interior_absolute[1].to_mm()),
|
||||
z: LengthUnit(0.0),
|
||||
},
|
||||
relative: false,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let start = [from.x, from.y];
|
||||
let end = point_to_len_unit(end_absolute, from.units);
|
||||
@ -1255,20 +1271,21 @@ pub async fn relative_arc(
|
||||
}
|
||||
let ccw = a_start < a_end;
|
||||
|
||||
args.batch_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::from(mcmd::ExtendPath {
|
||||
path: sketch.id.into(),
|
||||
segment: PathSegment::Arc {
|
||||
start: a_start,
|
||||
end: a_end,
|
||||
center: KPoint2d::from(untyped_point_to_mm(center, from.units)).map(LengthUnit),
|
||||
radius: LengthUnit(from.units.adjust_to(radius, UnitLen::Mm).0),
|
||||
relative: false,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
ModelingCmdMeta::from_args_id(args, id),
|
||||
ModelingCmd::from(mcmd::ExtendPath {
|
||||
path: sketch.id.into(),
|
||||
segment: PathSegment::Arc {
|
||||
start: a_start,
|
||||
end: a_end,
|
||||
center: KPoint2d::from(untyped_point_to_mm(center, from.units)).map(LengthUnit),
|
||||
radius: LengthUnit(from.units.adjust_to(radius, UnitLen::Mm).0),
|
||||
relative: false,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let current_path = Path::Arc {
|
||||
base: BasePath {
|
||||
@ -1399,7 +1416,7 @@ async fn inner_tangential_arc_radius_angle(
|
||||
|
||||
// Calculate the end point from the angle and radius.
|
||||
// atan2 outputs radians.
|
||||
let previous_end_tangent = Angle::from_radians(f64::atan2(
|
||||
let previous_end_tangent = Angle::from_radians(libm::atan2(
|
||||
from.y - tan_previous_point[1],
|
||||
from.x - tan_previous_point[0],
|
||||
));
|
||||
@ -1424,17 +1441,18 @@ async fn inner_tangential_arc_radius_angle(
|
||||
radius.to_length_units(from.units),
|
||||
);
|
||||
|
||||
args.batch_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::from(mcmd::ExtendPath {
|
||||
path: sketch.id.into(),
|
||||
segment: PathSegment::TangentialArc {
|
||||
radius: LengthUnit(radius.to_mm()),
|
||||
offset,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
ModelingCmdMeta::from_args_id(&args, id),
|
||||
ModelingCmd::from(mcmd::ExtendPath {
|
||||
path: sketch.id.into(),
|
||||
segment: PathSegment::TangentialArc {
|
||||
radius: LengthUnit(radius.to_mm()),
|
||||
offset,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
(center, to, ccw)
|
||||
}
|
||||
};
|
||||
@ -1524,7 +1542,9 @@ async fn inner_tangential_arc_to_point(
|
||||
point
|
||||
};
|
||||
let id = exec_state.next_uuid();
|
||||
args.batch_modeling_cmd(id, tan_arc_to(&sketch, delta)).await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(ModelingCmdMeta::from_args_id(&args, id), tan_arc_to(&sketch, delta))
|
||||
.await?;
|
||||
|
||||
let current_path = Path::TangentialArcTo {
|
||||
base: BasePath {
|
||||
@ -1612,37 +1632,39 @@ async fn inner_bezier_curve(
|
||||
from.y + end[1].to_length_units(from.units),
|
||||
];
|
||||
|
||||
args.batch_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::from(mcmd::ExtendPath {
|
||||
path: sketch.id.into(),
|
||||
segment: PathSegment::Bezier {
|
||||
control1: KPoint2d::from(point_to_mm(control1)).with_z(0.0).map(LengthUnit),
|
||||
control2: KPoint2d::from(point_to_mm(control2)).with_z(0.0).map(LengthUnit),
|
||||
end: KPoint2d::from(point_to_mm(delta)).with_z(0.0).map(LengthUnit),
|
||||
relative: true,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
ModelingCmdMeta::from_args_id(&args, id),
|
||||
ModelingCmd::from(mcmd::ExtendPath {
|
||||
path: sketch.id.into(),
|
||||
segment: PathSegment::Bezier {
|
||||
control1: KPoint2d::from(point_to_mm(control1)).with_z(0.0).map(LengthUnit),
|
||||
control2: KPoint2d::from(point_to_mm(control2)).with_z(0.0).map(LengthUnit),
|
||||
end: KPoint2d::from(point_to_mm(delta)).with_z(0.0).map(LengthUnit),
|
||||
relative: true,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
to
|
||||
}
|
||||
// Absolute
|
||||
(None, None, None, Some(control1), Some(control2), Some(end)) => {
|
||||
let to = [end[0].to_length_units(from.units), end[1].to_length_units(from.units)];
|
||||
args.batch_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::from(mcmd::ExtendPath {
|
||||
path: sketch.id.into(),
|
||||
segment: PathSegment::Bezier {
|
||||
control1: KPoint2d::from(point_to_mm(control1)).with_z(0.0).map(LengthUnit),
|
||||
control2: KPoint2d::from(point_to_mm(control2)).with_z(0.0).map(LengthUnit),
|
||||
end: KPoint2d::from(point_to_mm(end)).with_z(0.0).map(LengthUnit),
|
||||
relative: false,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
ModelingCmdMeta::from_args_id(&args, id),
|
||||
ModelingCmd::from(mcmd::ExtendPath {
|
||||
path: sketch.id.into(),
|
||||
segment: PathSegment::Bezier {
|
||||
control1: KPoint2d::from(point_to_mm(control1)).with_z(0.0).map(LengthUnit),
|
||||
control2: KPoint2d::from(point_to_mm(control2)).with_z(0.0).map(LengthUnit),
|
||||
end: KPoint2d::from(point_to_mm(end)).with_z(0.0).map(LengthUnit),
|
||||
relative: false,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
to
|
||||
}
|
||||
_ => {
|
||||
@ -1702,25 +1724,27 @@ async fn inner_subtract_2d(
|
||||
args: Args,
|
||||
) -> Result<Sketch, KclError> {
|
||||
for hole_sketch in tool {
|
||||
args.batch_modeling_cmd(
|
||||
exec_state.next_uuid(),
|
||||
ModelingCmd::from(mcmd::Solid2dAddHole {
|
||||
object_id: sketch.id,
|
||||
hole_id: hole_sketch.id,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
ModelingCmdMeta::from(&args),
|
||||
ModelingCmd::from(mcmd::Solid2dAddHole {
|
||||
object_id: sketch.id,
|
||||
hole_id: hole_sketch.id,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// suggestion (mike)
|
||||
// we also hide the source hole since its essentially "consumed" by this operation
|
||||
args.batch_modeling_cmd(
|
||||
exec_state.next_uuid(),
|
||||
ModelingCmd::from(mcmd::ObjectVisible {
|
||||
object_id: hole_sketch.id,
|
||||
hidden: true,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
ModelingCmdMeta::from(&args),
|
||||
ModelingCmd::from(mcmd::ObjectVisible {
|
||||
object_id: hole_sketch.id,
|
||||
hidden: true,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(sketch)
|
||||
|
||||
@ -11,7 +11,7 @@ use crate::{
|
||||
errors::KclError,
|
||||
execution::{
|
||||
types::{NumericType, RuntimeType},
|
||||
ExecState, Helix, KclValue, Sketch, Solid,
|
||||
ExecState, Helix, KclValue, ModelingCmdMeta, Sketch, Solid,
|
||||
},
|
||||
parsing::ast::types::TagNode,
|
||||
std::{extrude::do_post_extrude, Args},
|
||||
@ -86,17 +86,18 @@ async fn inner_sweep(
|
||||
let mut solids = Vec::new();
|
||||
for sketch in &sketches {
|
||||
let id = exec_state.next_uuid();
|
||||
args.batch_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::from(mcmd::Sweep {
|
||||
target: sketch.id.into(),
|
||||
trajectory,
|
||||
sectional: sectional.unwrap_or(false),
|
||||
tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)),
|
||||
relative_to,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
ModelingCmdMeta::from_args_id(&args, id),
|
||||
ModelingCmd::from(mcmd::Sweep {
|
||||
target: sketch.id.into(),
|
||||
trajectory,
|
||||
sectional: sectional.unwrap_or(false),
|
||||
tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)),
|
||||
relative_to,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
solids.push(
|
||||
do_post_extrude(
|
||||
@ -117,14 +118,15 @@ async fn inner_sweep(
|
||||
}
|
||||
|
||||
// Hide the artifact from the sketch or helix.
|
||||
args.batch_modeling_cmd(
|
||||
exec_state.next_uuid(),
|
||||
ModelingCmd::from(mcmd::ObjectVisible {
|
||||
object_id: trajectory.into(),
|
||||
hidden: true,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
(&args).into(),
|
||||
ModelingCmd::from(mcmd::ObjectVisible {
|
||||
object_id: trajectory.into(),
|
||||
hidden: true,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(solids)
|
||||
}
|
||||
|
||||
@ -68,34 +68,33 @@ async fn inner_scale(
|
||||
// If we have a solid, flush the fillets and chamfers.
|
||||
// Only transforms needs this, it is very odd, see: https://github.com/KittyCAD/modeling-app/issues/5880
|
||||
if let SolidOrSketchOrImportedGeometry::SolidSet(solids) = &objects {
|
||||
args.flush_batch_for_solids(exec_state, solids).await?;
|
||||
exec_state.flush_batch_for_solids((&args).into(), solids).await?;
|
||||
}
|
||||
|
||||
let mut objects = objects.clone();
|
||||
for object_id in objects.ids(&args.ctx).await? {
|
||||
let id = exec_state.next_uuid();
|
||||
|
||||
args.batch_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::from(mcmd::SetObjectTransform {
|
||||
object_id,
|
||||
transforms: vec![shared::ComponentTransform {
|
||||
scale: Some(shared::TransformBy::<Point3d<f64>> {
|
||||
property: Point3d {
|
||||
x: x.unwrap_or(1.0),
|
||||
y: y.unwrap_or(1.0),
|
||||
z: z.unwrap_or(1.0),
|
||||
},
|
||||
set: false,
|
||||
is_local: !global.unwrap_or(false),
|
||||
}),
|
||||
translate: None,
|
||||
rotate_rpy: None,
|
||||
rotate_angle_axis: None,
|
||||
}],
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
(&args).into(),
|
||||
ModelingCmd::from(mcmd::SetObjectTransform {
|
||||
object_id,
|
||||
transforms: vec![shared::ComponentTransform {
|
||||
scale: Some(shared::TransformBy::<Point3d<f64>> {
|
||||
property: Point3d {
|
||||
x: x.unwrap_or(1.0),
|
||||
y: y.unwrap_or(1.0),
|
||||
z: z.unwrap_or(1.0),
|
||||
},
|
||||
set: false,
|
||||
is_local: !global.unwrap_or(false),
|
||||
}),
|
||||
translate: None,
|
||||
rotate_rpy: None,
|
||||
rotate_angle_axis: None,
|
||||
}],
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(objects)
|
||||
@ -141,34 +140,33 @@ async fn inner_translate(
|
||||
// If we have a solid, flush the fillets and chamfers.
|
||||
// Only transforms needs this, it is very odd, see: https://github.com/KittyCAD/modeling-app/issues/5880
|
||||
if let SolidOrSketchOrImportedGeometry::SolidSet(solids) = &objects {
|
||||
args.flush_batch_for_solids(exec_state, solids).await?;
|
||||
exec_state.flush_batch_for_solids((&args).into(), solids).await?;
|
||||
}
|
||||
|
||||
let mut objects = objects.clone();
|
||||
for object_id in objects.ids(&args.ctx).await? {
|
||||
let id = exec_state.next_uuid();
|
||||
|
||||
args.batch_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::from(mcmd::SetObjectTransform {
|
||||
object_id,
|
||||
transforms: vec![shared::ComponentTransform {
|
||||
translate: Some(shared::TransformBy::<Point3d<LengthUnit>> {
|
||||
property: shared::Point3d {
|
||||
x: LengthUnit(x.as_ref().map(|t| t.to_mm()).unwrap_or_default()),
|
||||
y: LengthUnit(y.as_ref().map(|t| t.to_mm()).unwrap_or_default()),
|
||||
z: LengthUnit(z.as_ref().map(|t| t.to_mm()).unwrap_or_default()),
|
||||
},
|
||||
set: false,
|
||||
is_local: !global.unwrap_or(false),
|
||||
}),
|
||||
scale: None,
|
||||
rotate_rpy: None,
|
||||
rotate_angle_axis: None,
|
||||
}],
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
(&args).into(),
|
||||
ModelingCmd::from(mcmd::SetObjectTransform {
|
||||
object_id,
|
||||
transforms: vec![shared::ComponentTransform {
|
||||
translate: Some(shared::TransformBy::<Point3d<LengthUnit>> {
|
||||
property: shared::Point3d {
|
||||
x: LengthUnit(x.as_ref().map(|t| t.to_mm()).unwrap_or_default()),
|
||||
y: LengthUnit(y.as_ref().map(|t| t.to_mm()).unwrap_or_default()),
|
||||
z: LengthUnit(z.as_ref().map(|t| t.to_mm()).unwrap_or_default()),
|
||||
},
|
||||
set: false,
|
||||
is_local: !global.unwrap_or(false),
|
||||
}),
|
||||
scale: None,
|
||||
rotate_rpy: None,
|
||||
rotate_angle_axis: None,
|
||||
}],
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(objects)
|
||||
@ -313,59 +311,59 @@ async fn inner_rotate(
|
||||
// If we have a solid, flush the fillets and chamfers.
|
||||
// Only transforms needs this, it is very odd, see: https://github.com/KittyCAD/modeling-app/issues/5880
|
||||
if let SolidOrSketchOrImportedGeometry::SolidSet(solids) = &objects {
|
||||
args.flush_batch_for_solids(exec_state, solids).await?;
|
||||
exec_state.flush_batch_for_solids((&args).into(), solids).await?;
|
||||
}
|
||||
|
||||
let mut objects = objects.clone();
|
||||
for object_id in objects.ids(&args.ctx).await? {
|
||||
let id = exec_state.next_uuid();
|
||||
|
||||
if let (Some(axis), Some(angle)) = (&axis, angle) {
|
||||
args.batch_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::from(mcmd::SetObjectTransform {
|
||||
object_id,
|
||||
transforms: vec![shared::ComponentTransform {
|
||||
rotate_angle_axis: Some(shared::TransformBy::<Point4d<f64>> {
|
||||
property: shared::Point4d {
|
||||
x: axis[0],
|
||||
y: axis[1],
|
||||
z: axis[2],
|
||||
w: angle,
|
||||
},
|
||||
set: false,
|
||||
is_local: !global.unwrap_or(false),
|
||||
}),
|
||||
scale: None,
|
||||
rotate_rpy: None,
|
||||
translate: None,
|
||||
}],
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
(&args).into(),
|
||||
ModelingCmd::from(mcmd::SetObjectTransform {
|
||||
object_id,
|
||||
transforms: vec![shared::ComponentTransform {
|
||||
rotate_angle_axis: Some(shared::TransformBy::<Point4d<f64>> {
|
||||
property: shared::Point4d {
|
||||
x: axis[0],
|
||||
y: axis[1],
|
||||
z: axis[2],
|
||||
w: angle,
|
||||
},
|
||||
set: false,
|
||||
is_local: !global.unwrap_or(false),
|
||||
}),
|
||||
scale: None,
|
||||
rotate_rpy: None,
|
||||
translate: None,
|
||||
}],
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
// Do roll, pitch, and yaw.
|
||||
args.batch_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::from(mcmd::SetObjectTransform {
|
||||
object_id,
|
||||
transforms: vec![shared::ComponentTransform {
|
||||
rotate_rpy: Some(shared::TransformBy::<Point3d<f64>> {
|
||||
property: shared::Point3d {
|
||||
x: roll.unwrap_or(0.0),
|
||||
y: pitch.unwrap_or(0.0),
|
||||
z: yaw.unwrap_or(0.0),
|
||||
},
|
||||
set: false,
|
||||
is_local: !global.unwrap_or(false),
|
||||
}),
|
||||
scale: None,
|
||||
rotate_angle_axis: None,
|
||||
translate: None,
|
||||
}],
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
exec_state
|
||||
.batch_modeling_cmd(
|
||||
(&args).into(),
|
||||
ModelingCmd::from(mcmd::SetObjectTransform {
|
||||
object_id,
|
||||
transforms: vec![shared::ComponentTransform {
|
||||
rotate_rpy: Some(shared::TransformBy::<Point3d<f64>> {
|
||||
property: shared::Point3d {
|
||||
x: roll.unwrap_or(0.0),
|
||||
y: pitch.unwrap_or(0.0),
|
||||
z: yaw.unwrap_or(0.0),
|
||||
},
|
||||
set: false,
|
||||
is_local: !global.unwrap_or(false),
|
||||
}),
|
||||
scale: None,
|
||||
rotate_angle_axis: None,
|
||||
translate: None,
|
||||
}],
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -53,7 +53,7 @@ pub(crate) fn distance(a: Coords2d, b: Coords2d) -> f64 {
|
||||
pub(crate) fn between(a: Coords2d, b: Coords2d) -> Angle {
|
||||
let x = b[0] - a[0];
|
||||
let y = b[1] - a[1];
|
||||
normalize(Angle::from_radians(y.atan2(x)))
|
||||
normalize(Angle::from_radians(libm::atan2(y, x)))
|
||||
}
|
||||
|
||||
/// Normalize the angle
|
||||
@ -106,8 +106,8 @@ pub(crate) fn normalize_rad(angle: f64) -> f64 {
|
||||
|
||||
fn calculate_intersection_of_two_lines(line1: &[Coords2d; 2], line2_angle: f64, line2_point: Coords2d) -> Coords2d {
|
||||
let line2_point_b = [
|
||||
line2_point[0] + f64::cos(line2_angle.to_radians()) * 10.0,
|
||||
line2_point[1] + f64::sin(line2_angle.to_radians()) * 10.0,
|
||||
line2_point[0] + libm::cos(line2_angle.to_radians()) * 10.0,
|
||||
line2_point[1] + libm::sin(line2_angle.to_radians()) * 10.0,
|
||||
];
|
||||
intersect(line1[0], line1[1], line2_point, line2_point_b)
|
||||
}
|
||||
@ -147,13 +147,13 @@ fn offset_line(offset: f64, p1: Coords2d, p2: Coords2d) -> [Coords2d; 2] {
|
||||
let direction = (p2[0] - p1[0]).signum();
|
||||
return [[p1[0], p1[1] + offset * direction], [p2[0], p2[1] + offset * direction]];
|
||||
}
|
||||
let x_offset = offset / f64::sin(f64::atan2(p1[1] - p2[1], p1[0] - p2[0]));
|
||||
let x_offset = offset / libm::sin(libm::atan2(p1[1] - p2[1], p1[0] - p2[0]));
|
||||
[[p1[0] + x_offset, p1[1]], [p2[0] + x_offset, p2[1]]]
|
||||
}
|
||||
|
||||
pub(crate) fn get_y_component(angle: Angle, x: f64) -> Coords2d {
|
||||
let normalised_angle = ((angle.to_degrees() % 360.0) + 360.0) % 360.0; // between 0 and 360
|
||||
let y = x * f64::tan(normalised_angle.to_radians());
|
||||
let y = x * libm::tan(normalised_angle.to_radians());
|
||||
let sign = if normalised_angle > 90.0 && normalised_angle <= 270.0 {
|
||||
-1.0
|
||||
} else {
|
||||
@ -164,7 +164,7 @@ pub(crate) fn get_y_component(angle: Angle, x: f64) -> Coords2d {
|
||||
|
||||
pub(crate) fn get_x_component(angle: Angle, y: f64) -> Coords2d {
|
||||
let normalised_angle = ((angle.to_degrees() % 360.0) + 360.0) % 360.0; // between 0 and 360
|
||||
let x = y / f64::tan(normalised_angle.to_radians());
|
||||
let x = y / libm::tan(normalised_angle.to_radians());
|
||||
let sign = if normalised_angle > 180.0 && normalised_angle <= 360.0 {
|
||||
-1.0
|
||||
} else {
|
||||
@ -183,13 +183,13 @@ pub(crate) fn arc_center_and_end(
|
||||
let end_angle = end_angle.to_radians();
|
||||
|
||||
let center = [
|
||||
-1.0 * (radius * start_angle.cos() - from[0]),
|
||||
-1.0 * (radius * start_angle.sin() - from[1]),
|
||||
-1.0 * (radius * libm::cos(start_angle) - from[0]),
|
||||
-1.0 * (radius * libm::sin(start_angle) - from[1]),
|
||||
];
|
||||
|
||||
let end = [
|
||||
center[0] + radius * end_angle.cos(),
|
||||
center[1] + radius * end_angle.sin(),
|
||||
center[0] + radius * libm::cos(end_angle),
|
||||
center[1] + radius * libm::sin(end_angle),
|
||||
];
|
||||
|
||||
(center, end)
|
||||
@ -366,7 +366,10 @@ mod tests {
|
||||
|
||||
let get_point = |radius: f64, t: f64| {
|
||||
let angle = t * TAU;
|
||||
[center[0] + radius * angle.cos(), center[1] + radius * angle.sin()]
|
||||
[
|
||||
center[0] + radius * libm::cos(angle),
|
||||
center[1] + radius * libm::sin(angle),
|
||||
]
|
||||
};
|
||||
|
||||
for radius in radius_array {
|
||||
@ -451,7 +454,7 @@ fn get_slope(start: Coords2d, end: Coords2d) -> (f64, f64) {
|
||||
fn get_angle(point1: Coords2d, point2: Coords2d) -> f64 {
|
||||
let delta_x = point2[0] - point1[0];
|
||||
let delta_y = point2[1] - point1[1];
|
||||
let angle = delta_y.atan2(delta_x);
|
||||
let angle = libm::atan2(delta_y, delta_x);
|
||||
|
||||
let result = if angle < 0.0 { angle + 2.0 * PI } else { angle };
|
||||
result * (180.0 / PI)
|
||||
@ -493,13 +496,13 @@ fn get_mid_point(
|
||||
);
|
||||
let delta_ang = delta_ang / 2.0 + deg2rad(angle_from_center_to_arc_start);
|
||||
let shortest_arc_mid_point: Coords2d = [
|
||||
delta_ang.cos() * radius + center[0],
|
||||
delta_ang.sin() * radius + center[1],
|
||||
libm::cos(delta_ang) * radius + center[0],
|
||||
libm::sin(delta_ang) * radius + center[1],
|
||||
];
|
||||
let opposite_delta = delta_ang + PI;
|
||||
let longest_arc_mid_point: Coords2d = [
|
||||
opposite_delta.cos() * radius + center[0],
|
||||
opposite_delta.sin() * radius + center[1],
|
||||
libm::cos(opposite_delta) * radius + center[0],
|
||||
libm::sin(opposite_delta) * radius + center[1],
|
||||
];
|
||||
|
||||
let rotation_direction_original_points = is_points_ccw(&[tan_previous_point, arc_start_point, arc_end_point]);
|
||||
@ -601,11 +604,14 @@ pub fn get_tangential_arc_to_info(input: TangentialArcInfoInput) -> TangentialAr
|
||||
input.obtuse,
|
||||
);
|
||||
|
||||
let start_angle = (input.arc_start_point[1] - center[1]).atan2(input.arc_start_point[0] - center[0]);
|
||||
let end_angle = (input.arc_end_point[1] - center[1]).atan2(input.arc_end_point[0] - center[0]);
|
||||
let start_angle = libm::atan2(
|
||||
input.arc_start_point[1] - center[1],
|
||||
input.arc_start_point[0] - center[0],
|
||||
);
|
||||
let end_angle = libm::atan2(input.arc_end_point[1] - center[1], input.arc_end_point[0] - center[0]);
|
||||
let ccw = is_points_ccw(&[input.arc_start_point, arc_mid_point, input.arc_end_point]);
|
||||
|
||||
let arc_mid_angle = (arc_mid_point[1] - center[1]).atan2(arc_mid_point[0] - center[0]);
|
||||
let arc_mid_angle = libm::atan2(arc_mid_point[1] - center[1], arc_mid_point[0] - center[0]);
|
||||
let start_to_mid_arc_length = radius
|
||||
* delta(Angle::from_radians(start_angle), Angle::from_radians(arc_mid_angle))
|
||||
.to_radians()
|
||||
@ -733,7 +739,7 @@ mod get_tangential_arc_to_info_tests {
|
||||
|
||||
#[test]
|
||||
fn test_get_tangential_arc_to_info_obtuse_with_wrap_around() {
|
||||
let arc_end = (std::f64::consts::PI / 4.0).cos() * 2.0;
|
||||
let arc_end = libm::cos(std::f64::consts::PI / 4.0) * 2.0;
|
||||
let result = get_tangential_arc_to_info(TangentialArcInfoInput {
|
||||
tan_previous_point: [2.0, -4.0],
|
||||
arc_start_point: [2.0, 0.0],
|
||||
@ -812,7 +818,7 @@ pub(crate) fn get_tangent_point_from_previous_arc(
|
||||
let tangential_angle = angle_from_old_center_to_arc_start + if last_arc_ccw { -90.0 } else { 90.0 };
|
||||
// What is the 10.0 constant doing???
|
||||
[
|
||||
tangential_angle.to_radians().cos() * 10.0 + last_arc_end[0],
|
||||
tangential_angle.to_radians().sin() * 10.0 + last_arc_end[1],
|
||||
libm::cos(tangential_angle.to_radians()) * 10.0 + last_arc_end[0],
|
||||
libm::sin(tangential_angle.to_radians()) * 10.0 + last_arc_end[1],
|
||||
]
|
||||
}
|
||||
|
||||
@ -3,8 +3,8 @@ use std::fmt::Write;
|
||||
use crate::{
|
||||
parsing::{
|
||||
ast::types::{
|
||||
Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryOperator,
|
||||
BinaryPart, BodyItem, CallExpressionKw, CommentStyle, DefaultParamVal, Expr, FormatOptions,
|
||||
Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, Associativity, BinaryExpression,
|
||||
BinaryOperator, BinaryPart, BodyItem, CallExpressionKw, CommentStyle, DefaultParamVal, Expr, FormatOptions,
|
||||
FunctionExpression, IfExpression, ImportSelector, ImportStatement, ItemVisibility, LabeledArg, Literal,
|
||||
LiteralIdentifier, LiteralValue, MemberExpression, Node, NonCodeNode, NonCodeValue, ObjectExpression,
|
||||
Parameter, PipeExpression, Program, TagDeclarator, TypeDeclaration, UnaryExpression, VariableDeclaration,
|
||||
@ -710,17 +710,28 @@ impl BinaryExpression {
|
||||
}
|
||||
};
|
||||
|
||||
let should_wrap_right = match &self.right {
|
||||
// It would be better to always preserve the user's parentheses but since we've dropped that
|
||||
// info from the AST, we bracket expressions as necessary.
|
||||
let should_wrap_left = match &self.left {
|
||||
BinaryPart::BinaryExpression(bin_exp) => {
|
||||
self.precedence() > bin_exp.precedence()
|
||||
|| self.operator == BinaryOperator::Sub
|
||||
|| self.operator == BinaryOperator::Div
|
||||
|| ((self.precedence() == bin_exp.precedence())
|
||||
&& (!(self.operator.associative() && self.operator == bin_exp.operator)
|
||||
&& self.operator.associativity() == Associativity::Right))
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let should_wrap_left = match &self.left {
|
||||
BinaryPart::BinaryExpression(bin_exp) => self.precedence() > bin_exp.precedence(),
|
||||
let should_wrap_right = match &self.right {
|
||||
BinaryPart::BinaryExpression(bin_exp) => {
|
||||
self.precedence() > bin_exp.precedence()
|
||||
// These two lines preserve previous reformatting behaviour.
|
||||
|| self.operator == BinaryOperator::Sub
|
||||
|| self.operator == BinaryOperator::Div
|
||||
|| ((self.precedence() == bin_exp.precedence())
|
||||
&& (!(self.operator.associative() && self.operator == bin_exp.operator)
|
||||
&& self.operator.associativity() == Associativity::Left))
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
@ -2820,4 +2831,36 @@ yo = 'bing'
|
||||
let recasted = ast.recast(&FormatOptions::new(), 0);
|
||||
assert_eq!(recasted, code);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn paren_precedence() {
|
||||
let code = r#"x = 1 - 2 - 3
|
||||
x = (1 - 2) - 3
|
||||
x = 1 - (2 - 3)
|
||||
x = 1 + 2 + 3
|
||||
x = (1 + 2) + 3
|
||||
x = 1 + (2 + 3)
|
||||
x = 2 * (y % 2)
|
||||
x = (2 * y) % 2
|
||||
x = 2 % (y * 2)
|
||||
x = (2 % y) * 2
|
||||
x = 2 * y % 2
|
||||
"#;
|
||||
|
||||
let expected = r#"x = 1 - 2 - 3
|
||||
x = 1 - 2 - 3
|
||||
x = 1 - (2 - 3)
|
||||
x = 1 + 2 + 3
|
||||
x = 1 + 2 + 3
|
||||
x = 1 + 2 + 3
|
||||
x = 2 * (y % 2)
|
||||
x = 2 * y % 2
|
||||
x = 2 % (y * 2)
|
||||
x = 2 % y * 2
|
||||
x = 2 * y % 2
|
||||
"#;
|
||||
let ast = crate::parsing::top_level_parse(code).unwrap();
|
||||
let recasted = ast.recast(&FormatOptions::new(), 0);
|
||||
assert_eq!(recasted, expected);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user