Compare commits

...

13 Commits

Author SHA1 Message Date
c64c4705ef Write prepareToEdit callback for Fillet 2025-03-11 16:20:42 -04:00
a122128e0b Add codeAtRange utility 2025-03-11 16:20:27 -04:00
0fe79ea7cb Allow nodeToEdit in Fillet command 2025-03-11 16:20:06 -04:00
021e4b9565 Remove Selection from headerArguments in edit flow tests 2025-03-11 15:21:38 -04:00
e57783483a Hide selection type args if nodeToEdit is present
in workflows that have an edit flow set up
2025-03-11 15:21:14 -04:00
e952148cd2 Allow hidden config to be a callback 2025-03-11 15:19:35 -04:00
b5028f7aa8 Change to not expose exec artifacts (#5700)
Now that the artifact graph is built completely in Rust, these are an
implementation detail that shouldn't be exposed to TS.
2025-03-11 19:09:11 +00:00
df6b4f4c37 Move consts to dir in docs (#5753)
* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* add consts to dir

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>

* add consts to dir

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-03-11 18:44:27 +00:00
41eb64925b Improve sketch constraint test (#5756) 2025-03-11 14:14:03 -04:00
fc076173ff Make any error show on ErrorPage, not just route error responses (#5623)
* Make any error show on ErrorPage, not just route error responses

Closes #5620 by making any error show on the page so the user can copy
it for use in an issue. Previously, the logic was too narrow and only
showed the error on the page if `isRouterErrorResponse` was true.

* @nadr0's feedback

thanks for giving my crap work a review

* Update src/components/ErrorPage.tsx

Co-authored-by: Jonathan Tran <jonnytran@gmail.com>

* @nadr0 feedback, handle overflowing error case

---------

Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
Co-authored-by: Kevin Nadro <nadr0@users.noreply.github.com>
2025-03-11 17:46:07 +00:00
98822869f7 fix settings docs names (#5751)
* fix settings docs names

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* add consts to dir

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-11 10:20:18 -07:00
df0510c199 cleandocs (#5750)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-03-11 08:51:23 -07:00
fda65bcbd7 codspeed (#5741)
* codspeed

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* Update cargo-bench.yml

* Update cargo-bench.yml

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* Update settings.md (#5743)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-11 08:03:49 -07:00
42 changed files with 562 additions and 427 deletions

View File

@ -40,13 +40,16 @@ jobs:
- name: Install dependencies
run: |
cargo install cargo-criterion
sudo apt update
sudo apt install -y valgrind
- uses: boa-dev/criterion-compare-action@v3
cargo install cargo-codspeed
cd rust/kcl-lib
cargo add --dev codspeed-criterion-compat --rename criterion
- name: Build the benchmark target(s)
run: |
cd rust
cargo codspeed build
- name: Run the benchmarks
uses: CodSpeedHQ/action@v3
with:
cwd: "rust"
defaultFeatures: true
# Needed. The name of the branch to compare with. This default uses the branch which is being pulled against
branchName: ${{ github.base_ref }}
env:
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
working-directory: rust
run: KITTYCAD_API_TOKEN=${{secrets.KITTYCAD_API_TOKEN}} cargo codspeed run
token: ${{ secrets.CODSPEED_TOKEN }}

View File

@ -40,9 +40,13 @@ jobs:
# cleanup old
rm -rf documentation/content/pages/docs/kcl/*.md
rm -rf documentation/content/pages/docs/kcl/types
rm -rf documentation/content/pages/docs/kcl/settings
rm -rf documentation/content/pages/docs/kcl/consts
# move new
mv -f docs/kcl/*.md documentation/content/pages/docs/kcl/
mv -f docs/kcl/types documentation/content/pages/docs/kcl/
mv -f docs/kcl/settings documentation/content/pages/docs/kcl/
mv -f docs/kcl/consts documentation/content/pages/docs/kcl/
- name: move kcl-samples
shell: bash
run: |

25
docs/kcl/consts.md Normal file
View File

@ -0,0 +1,25 @@
---
title: "KCL Constants"
excerpt: "Documentation for the KCL constants."
layout: manual
---
## Table of Contents
### `std`
- [`HALF_TURN`](/docs/kcl/consts/std-HALF_TURN)
- [`QUARTER_TURN`](/docs/kcl/consts/std-QUARTER_TURN)
- [`THREE_QUARTER_TURN`](/docs/kcl/consts/std-THREE_QUARTER_TURN)
- [`XY`](/docs/kcl/consts/std-XY)
- [`XZ`](/docs/kcl/consts/std-XZ)
- [`YZ`](/docs/kcl/consts/std-YZ)
- [`ZERO`](/docs/kcl/consts/std-ZERO)
### `std::math`
- [`E`](/docs/kcl/consts/std-math-E)
- [`PI`](/docs/kcl/consts/std-math-PI)
- [`TAU`](/docs/kcl/consts/std-math-TAU)

View File

@ -12,25 +12,26 @@ layout: manual
* [`Modules`](kcl/modules)
* [`Settings`](kcl/settings)
* [`Known Issues`](kcl/known-issues)
* [`Constants`](kcl/consts)
### Standard library
* **Primitive types**
* [`bool`](kcl/bool)
* [`number`](kcl/number)
* [`string`](kcl/string)
* [`tag`](kcl/tag)
* [`bool`](kcl/types/bool)
* [`number`](kcl/types/number)
* [`string`](kcl/types/string)
* [`tag`](kcl/types/tag)
* **std**
* [`HALF_TURN`](kcl/const_std-HALF_TURN)
* [`Plane`](kcl/Plane)
* [`QUARTER_TURN`](kcl/const_std-QUARTER_TURN)
* [`Sketch`](kcl/Sketch)
* [`Solid`](kcl/Solid)
* [`THREE_QUARTER_TURN`](kcl/const_std-THREE_QUARTER_TURN)
* [`XY`](kcl/const_std-XY)
* [`XZ`](kcl/const_std-XZ)
* [`YZ`](kcl/const_std-YZ)
* [`ZERO`](kcl/const_std-ZERO)
* [`HALF_TURN`](kcl/consts/std-HALF_TURN)
* [`Plane`](kcl/types/Plane)
* [`QUARTER_TURN`](kcl/consts/std-QUARTER_TURN)
* [`Sketch`](kcl/types/Sketch)
* [`Solid`](kcl/types/Solid)
* [`THREE_QUARTER_TURN`](kcl/consts/std-THREE_QUARTER_TURN)
* [`XY`](kcl/consts/std-XY)
* [`XZ`](kcl/consts/std-XZ)
* [`YZ`](kcl/consts/std-YZ)
* [`ZERO`](kcl/consts/std-ZERO)
* [`abs`](kcl/abs)
* [`acos`](kcl/acos)
* [`angleToMatchLengthX`](kcl/angleToMatchLengthX)
@ -134,9 +135,9 @@ layout: manual
* [`yLine`](kcl/yLine)
* [`yd`](kcl/yd)
* **std::math**
* [`E`](kcl/const_std-math-E)
* [`PI`](kcl/const_std-math-PI)
* [`TAU`](kcl/const_std-math-TAU)
* [`E`](kcl/consts/std-math-E)
* [`PI`](kcl/consts/std-math-PI)
* [`TAU`](kcl/consts/std-math-TAU)
* [`cos`](kcl/std-math-cos)
* [`sin`](kcl/std-math-sin)
* [`tan`](kcl/std-math-tan)

View File

@ -1,5 +1,5 @@
---
title: "KCL settings"
title: "KCL Settings"
excerpt: "Documentation of settings for the KCL language and Zoo Modeling App."
layout: manual
---
@ -8,16 +8,16 @@ layout: manual
There are three levels of settings available in the KittyCAD modeling application:
1. [User Settings](/docs/kcl/settings/user.toml): Global settings that apply to all projects, stored in `user.toml`
2. [Project Settings](/docs/kcl/settings/project.toml): Settings specific to a project, stored in `project.toml`
1. [User Settings](/docs/kcl/settings/user): Global settings that apply to all projects, stored in `user.toml`
2. [Project Settings](/docs/kcl/settings/project): Settings specific to a project, stored in `project.toml`
3. Per-file Settings: Settings that apply to a single KCL file, specified using the `@settings` attribute
## Configuration Files
The KittyCAD modeling app uses TOML files for configuration:
* **User Settings**: `user.toml` - See [complete documentation](/docs/kcl/settings/user.toml)
* **Project Settings**: `project.toml` - See [complete documentation](/docs/kcl/settings/project.toml)
* **User Settings**: `user.toml` - See [complete documentation](/docs/kcl/settings/user)
* **Project Settings**: `project.toml` - See [complete documentation](/docs/kcl/settings/project)
## Per-file settings

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -274,7 +274,6 @@ test.describe('Feature Tree pane', () => {
currentArgKey: 'distance',
currentArgValue: initialInput,
headerArguments: {
Selection: '1 face',
Distance: initialInput,
},
highlightedHeaderArg: 'distance',
@ -291,7 +290,6 @@ test.describe('Feature Tree pane', () => {
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Selection: '1 face',
// The calculated value is shown in the argument summary
Distance: initialInput,
},

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View File

@ -29,5 +29,5 @@
}
}
],
"kcl_version": "0.2.47"
"kcl_version": "0.2.48"
}

View File

@ -1127,7 +1127,6 @@ test.describe('Electron constraint tests', () => {
path.join(bracketDir, 'main.kcl')
)
})
const [clickHandler] = scene.makeMouseHelpers(600, 300)
await test.step('setup test', async () => {
await homePage.expectState({
@ -1144,8 +1143,12 @@ test.describe('Electron constraint tests', () => {
})
await test.step('Double click to constrain', async () => {
await clickHandler()
await page.getByRole('button', { name: 'Edit Sketch' }).click()
// Enter sketch edit mode via feature tree
await toolbar.openPane('feature-tree')
const op = await toolbar.getFeatureTreeOperation('Sketch', 0)
await op.dblclick()
await toolbar.closePane('feature-tree')
const child = page
.locator('.segment-length-label-text')
.first()

View File

@ -3,7 +3,7 @@
# This way we don't start and stop too many engine instances, putting pressure on our cloud.
uses-engine = { max-threads = 4 }
# If a test must run after the engine tests, we want to make sure the engine tests are done first.
after-engine = { depends-on = ["uses-engine"], max-threads = 12 }
after-engine = { max-threads = 12 }
[profile.default]
slow-timeout = { period = "30s", terminate-after = 1 }

View File

@ -21,7 +21,7 @@ use crate::{
};
const TYPES_DIR: &str = "../../docs/kcl/types";
const LANG_TOPICS: [&str; 4] = ["Types", "Modules", "Settings", "Known Issues"];
const LANG_TOPICS: [&str; 5] = ["Types", "Modules", "Settings", "Known Issues", "Constants"];
// These types are declared in std.
const DECLARED_TYPES: [&str; 7] = ["number", "string", "tag", "bool", "Sketch", "Solid", "Plane"];
@ -298,6 +298,7 @@ fn init_handlebars() -> Result<handlebars::Handlebars<'static>> {
hbs.register_template_string("propertyType", include_str!("templates/propertyType.hbs"))?;
hbs.register_template_string("schema", include_str!("templates/schema.hbs"))?;
hbs.register_template_string("index", include_str!("templates/index.hbs"))?;
hbs.register_template_string("consts-index", include_str!("templates/consts-index.hbs"))?;
hbs.register_template_string("function", include_str!("templates/function.hbs"))?;
hbs.register_template_string("const", include_str!("templates/const.hbs"))?;
hbs.register_template_string("type", include_str!("templates/type.hbs"))?;
@ -312,6 +313,9 @@ fn generate_index(combined: &IndexMap<String, Box<dyn StdLibFn>>, kcl_lib: &[Doc
let mut functions = HashMap::new();
functions.insert("std".to_owned(), Vec::new());
let mut constants = HashMap::new();
constants.insert("std".to_owned(), Vec::new());
for key in combined.keys() {
let internal_fn = combined
.get(key)
@ -337,6 +341,13 @@ fn generate_index(combined: &IndexMap<String, Box<dyn StdLibFn>>, kcl_lib: &[Doc
DocData::Const(c) => (c.name.clone(), d.file_name()),
DocData::Ty(t) => (t.name.clone(), d.file_name()),
});
if let DocData::Const(c) = d {
constants
.entry(d.mod_name())
.or_default()
.push((c.name.clone(), d.file_name()));
}
}
// TODO we should sub-divide into types, constants, and functions.
@ -362,7 +373,7 @@ fn generate_index(combined: &IndexMap<String, Box<dyn StdLibFn>>, kcl_lib: &[Doc
.map(|name| {
json!({
"name": name,
"file_name": name.to_lowercase().replace(' ', "-"),
"file_name": name.to_lowercase().replace(' ', "-").replace("constants", "consts"),
})
})
.collect();
@ -375,6 +386,31 @@ fn generate_index(combined: &IndexMap<String, Box<dyn StdLibFn>>, kcl_lib: &[Doc
expectorate::assert_contents("../../docs/kcl/index.md", &output);
// Generate the index for the constants.
let mut sorted_consts: Vec<_> = constants
.into_iter()
.map(|(m, mut consts)| {
consts.sort();
let val = json!({
"name": m,
"consts": consts.into_iter().map(|(n, f)| json!({
"name": n,
"file_name": f,
})).collect::<Vec<_>>(),
});
(m, val)
})
.collect();
sorted_consts.sort_by(|t1, t2| t1.0.cmp(&t2.0));
let data: Vec<_> = sorted_consts.into_iter().map(|(_, val)| val).collect();
let data = json!({
"consts": data,
});
let output = hbs.render("consts-index", &data)?;
expectorate::assert_contents("../../docs/kcl/consts.md", &output);
Ok(())
}
@ -405,7 +441,7 @@ fn generate_example(index: usize, src: &str, props: &ExampleProperties, file_nam
}))
}
fn generate_type_from_kcl(ty: &TyData, file_name: String) -> Result<()> {
fn generate_type_from_kcl(ty: &TyData, file_name: String, example_name: String) -> Result<()> {
if ty.properties.doc_hidden {
return Ok(());
}
@ -416,7 +452,7 @@ fn generate_type_from_kcl(ty: &TyData, file_name: String) -> Result<()> {
.examples
.iter()
.enumerate()
.filter_map(|(index, example)| generate_example(index, &example.0, &example.1, &file_name))
.filter_map(|(index, example)| generate_example(index, &example.0, &example.1, &example_name))
.collect();
let data = json!({
@ -428,7 +464,7 @@ fn generate_type_from_kcl(ty: &TyData, file_name: String) -> Result<()> {
});
let output = hbs.render("kclType", &data)?;
expectorate::assert_contents(format!("../../docs/kcl/types/{}.md", file_name), &output);
expectorate::assert_contents(format!("../../docs/kcl/{}.md", file_name), &output);
Ok(())
}
@ -480,7 +516,7 @@ fn generate_function_from_kcl(function: &FnData, file_name: String) -> Result<()
Ok(())
}
fn generate_const_from_kcl(cnst: &ConstData, file_name: String) -> Result<()> {
fn generate_const_from_kcl(cnst: &ConstData, file_name: String, example_name: String) -> Result<()> {
if cnst.properties.doc_hidden {
return Ok(());
}
@ -490,7 +526,7 @@ fn generate_const_from_kcl(cnst: &ConstData, file_name: String) -> Result<()> {
.examples
.iter()
.enumerate()
.filter_map(|(index, example)| generate_example(index, &example.0, &example.1, &file_name))
.filter_map(|(index, example)| generate_example(index, &example.0, &example.1, &example_name))
.collect();
let data = json!({
@ -1028,8 +1064,8 @@ fn test_generate_stdlib_markdown_docs() {
for d in &kcl_std {
match d {
DocData::Fn(f) => generate_function_from_kcl(f, d.file_name()).unwrap(),
DocData::Const(c) => generate_const_from_kcl(c, d.file_name()).unwrap(),
DocData::Ty(t) => generate_type_from_kcl(t, d.file_name()).unwrap(),
DocData::Const(c) => generate_const_from_kcl(c, d.file_name(), d.example_name()).unwrap(),
DocData::Ty(t) => generate_type_from_kcl(t, d.file_name(), d.example_name()).unwrap(),
}
}
}
@ -1061,7 +1097,8 @@ fn test_generate_stdlib_json_schema() {
async fn test_code_in_topics() {
let mut join_set = JoinSet::new();
for name in LANG_TOPICS {
let filename = format!("../../docs/kcl/{}.md", name.to_lowercase().replace(' ', "-"));
let filename =
format!("../../docs/kcl/{}.md", name.to_lowercase().replace(' ', "-")).replace("constants", "consts");
let mut file = File::open(&filename).unwrap();
let mut text = String::new();
file.read_to_string(&mut text).unwrap();

View File

@ -116,10 +116,18 @@ impl DocData {
#[allow(dead_code)]
pub fn file_name(&self) -> String {
match self {
DocData::Fn(f) => f.qual_name.replace("::", "-"),
DocData::Const(c) => format!("consts/{}", c.qual_name.replace("::", "-")),
DocData::Ty(t) => format!("types/{}", t.name.clone()),
}
}
#[allow(dead_code)]
pub fn example_name(&self) -> String {
match self {
DocData::Fn(f) => f.qual_name.replace("::", "-"),
DocData::Const(c) => format!("const_{}", c.qual_name.replace("::", "-")),
// TODO might want to change this
DocData::Ty(t) => t.name.clone(),
}
}
@ -872,7 +880,7 @@ mod test {
Ok(img) => img,
};
twenty_twenty::assert_image(
format!("tests/outputs/serial_test_example_{}{i}.png", d.file_name()),
format!("tests/outputs/serial_test_example_{}{i}.png", d.example_name()),
&result,
0.99,
);

View File

@ -0,0 +1,17 @@
---
title: "KCL Constants"
excerpt: "Documentation for the KCL constants."
layout: manual
---
## Table of Contents
{{#each consts}}
### `{{name}}`
{{#each consts}}
- [`{{name}}`](/docs/kcl/{{file_name}})
{{/each}}
{{/each}}

View File

@ -64,8 +64,6 @@ pub struct ExecOutcome {
/// Operations that have been performed in execution order, for display in
/// the Feature Tree.
pub operations: Vec<Operation>,
/// Output map of UUIDs to artifacts.
pub artifacts: IndexMap<ArtifactId, Artifact>,
/// Output commands to allow building the artifact graph by the caller.
pub artifact_commands: Vec<ArtifactCommand>,
/// Output artifact graph.

View File

@ -123,7 +123,6 @@ impl ExecState {
.map(|(k, v)| (k.clone(), v.clone()))
.collect(),
operations: self.global.operations,
artifacts: self.global.artifacts,
artifact_commands: self.global.artifact_commands,
artifact_graph: self.global.artifact_graph,
errors: self.global.errors,
@ -146,7 +145,6 @@ impl ExecState {
.map(|(k, v)| (k.clone(), v.clone()))
.collect(),
operations: Default::default(),
artifacts: Default::default(),
artifact_commands: Default::default(),
artifact_graph: Default::default(),
errors: self.global.errors,

View File

@ -39,8 +39,8 @@ base_unit = "mm"
text_wrapping = false
"#;
const PROJECT_SETTINGS_DOC_PATH: &str = "../../docs/kcl/settings/project.toml.md";
const USER_SETTINGS_DOC_PATH: &str = "../../docs/kcl/settings/user.toml.md";
const PROJECT_SETTINGS_DOC_PATH: &str = "../../docs/kcl/settings/project.md";
const USER_SETTINGS_DOC_PATH: &str = "../../docs/kcl/settings/user.md";
fn init_handlebars() -> handlebars::Handlebars<'static> {
let mut hbs = handlebars::Handlebars::new();

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Result of parsing array_elem_pop.kcl
---
{

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Result of parsing array_elem_pop_empty_fail.kcl
---
{

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Error from executing array_elem_pop_empty_fail.kcl
---
KCL Semantic error

File diff suppressed because one or more lines are too long

View File

@ -17,7 +17,12 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
if (!selectedCommand?.args) return undefined
const s = { ...selectedCommand.args }
for (const [name, arg] of Object.entries(s)) {
if (arg.hidden) delete s[name]
if (
typeof arg.hidden === 'function'
? arg.hidden(commandBarState.context)
: arg.hidden
)
delete s[name]
}
return s
}, [selectedCommand])

View File

@ -8,9 +8,32 @@ import {
faTrash,
} from '@fortawesome/free-solid-svg-icons'
/** Type narrowing function of unknown error to a string */
function errorMessage(error: unknown): string {
if (isRouteErrorResponse(error)) {
return `${error.status} ${error.statusText}`
} else if (error != undefined && error instanceof Error) {
return error.message
} else if (error && typeof error === 'object') {
return JSON.stringify(error)
} else if (typeof error === 'string') {
return error
} else {
return 'Unknown error'
}
}
/** Generate a GitHub issue URL from the error */
function generateToUrl(error: unknown) {
const title: string = 'An unexpected error occurred'
const body = errorMessage(error)
const result = `https://github.com/KittyCAD/modeling-app/issues/new?title=${title}&body=${body}`
return result
}
export const ErrorPage = () => {
let error = useRouteError()
// We log the error to the console no matter what
console.error('error', error)
return (
@ -19,11 +42,9 @@ export const ErrorPage = () => {
<h1 className="text-4xl mb-8 font-bold" data-testid="unexpected-error">
An unexpected error occurred
</h1>
{isRouteErrorResponse(error) && (
<p className="mb-8">
{error.status}: {error.data}
</p>
)}
<p className="mb-8 w-full overflow-aut">
<>{errorMessage(error)}</>
</p>
<div className="flex justify-between gap-2 mt-6">
{isDesktop() && (
<ActionButton
@ -54,7 +75,7 @@ export const ErrorPage = () => {
<ActionButton
Element="externalLink"
iconStart={{ icon: faBug }}
to="https://github.com/KittyCAD/modeling-app/issues/new"
to={generateToUrl(error)}
>
Report Bug
</ActionButton>

View File

@ -7,7 +7,7 @@ import toast from 'react-hot-toast'
import { editorManager } from 'lib/singletons'
import { Annotation, Transaction } from '@codemirror/state'
import { EditorView, KeyBinding } from '@codemirror/view'
import { recast, Program, parse } from 'lang/wasm'
import { recast, Program, parse, SourceRange } from 'lang/wasm'
import { err, reportRejection } from 'lib/trap'
import { Compartment } from '@codemirror/state'
import { history } from '@codemirror/commands'
@ -57,6 +57,10 @@ export default class CodeManager {
return this._code
}
getCodeAtRange(range: SourceRange) {
return this._code.slice(range[0], range[1])
}
localStoragePersistCode(): string {
return safeLSGetItem(PERSIST_CODE_KEY) || ''
}

View File

@ -295,7 +295,6 @@ export const isPathToNodeNumber = (
export interface ExecState {
variables: { [key in string]?: KclValue }
operations: Operation[]
artifacts: { [key in ArtifactId]?: RustArtifact }
artifactCommands: ArtifactCommand[]
artifactGraph: ArtifactGraph
errors: CompilationError[]
@ -310,7 +309,6 @@ export function emptyExecState(): ExecState {
return {
variables: {},
operations: [],
artifacts: {},
artifactCommands: [],
artifactGraph: defaultArtifactGraph(),
errors: [],
@ -337,7 +335,6 @@ function execStateFromRust(
return {
variables: execOutcome.variables,
operations: execOutcome.operations,
artifacts: execOutcome.artifacts,
artifactCommands: execOutcome.artifactCommands,
artifactGraph,
errors: execOutcome.errors,
@ -349,7 +346,6 @@ function mockExecStateFromRust(execOutcome: RustExecOutcome): ExecState {
return {
variables: execOutcome.variables,
operations: execOutcome.operations,
artifacts: execOutcome.artifacts,
artifactCommands: execOutcome.artifactCommands,
artifactGraph: new Map<ArtifactId, Artifact>(),
errors: execOutcome.errors,

View File

@ -69,6 +69,8 @@ export type ModelingCommandSchema = {
edge: Selections
}
Fillet: {
// Enables editing workflow
nodeToEdit?: PathToNode
selection: Selections
radius: KclCommandValue
}
@ -319,6 +321,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
multiple: false, // TODO: multiple selection
required: true,
skip: true,
hidden: (context) => Boolean(context.argumentsToSubmit.nodeToEdit),
},
// result: {
// inputType: 'options',
@ -407,6 +410,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
multiple: false, // TODO: multiple selection
required: true,
skip: true,
hidden: (context) => Boolean(context.argumentsToSubmit.nodeToEdit),
},
axisOrEdge: {
inputType: 'options',
@ -416,6 +420,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
{ name: 'Axis', isCurrent: true, value: 'Axis' },
{ name: 'Edge', isCurrent: false, value: 'Edge' },
],
hidden: (context) => Boolean(context.argumentsToSubmit.nodeToEdit),
},
axis: {
required: (commandContext) =>
@ -437,6 +442,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
selectionTypes: ['segment', 'sweepEdge', 'edgeCutEdge'],
multiple: false,
validation: revolveAxisValidator,
hidden: (context) => Boolean(context.argumentsToSubmit.nodeToEdit),
},
angle: {
inputType: 'kcl',
@ -534,12 +540,21 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
status: 'development',
needsReview: true,
args: {
nodeToEdit: {
description:
'Path to the node in the AST to edit. Never shown to the user.',
skip: true,
inputType: 'text',
required: false,
hidden: true,
},
selection: {
inputType: 'selection',
selectionTypes: ['segment', 'sweepEdge', 'edgeCutEdge'],
multiple: true,
required: true,
skip: false,
hidden: (context) => Boolean(context.argumentsToSubmit.nodeToEdit),
warningMessage:
'Fillets cannot touch other fillets yet. This is under development.',
},

View File

@ -120,7 +120,12 @@ export type CommandArgumentConfig<
) => boolean)
warningMessage?: string
/** If `true`, arg is used as passed-through data, never for user input */
hidden?: boolean
hidden?:
| boolean
| ((
commandBarContext: { argumentsToSubmit: Record<string, unknown> }, // Should be the commandbarMachine's context, but it creates a circular dependency
machineContext?: C
) => boolean)
skip?: boolean
/** For showing a summary display of the current value, such as in
* the command bar's header
@ -236,7 +241,12 @@ export type CommandArgument<
machineContext?: ContextFrom<T>
) => boolean)
/** If `true`, arg is used as passed-through data, never for user input */
hidden?: boolean
hidden?:
| boolean
| ((
commandBarContext: { argumentsToSubmit: Record<string, unknown> }, // Should be the commandbarMachine's context, but it creates a circular dependency
machineContext?: ContextFrom<T>
) => boolean)
skip?: boolean
machineActor?: Actor<T>
warningMessage?: string

View File

@ -1,5 +1,10 @@
import { CustomIconName } from 'components/CustomIcon'
import { Artifact, getArtifactOfTypes } from 'lang/std/artifactGraph'
import {
Artifact,
getArtifactByPredicate,
getArtifactOfTypes,
getArtifactsOfTypes,
} from 'lang/std/artifactGraph'
import { Operation } from '@rust/kcl-lib/bindings/Operation'
import { codeManager, engineCommandManager, kclManager } from './singletons'
import { err } from './trap'
@ -293,6 +298,84 @@ const prepareToEditHelix: PrepareToEditCallback = async ({ operation }) => {
}
}
const prepareToEditFillet: PrepareToEditCallback = async ({
artifact,
operation,
}) => {
const baseCommand = {
name: 'Fillet',
groupId: 'modeling',
}
if (
operation.type !== 'StdLibCall' ||
!operation.labeledArgs ||
operation.name !== 'fillet' ||
!('tags' in operation.labeledArgs) ||
!('radius' in operation.labeledArgs) ||
!operation.labeledArgs.radius ||
!artifact ||
artifact.type !== 'edgeCut' ||
artifact.subType !== 'fillet'
) {
return baseCommand
}
if (
!operation.labeledArgs.tags ||
operation.labeledArgs.tags.value.type !== 'Array'
) {
return baseCommand
}
const edgeIds = operation.labeledArgs.tags.value.value.map((tag) =>
tag.type === 'Uuid' ? tag.value : ''
)
if (!edgeIds.every((id) => id)) {
return baseCommand
}
const edges = getArtifactsOfTypes(
{
keys: edgeIds,
types: ['sweepEdge', 'segment'],
},
engineCommandManager.artifactGraph
)
const selection: ModelingCommandSchema['Fillet']['selection'] = {
graphSelections: edges
.values()
.map((edge, index) => ({
artifact: edge,
codeRef: {
range: operation.labeledArgs.tags?.sourceRange ?? [0, 0, 0],
pathToNode: getNodePathFromSourceRange(
kclManager.ast,
operation.labeledArgs.tags?.sourceRange ?? [0, 0, 0]
),
},
}))
.toArray(),
otherSelections: [],
}
const maybeRadius = await stringToKclExpression(
codeManager.getCodeAtRange(operation.labeledArgs.radius.sourceRange)
)
if (err(maybeRadius) || 'errors' in maybeRadius) return baseCommand
const argDefaultValues: ModelingCommandSchema['Fillet'] = {
nodeToEdit: getNodePathFromSourceRange(
kclManager.ast,
sourceRangeFromRust(operation.sourceRange)
),
radius: maybeRadius,
selection,
}
return {
...baseCommand,
argDefaultValues,
}
}
/**
* A map of standard library calls to their corresponding information
* for use in the feature tree UI.
@ -312,6 +395,7 @@ export const stdLibMap: Record<string, StdLibCallInfo> = {
fillet: {
label: 'Fillet',
icon: 'fillet3d',
prepareToEdit: prepareToEditFillet,
},
helix: {
label: 'Helix',

View File

@ -134,8 +134,10 @@ export const commandBarMachine = setup({
// that is, the first argument that is not already in the argumentsToSubmit
// or hidden, or that is not undefined, or that is not marked as "skippable".
// TODO validate the type of the existing arguments
const nonHiddenArgs = Object.entries(selectedCommand.args).filter(
(a) => !a[1].hidden
const nonHiddenArgs = Object.entries(selectedCommand.args).filter((a) =>
a[1].hidden && typeof a[1].hidden === 'function'
? !a[1].hidden(context)
: !a[1].hidden
)
let argIndex = 0
@ -260,7 +262,11 @@ export const commandBarMachine = setup({
},
'All arguments are skippable': ({ context }) => {
return Object.values(context.selectedCommand!.args!).every(
(argConfig) => argConfig.skip || argConfig.hidden
(argConfig) =>
argConfig.skip ||
(typeof argConfig.hidden === 'function'
? argConfig.hidden(context)
: argConfig.hidden)
)
},
'Has selected command': ({ context }) => !!context.selectedCommand,