more updates for kcl-samples (#5696)
* screenshots and step Signed-off-by: Jess Frazelle <github@jessfraz.com> * automations Signed-off-by: Jess Frazelle <github@jessfraz.com> * add manifest generation Signed-off-by: Jess Frazelle <github@jessfraz.com> * small refactor Signed-off-by: Jess Frazelle <github@jessfraz.com> * update readme Signed-off-by: Jess Frazelle <github@jessfraz.com> * write the readme Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixes for comments Signed-off-by: Jess Frazelle <github@jessfraz.com> * derive-docs tests updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * update all the generated artifact commands, since we dont need to clear scene at the start of run so we dont need to recreate all the planes Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
@ -152,7 +152,7 @@ impl KclErrorWithOutputs {
|
||||
source_files: Default::default(),
|
||||
}
|
||||
}
|
||||
pub fn into_miette_report_with_outputs(self) -> anyhow::Result<ReportWithOutputs> {
|
||||
pub fn into_miette_report_with_outputs(self, code: &str) -> anyhow::Result<ReportWithOutputs> {
|
||||
let mut source_ranges = self.error.source_ranges();
|
||||
|
||||
// Pop off the first source range to get the filename.
|
||||
@ -164,12 +164,19 @@ impl KclErrorWithOutputs {
|
||||
.source_files
|
||||
.get(&first_source_range.module_id())
|
||||
.cloned()
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"Could not find source file for module id: {:?}",
|
||||
first_source_range.module_id()
|
||||
)
|
||||
})?;
|
||||
.unwrap_or(ModuleSource {
|
||||
source: code.to_string(),
|
||||
path: self
|
||||
.filenames
|
||||
.get(&first_source_range.module_id())
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"Could not find filename for module id: {:?}",
|
||||
first_source_range.module_id()
|
||||
)
|
||||
})?
|
||||
.clone(),
|
||||
});
|
||||
let filename = source.path.to_string();
|
||||
let kcl_source = source.source.to_string();
|
||||
|
||||
|
@ -625,8 +625,6 @@ impl ExecutorContext {
|
||||
let mut exec_state = old_state;
|
||||
exec_state.reset(&self.settings);
|
||||
|
||||
exec_state.add_root_module_contents(&program);
|
||||
|
||||
// We don't do this in mock mode since there is no engine connection
|
||||
// anyways and from the TS side we override memory and don't want to clear it.
|
||||
self.send_clear_scene(&mut exec_state, Default::default())
|
||||
@ -636,7 +634,6 @@ impl ExecutorContext {
|
||||
(exec_state, false)
|
||||
} else {
|
||||
old_state.mut_stack().restore_env(result_env);
|
||||
old_state.add_root_module_contents(&program);
|
||||
|
||||
(old_state, true)
|
||||
};
|
||||
@ -644,7 +641,6 @@ impl ExecutorContext {
|
||||
(program, exec_state, preserve_mem)
|
||||
} else {
|
||||
let mut exec_state = ExecState::new(&self.settings);
|
||||
exec_state.add_root_module_contents(&program);
|
||||
self.send_clear_scene(&mut exec_state, Default::default())
|
||||
.await
|
||||
.map_err(KclErrorWithOutputs::no_outputs)?;
|
||||
@ -683,28 +679,7 @@ impl ExecutorContext {
|
||||
&self,
|
||||
program: &crate::Program,
|
||||
exec_state: &mut ExecState,
|
||||
) -> Result<(EnvironmentRef, Option<ModelingSessionData>), KclError> {
|
||||
self.run_with_ui_outputs(program, exec_state)
|
||||
.await
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
/// Perform the execution of a program.
|
||||
///
|
||||
/// You can optionally pass in some initialization memory for partial
|
||||
/// execution.
|
||||
///
|
||||
/// The error includes additional outputs used for the feature tree and
|
||||
/// artifact graph.
|
||||
pub async fn run_with_ui_outputs(
|
||||
&self,
|
||||
program: &crate::Program,
|
||||
exec_state: &mut ExecState,
|
||||
) -> Result<(EnvironmentRef, Option<ModelingSessionData>), KclErrorWithOutputs> {
|
||||
exec_state.add_root_module_contents(program);
|
||||
self.send_clear_scene(exec_state, Default::default())
|
||||
.await
|
||||
.map_err(KclErrorWithOutputs::no_outputs)?;
|
||||
self.inner_run(program, exec_state, false).await
|
||||
}
|
||||
|
||||
@ -716,6 +691,8 @@ impl ExecutorContext {
|
||||
exec_state: &mut ExecState,
|
||||
preserve_mem: bool,
|
||||
) -> Result<(EnvironmentRef, Option<ModelingSessionData>), KclErrorWithOutputs> {
|
||||
exec_state.add_root_module_contents(program);
|
||||
|
||||
let _stats = crate::log::LogPerfStats::new("Interpretation");
|
||||
|
||||
// Re-apply the settings, in case the cache was busted.
|
||||
@ -882,6 +859,73 @@ impl ExecutorContext {
|
||||
Ok(contents)
|
||||
}
|
||||
|
||||
/// Export the current scene as a CAD file.
|
||||
pub async fn export(
|
||||
&self,
|
||||
format: kittycad_modeling_cmds::format::OutputFormat3d,
|
||||
) -> Result<Vec<kittycad_modeling_cmds::websocket::RawFile>, KclError> {
|
||||
let resp = self
|
||||
.engine
|
||||
.send_modeling_cmd(
|
||||
uuid::Uuid::new_v4(),
|
||||
crate::SourceRange::default(),
|
||||
&kittycad_modeling_cmds::ModelingCmd::Export(kittycad_modeling_cmds::Export {
|
||||
entity_ids: vec![],
|
||||
format,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let kittycad_modeling_cmds::websocket::OkWebSocketResponseData::Export { files } = resp else {
|
||||
return Err(KclError::Internal(crate::errors::KclErrorDetails {
|
||||
message: format!("Expected Export response, got {resp:?}",),
|
||||
source_ranges: vec![SourceRange::default()],
|
||||
}));
|
||||
};
|
||||
|
||||
Ok(files)
|
||||
}
|
||||
|
||||
/// Export the current scene as a STEP file.
|
||||
pub async fn export_step(
|
||||
&self,
|
||||
deterministic_time: bool,
|
||||
) -> Result<Vec<kittycad_modeling_cmds::websocket::RawFile>, KclError> {
|
||||
let mut files = self
|
||||
.export(kittycad_modeling_cmds::format::OutputFormat3d::Step(
|
||||
kittycad_modeling_cmds::format::step::export::Options {
|
||||
coords: *kittycad_modeling_cmds::coord::KITTYCAD,
|
||||
created: None,
|
||||
},
|
||||
))
|
||||
.await?;
|
||||
|
||||
if deterministic_time {
|
||||
for kittycad_modeling_cmds::websocket::RawFile { contents, .. } in &mut files {
|
||||
use std::fmt::Write;
|
||||
let utf8 = std::str::from_utf8(contents).unwrap();
|
||||
let mut postprocessed = String::new();
|
||||
for line in utf8.lines() {
|
||||
if line.starts_with("FILE_NAME") {
|
||||
let name = "test.step";
|
||||
let time = "2021-01-01T00:00:00Z";
|
||||
let author = "Test";
|
||||
let org = "Zoo";
|
||||
let version = "zoo.dev beta";
|
||||
let system = "zoo.dev";
|
||||
let authorization = "Test";
|
||||
writeln!(&mut postprocessed, "FILE_NAME('{name}', '{time}', ('{author}'), ('{org}'), '{version}', '{system}', '{authorization}');").unwrap();
|
||||
} else {
|
||||
writeln!(&mut postprocessed, "{line}").unwrap();
|
||||
}
|
||||
}
|
||||
*contents = postprocessed.into_bytes();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(files)
|
||||
}
|
||||
|
||||
pub async fn close(&self) {
|
||||
self.engine.close().await;
|
||||
}
|
||||
|
@ -10,8 +10,10 @@ use uuid::Uuid;
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails, Severity},
|
||||
execution::{
|
||||
annotations, kcl_value, memory::ProgramMemory, memory::Stack, Artifact, ArtifactCommand, ArtifactGraph,
|
||||
ArtifactId, EnvironmentRef, ExecOutcome, ExecutorSettings, KclValue, Operation, UnitAngle, UnitLen,
|
||||
annotations, kcl_value,
|
||||
memory::{ProgramMemory, Stack},
|
||||
Artifact, ArtifactCommand, ArtifactGraph, ArtifactId, EnvironmentRef, ExecOutcome, ExecutorSettings, KclValue,
|
||||
Operation, UnitAngle, UnitLen,
|
||||
},
|
||||
modules::{ModuleId, ModuleInfo, ModuleLoader, ModulePath, ModuleRepr, ModuleSource},
|
||||
parsing::ast::types::Annotation,
|
||||
|
@ -26,6 +26,9 @@ struct Test {
|
||||
output_dir: PathBuf,
|
||||
}
|
||||
|
||||
pub(crate) const RENDERED_MODEL_NAME: &str = "rendered_model.png";
|
||||
//pub(crate) const EXPORTED_STEP_NAME: &str = "exported_step.step";
|
||||
|
||||
impl Test {
|
||||
fn new(name: &str) -> Self {
|
||||
Self {
|
||||
@ -116,10 +119,10 @@ fn unparse_test(test: &Test) {
|
||||
}
|
||||
|
||||
async fn execute(test_name: &str, render_to_png: bool) {
|
||||
execute_test(&Test::new(test_name), render_to_png).await
|
||||
execute_test(&Test::new(test_name), render_to_png, false).await
|
||||
}
|
||||
|
||||
async fn execute_test(test: &Test, render_to_png: bool) {
|
||||
async fn execute_test(test: &Test, render_to_png: bool, export_step: bool) {
|
||||
// Read the AST from disk.
|
||||
let input = read("ast.snap", &test.output_dir);
|
||||
let ast_res: Result<Node<Program>, KclError> = get(&input);
|
||||
@ -136,16 +139,26 @@ async fn execute_test(test: &Test, render_to_png: bool) {
|
||||
ast,
|
||||
crate::settings::types::UnitLength::Mm,
|
||||
Some(test.input_dir.join(&test.entry_point)),
|
||||
export_step,
|
||||
)
|
||||
.await;
|
||||
match exec_res {
|
||||
Ok((exec_state, env_ref, png)) => {
|
||||
Ok((exec_state, env_ref, png, step)) => {
|
||||
let fail_path = test.output_dir.join("execution_error.snap");
|
||||
if std::fs::exists(&fail_path).unwrap() {
|
||||
panic!("This test case is expected to fail, but it passed. If this is intended, and the test should actually be passing now, please delete kcl-lib/{}", fail_path.to_string_lossy())
|
||||
}
|
||||
if render_to_png {
|
||||
twenty_twenty::assert_image(test.output_dir.join("rendered_model.png"), &png, 0.99);
|
||||
twenty_twenty::assert_image(test.output_dir.join(RENDERED_MODEL_NAME), &png, 0.99);
|
||||
}
|
||||
if export_step {
|
||||
let step = step.unwrap();
|
||||
// TODO FIXME: This is failing because the step file is not deterministic.
|
||||
// But it should be, talk to @katie
|
||||
/*assert_snapshot(test, "Step file", || {
|
||||
insta::assert_binary_snapshot!(EXPORTED_STEP_NAME, step);
|
||||
});*/
|
||||
std::fs::write(test.output_dir.join("exported_step.snap.step"), step).unwrap();
|
||||
}
|
||||
let outcome = exec_state.to_wasm_outcome(env_ref);
|
||||
assert_common_snapshots(
|
||||
@ -179,7 +192,7 @@ async fn execute_test(test: &Test, render_to_png: bool) {
|
||||
Box::new(miette::MietteHandlerOpts::new().show_related_errors_as_nested().build())
|
||||
}))
|
||||
.unwrap();
|
||||
let report = error.clone().into_miette_report_with_outputs().unwrap();
|
||||
let report = error.clone().into_miette_report_with_outputs(&input).unwrap();
|
||||
let report = miette::Report::new(report);
|
||||
if previously_passed {
|
||||
eprintln!("This test case failed, but it previously passed. If this is intended, and the test should actually be failing now, please delete kcl-lib/{} and other associated passing artifacts", ok_path.to_string_lossy());
|
||||
|
@ -4,10 +4,14 @@
|
||||
//! the samples, in this case, all those that start with "gear".
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs,
|
||||
io::Write,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use fnv::FnvHashSet;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::task::JoinSet;
|
||||
|
||||
use super::Test;
|
||||
@ -73,7 +77,7 @@ async fn kcl_test_execute() {
|
||||
// Spawn a task for each test.
|
||||
for (index, test) in tests.iter().cloned().enumerate() {
|
||||
let handle = tasks.spawn(async move {
|
||||
super::execute_test(&test, true).await;
|
||||
super::execute_test(&test, true, true).await;
|
||||
});
|
||||
id_to_index.insert(handle.id(), index);
|
||||
}
|
||||
@ -99,6 +103,53 @@ async fn kcl_test_execute() {
|
||||
.filter(|name| !input_names.contains(name))
|
||||
.collect::<Vec<_>>();
|
||||
assert!(missing.is_empty(), "Expected input kcl-samples for the following. If these are no longer tests, delete the expected output directories for them in {}: {missing:?}", OUTPUTS_DIR.to_string_lossy());
|
||||
|
||||
// We want to move the step and screenshot for the inputs to the public/kcl-samples
|
||||
// directory so that they can be used as inputs for the next run.
|
||||
// First ensure each directory exists.
|
||||
let public_screenshot_dir = INPUTS_DIR.join("screenshots");
|
||||
let public_step_dir = INPUTS_DIR.join("step");
|
||||
for dir in [&public_step_dir, &public_screenshot_dir] {
|
||||
if !dir.exists() {
|
||||
std::fs::create_dir_all(dir).unwrap();
|
||||
}
|
||||
}
|
||||
for tests in &tests {
|
||||
let screenshot_file = OUTPUTS_DIR.join(&tests.name).join(super::RENDERED_MODEL_NAME);
|
||||
if !screenshot_file.exists() {
|
||||
panic!("Missing screenshot for test: {}", tests.name);
|
||||
}
|
||||
std::fs::copy(
|
||||
screenshot_file,
|
||||
public_screenshot_dir.join(format!("{}.png", &tests.name)),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let step_file = OUTPUTS_DIR.join(&tests.name).join("exported_step.snap.step");
|
||||
if !step_file.exists() {
|
||||
panic!("Missing step for test: {}", tests.name);
|
||||
}
|
||||
std::fs::copy(step_file, public_step_dir.join(format!("{}.step", &tests.name))).unwrap();
|
||||
}
|
||||
|
||||
// Update the README.md with the new screenshots and steps.
|
||||
let mut new_content = String::new();
|
||||
for test in tests {
|
||||
// Format:
|
||||
new_content.push_str(&format!(
|
||||
r#"#### [{}]({}/main.kcl) ([step](step/{}.step)) ([screenshot](screenshots/{}.png))
|
||||
[]({}/main.kcl)
|
||||
"#,
|
||||
test.name, test.name, test.name, test.name, test.name, test.name, test.name
|
||||
));
|
||||
}
|
||||
update_readme(&INPUTS_DIR, &new_content).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_manifest() {
|
||||
// Generate the manifest.json
|
||||
generate_kcl_manifest(&INPUTS_DIR).unwrap();
|
||||
}
|
||||
|
||||
fn test(test_name: &str, entry_point: String) -> Test {
|
||||
@ -116,8 +167,19 @@ fn filter_from_env() -> Option<String> {
|
||||
|
||||
fn kcl_samples_inputs(filter: Option<&str>) -> Vec<Test> {
|
||||
let mut tests = Vec::new();
|
||||
for entry in INPUTS_DIR.read_dir().unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
|
||||
// Collect all directory entries first and sort them by name for consistent ordering
|
||||
let mut entries: Vec<_> = INPUTS_DIR
|
||||
.read_dir()
|
||||
.unwrap()
|
||||
.filter_map(Result::ok)
|
||||
.filter(|e| e.path().is_dir())
|
||||
.collect();
|
||||
|
||||
// Sort directories by name for consistent ordering
|
||||
entries.sort_by_key(|a| a.file_name());
|
||||
|
||||
for entry in entries {
|
||||
let path = entry.path();
|
||||
if !path.is_dir() {
|
||||
// We're looking for directories only.
|
||||
@ -146,7 +208,7 @@ fn kcl_samples_inputs(filter: Option<&str>) -> Vec<Test> {
|
||||
let entry_point = if sub_dir.join("main.kcl").exists() {
|
||||
"main.kcl".to_owned()
|
||||
} else {
|
||||
format!("{dir_name_str}.kcl")
|
||||
panic!("No main.kcl found in {:?}", sub_dir);
|
||||
};
|
||||
tests.push(test(&dir_name_str, entry_point));
|
||||
}
|
||||
@ -184,3 +246,152 @@ fn kcl_samples_outputs(filter: Option<&str>) -> Vec<String> {
|
||||
|
||||
outputs
|
||||
}
|
||||
|
||||
const MANIFEST_FILE: &str = "manifest.json";
|
||||
const COMMENT_PREFIX: &str = "//";
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct KclMetadata {
|
||||
file: String,
|
||||
path_from_project_directory_to_first_file: String,
|
||||
multiple_files: bool,
|
||||
title: String,
|
||||
description: String,
|
||||
}
|
||||
|
||||
// Function to read and parse .kcl files
|
||||
fn get_kcl_metadata(project_path: &Path, files: &[String]) -> Option<KclMetadata> {
|
||||
// Find primary kcl file (main.kcl or first sorted file)
|
||||
let primary_kcl_file = files
|
||||
.iter()
|
||||
.find(|file| file.contains("main.kcl"))
|
||||
.unwrap_or_else(|| files.iter().min().unwrap())
|
||||
.clone();
|
||||
|
||||
let full_path_to_primary_kcl = project_path.join(&primary_kcl_file);
|
||||
|
||||
// Read the file content
|
||||
let content = match fs::read_to_string(&full_path_to_primary_kcl) {
|
||||
Ok(content) => content,
|
||||
Err(_) => return None,
|
||||
};
|
||||
|
||||
let lines: Vec<&str> = content.lines().collect();
|
||||
|
||||
if lines.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Extract title and description from the first two lines
|
||||
let title = lines[0].trim_start_matches(COMMENT_PREFIX).trim().to_string();
|
||||
let description = lines[1].trim_start_matches(COMMENT_PREFIX).trim().to_string();
|
||||
|
||||
// Get the path components
|
||||
let path_components: Vec<String> = full_path_to_primary_kcl
|
||||
.components()
|
||||
.map(|comp| comp.as_os_str().to_string_lossy().to_string())
|
||||
.collect();
|
||||
|
||||
// Get the last two path components
|
||||
let len = path_components.len();
|
||||
let path_from_project_dir = if len >= 2 {
|
||||
format!("{}/{}", path_components[len - 2], path_components[len - 1])
|
||||
} else {
|
||||
primary_kcl_file.clone()
|
||||
};
|
||||
|
||||
Some(KclMetadata {
|
||||
file: primary_kcl_file,
|
||||
path_from_project_directory_to_first_file: path_from_project_dir,
|
||||
multiple_files: files.len() > 1,
|
||||
title,
|
||||
description,
|
||||
})
|
||||
}
|
||||
|
||||
// Function to scan the directory and generate the manifest.json
|
||||
fn generate_kcl_manifest(dir: &Path) -> Result<()> {
|
||||
let mut manifest = Vec::new();
|
||||
|
||||
// Collect all directory entries first and sort them by name for consistent ordering
|
||||
let mut entries: Vec<_> = fs::read_dir(dir)?
|
||||
.filter_map(Result::ok)
|
||||
.filter(|e| e.path().is_dir())
|
||||
.collect();
|
||||
|
||||
// Sort directories by name for consistent ordering
|
||||
entries.sort_by_key(|a| a.file_name());
|
||||
|
||||
for entry in entries {
|
||||
let project_path = entry.path();
|
||||
|
||||
if project_path.is_dir() {
|
||||
// Get all .kcl files in the directory
|
||||
let files: Vec<String> = fs::read_dir(&project_path)?
|
||||
.filter_map(Result::ok)
|
||||
.filter(|e| {
|
||||
if let Some(ext) = e.path().extension() {
|
||||
ext == "kcl"
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.map(|e| e.file_name().to_string_lossy().to_string())
|
||||
.collect();
|
||||
|
||||
if files.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(metadata) = get_kcl_metadata(&project_path, &files) {
|
||||
manifest.push(metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write the manifest.json
|
||||
let output_path = dir.join(MANIFEST_FILE);
|
||||
let manifest_json = serde_json::to_string_pretty(&manifest)?;
|
||||
|
||||
let mut file = fs::File::create(output_path.clone())?;
|
||||
file.write_all(manifest_json.as_bytes())?;
|
||||
|
||||
println!(
|
||||
"Manifest of {} items written to {}",
|
||||
manifest.len(),
|
||||
output_path.display()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Updates README.md by finding a specific search string and replacing all content after it
|
||||
/// with the new content provided.
|
||||
fn update_readme(dir: &Path, new_content: &str) -> Result<()> {
|
||||
let search_str = "---\n";
|
||||
let readme_path = dir.join("README.md");
|
||||
|
||||
// Read the file content
|
||||
let content = fs::read_to_string(&readme_path)?;
|
||||
|
||||
// Find the line containing the search string
|
||||
let Some(index) = content.find(search_str) else {
|
||||
anyhow::bail!(
|
||||
"Search string '{}' not found in `{}`",
|
||||
search_str,
|
||||
readme_path.display()
|
||||
);
|
||||
};
|
||||
|
||||
// Get the position just after the search string
|
||||
let position = index + search_str.len();
|
||||
|
||||
// Create the updated content
|
||||
let updated_content = format!("{}{}\n", &content[..position], new_content);
|
||||
|
||||
// Write the modified content back to the file
|
||||
std::fs::write(readme_path, updated_content)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -40,11 +40,26 @@ pub async fn execute_and_snapshot_ast(
|
||||
ast: Program,
|
||||
units: UnitLength,
|
||||
current_file: Option<PathBuf>,
|
||||
) -> Result<(ExecState, EnvironmentRef, image::DynamicImage), ExecErrorWithState> {
|
||||
with_export_step: bool,
|
||||
) -> Result<(ExecState, EnvironmentRef, image::DynamicImage, Option<Vec<u8>>), ExecErrorWithState> {
|
||||
let ctx = new_context(units, true, current_file).await?;
|
||||
let res = do_execute_and_snapshot(&ctx, ast).await;
|
||||
let (exec_state, env, img) = do_execute_and_snapshot(&ctx, ast).await?;
|
||||
let mut step = None;
|
||||
if with_export_step {
|
||||
let files = match ctx.export_step(true).await {
|
||||
Ok(f) => f,
|
||||
Err(err) => {
|
||||
return Err(ExecErrorWithState::new(
|
||||
ExecError::BadExport(format!("Export failed: {:?}", err)),
|
||||
exec_state.clone(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
step = files.into_iter().next().map(|f| f.contents);
|
||||
}
|
||||
ctx.close().await;
|
||||
res
|
||||
Ok((exec_state, env, img, step))
|
||||
}
|
||||
|
||||
pub async fn execute_and_snapshot_no_auth(
|
||||
@ -68,7 +83,7 @@ async fn do_execute_and_snapshot(
|
||||
) -> Result<(ExecState, EnvironmentRef, image::DynamicImage), ExecErrorWithState> {
|
||||
let mut exec_state = ExecState::new(&ctx.settings);
|
||||
let result = ctx
|
||||
.run_with_ui_outputs(&program, &mut exec_state)
|
||||
.run(&program, &mut exec_state)
|
||||
.await
|
||||
.map_err(|err| ExecErrorWithState::new(err.into(), exec_state.clone()))?;
|
||||
for e in exec_state.errors() {
|
||||
@ -93,8 +108,6 @@ async fn do_execute_and_snapshot(
|
||||
.and_then(|x| x.decode().map_err(|e| ExecError::BadPng(e.to_string())))
|
||||
.map_err(|err| ExecErrorWithState::new(err, exec_state.clone()))?;
|
||||
|
||||
ctx.close().await;
|
||||
|
||||
Ok((exec_state, result.0, img))
|
||||
}
|
||||
|
||||
@ -147,7 +160,7 @@ pub async fn execute_and_export_step(
|
||||
let program = Program::parse_no_errs(code)
|
||||
.map_err(|err| ExecErrorWithState::new(KclErrorWithOutputs::no_outputs(err).into(), exec_state.clone()))?;
|
||||
let result = ctx
|
||||
.run_with_ui_outputs(&program, &mut exec_state)
|
||||
.run(&program, &mut exec_state)
|
||||
.await
|
||||
.map_err(|err| ExecErrorWithState::new(err.into(), exec_state.clone()))?;
|
||||
for e in exec_state.errors() {
|
||||
@ -159,56 +172,15 @@ pub async fn execute_and_export_step(
|
||||
}
|
||||
}
|
||||
|
||||
let resp = ctx
|
||||
.engine
|
||||
.send_modeling_cmd(
|
||||
uuid::Uuid::new_v4(),
|
||||
crate::SourceRange::default(),
|
||||
&kittycad_modeling_cmds::ModelingCmd::Export(kittycad_modeling_cmds::Export {
|
||||
entity_ids: vec![],
|
||||
format: kittycad_modeling_cmds::format::OutputFormat3d::Step(
|
||||
kittycad_modeling_cmds::format::step::export::Options {
|
||||
coords: *kittycad_modeling_cmds::coord::KITTYCAD,
|
||||
// We want all to have the same timestamp.
|
||||
created: Some(
|
||||
chrono::DateTime::parse_from_rfc3339("2021-01-01T00:00:00Z")
|
||||
.unwrap()
|
||||
.into(),
|
||||
),
|
||||
},
|
||||
),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| ExecErrorWithState::new(KclErrorWithOutputs::no_outputs(err).into(), exec_state.clone()))?;
|
||||
|
||||
let kittycad_modeling_cmds::websocket::OkWebSocketResponseData::Export { mut files } = resp else {
|
||||
return Err(ExecErrorWithState::new(
|
||||
ExecError::BadExport(format!("Expected export response, got: {:?}", resp)),
|
||||
exec_state.clone(),
|
||||
));
|
||||
};
|
||||
|
||||
for kittycad_modeling_cmds::websocket::RawFile { contents, .. } in &mut files {
|
||||
use std::fmt::Write;
|
||||
let utf8 = std::str::from_utf8(contents).unwrap();
|
||||
let mut postprocessed = String::new();
|
||||
for line in utf8.lines() {
|
||||
if line.starts_with("FILE_NAME") {
|
||||
let name = "test.step";
|
||||
let time = "2021-01-01T00:00:00Z";
|
||||
let author = "Test";
|
||||
let org = "Zoo";
|
||||
let version = "zoo.dev beta";
|
||||
let system = "zoo.dev";
|
||||
let authorization = "Test";
|
||||
writeln!(&mut postprocessed, "FILE_NAME('{name}', '{time}', ('{author}'), ('{org}'), '{version}', '{system}', '{authorization}');").unwrap();
|
||||
} else {
|
||||
writeln!(&mut postprocessed, "{line}").unwrap();
|
||||
}
|
||||
let files = match ctx.export_step(true).await {
|
||||
Ok(f) => f,
|
||||
Err(err) => {
|
||||
return Err(ExecErrorWithState::new(
|
||||
ExecError::BadExport(format!("Export failed: {:?}", err)),
|
||||
exec_state.clone(),
|
||||
));
|
||||
}
|
||||
*contents = postprocessed.into_bytes();
|
||||
}
|
||||
};
|
||||
|
||||
ctx.close().await;
|
||||
|
||||
|
Reference in New Issue
Block a user