Compare commits

...

17 Commits

Author SHA1 Message Date
a74771c50d Change to only check formatting, not do it 2025-05-11 14:17:53 -04:00
cb330b99f0 Change biome fmt to be pre-push 2025-05-11 14:14:58 -04:00
0621e1a53e Docs content (#6792)
* Add documentation to modules, and some constants and types

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Improve the language reference

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-05-11 19:32:33 +12:00
f36b69f4f0 Source range for pipe value used as unlabelled arg (#6787)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-05-11 17:43:12 +12:00
92f7a62af2 Create JUnit reports from Rust tests (#6743) 2025-05-11 00:49:55 -04:00
f73831ac27 more subtract regression tests (#6831)
updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-10 19:23:55 -04:00
09f39499e9 add subtract regression tests (#6829)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-10 13:48:38 -07:00
7ac4a9507b fix unwrap (#6825)
* fix unwrap

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

* add test

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

* updates

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

* udaptes

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-10 13:00:14 -07:00
3956958452 Force idle stream to 5min and hide setting (#6810)
* Hide "Allow orbit in sketch mode" setting from the app

The ability to orbit while in sketch mode is not well-tested or
well-used, so we should pull it from the app. The easiest way to do that
is leave the setting in the WASM definition, but configure the setting
to be uneditable from the interface.

* pierremtb/adhoc/force-idle-stream-and-hide

---------

Co-authored-by: Frank Noirot <frankjohnson1993@gmail.com>
Co-authored-by: Frank Noirot <frank@zoo.dev>
2025-05-10 15:54:58 -04:00
0fe866ad8b Update utility sink sample (#6798)
* upadte utility sink sample

* remove old screenshot
2025-05-10 19:52:55 +00:00
cca498be04 Fix ENOENT error when opening the app with a path on Linux (#6813)
* Fix ENOENT error when opening the app with a path on Linux
Fixes #6796

* Clean up
2025-05-10 15:46:30 -04:00
max
039cb38d56 Fix deletion of unassigned standalone edge treatments (#6815)
* oops, make it nicer for no reason

* tests

* deleteTopLevelStatement

* little swap

* astMod edits

* typos

* add playwright test for chamfers

* scene.settled instead of page.waitForTimeout

* unfuck circular dep - move locateExtrudeDeclarator

* locateExtrudeDeclarator > locateVariableWithCallOrPipe

* fmt

* edit the comment
2025-05-10 16:53:05 +00:00
4fb7065ddf Fix sign error. (#6821)
* Fix sign error.

* Update kcl-samples simulation test output

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-05-10 08:45:05 -07:00
39f512d32d profile start constrain overlays (#6795)
* constrain profile start

* add test

* make sure it works on segment drag too, fix tests

* remove old log

* some tests fixes

* Bump more segment counters

* Two more fixes

* Two more test fixes

* small test fix

* moretest fixes

* another test

---------

Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
Co-authored-by: Pierre Jacquier <pierre@zoo.dev>
2025-05-10 12:29:41 +10:00
85469f2d7d Hide "Allow orbit in sketch mode" setting from the app (#6803)
The ability to orbit while in sketch mode is not well-tested or
well-used, so we should pull it from the app. The easiest way to do that
is leave the setting in the WASM definition, but configure the setting
to be uneditable from the interface.
2025-05-09 22:58:28 +00:00
3e24e2c9e8 Enable hotkeys for toolbar items within dropdowns (#6765)
* Remove gnarly fake union hotkeys

* Enable hotkey for items buried in ActionButtonDropdown

I'm kinda over `useHotkeys` as a hook

* Add hotkeys for other sketch tools

* Fix lint and tsc

* Fix duplicate locator

* The circular dependecies got reordered somehow

* Update src/lib/toolbar.ts
2025-05-09 22:56:11 +00:00
2d9f6c7b2a Fix rendered image in subtract CSG sim test (#6812) 2025-05-09 22:27:27 +00:00
222 changed files with 36452 additions and 12063 deletions

13
.github/ci-cd-scripts/upload-results.sh vendored Executable file
View File

@ -0,0 +1,13 @@
#!/bin/bash
set -euo pipefail
BRANCH="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME:-}}"
COMMIT="${CI_COMMIT_SHA:-${GITHUB_SHA:-}}"
curl --request POST \
--header "X-API-Key: ${TAB_API_KEY}" \
--form "project=https://github.com/KittyCAD/modeling-app" \
--form "branch=${BRANCH}" \
--form "commit=${COMMIT}" \
--form "tests=@test-results/junit.xml" \
${TAB_API_URL}/api/results/bulk

View File

@ -98,9 +98,7 @@ jobs:
popd popd
git add \ git add \
rust/kcl-lib/tests \ rust/kcl-lib/tests \
public/kcl-samples/manifest.json \ public/kcl-samples
public/kcl-samples/README.md \
public/kcl-samples/screenshots
git config --local user.email "github-actions[bot]@users.noreply.github.com" git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]" git config --local user.name "github-actions[bot]"
git remote set-url origin https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git git remote set-url origin https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
@ -177,13 +175,19 @@ jobs:
cp nextest-archive.tar.zst rust/nextest-archive.tar.zst cp nextest-archive.tar.zst rust/nextest-archive.tar.zst
ls -lah ls -lah
cd rust cd rust
cargo nextest run\ cargo nextest run \
--retries=10 --no-fail-fast --profile ci --archive-file nextest-archive.tar.zst \ --retries=10 --no-fail-fast --profile=ci --archive-file nextest-archive.tar.zst \
--partition count:${{ matrix.partitionIndex}}/${{ matrix.partitionTotal }} \ --partition count:${{ matrix.partitionIndex}}/${{ matrix.partitionTotal }} \
2>&1 | tee /tmp/github-actions.log 2>&1 | tee /tmp/github-actions.log
env: env:
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}} KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}}
ZOO_HOST: https://api.dev.zoo.dev ZOO_HOST: https://api.dev.zoo.dev
- name: Upload results
if: always()
run: .github/ci-cd-scripts/upload-results.sh
env:
TAB_API_URL: ${{ secrets.TAB_API_URL }}
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
run-wasm-tests: run-wasm-tests:
name: Run wasm tests name: Run wasm tests
strategy: strategy:

View File

@ -1 +0,0 @@
npm run fmt

1
.husky/pre-push Executable file
View File

@ -0,0 +1 @@
npm run fmt:check

View File

@ -0,0 +1,53 @@
---
title: "Arithmetic and logic"
excerpt: "Documentation of the KCL language for the Zoo Design Studio."
layout: manual
---
KCL supports the usual arithmetic operators on numbers and logic operators on booleans:
| Operator | Meaning |
|----------|---------|
| `+` | Addition |
| `-` | Subtraction or unary negation |
| `*` | Multiplication |
| `/` | Division |
| `%` | Modulus aka remainder |
| `^` | Power, e.g., `x ^ 2` means `x` squared |
| `&` | Logical 'and' |
| `|` | Logical 'or' |
| `!` | Unary logical 'not' |
KCL also supports comparsion operators which operate on numbers and produce booleans:
| Operator | Meaning |
|----------|---------|
| `==` | Equal |
| `!=` | Not equal |
| `<` | Less than |
| `>` | Greater than |
| `<=` | Less than or equal |
| `>=` | Greater than or equal |
Arithmetics and logic expressions can be arbitrairly combined with the usual rules of associativity and precedence, e.g.,
```
myMathExpression = 3 + 1 * 2 / 3 - 7
```
You can also nest expressions in parenthesis:
```
myMathExpression = 3 + (1 * 2 / (3 - 7))
```
KCL numbers are implemented using [floating point numbers](https://en.wikipedia.org/wiki/Floating-point_arithmetic). This means that there are occasionally representation and rounding issues, and some oddities such as supporting positive and negative zero.
Some operators can be applied to other types:
- `+` can be used to concatenate strings, e.g., `'hello' + ' ' + 'world!'`
- Unary `-` can be used with planes or line-like objects such as axes to produce an object with opposite orientation, e.g., `-XY` is a plain which is aligned with `XY` but whose normal aligns with the negative Z axis.
- The following operators can be used with solids as shorthand for CSG operations:
- `+` or `|` for [`union`](/docs/kcl-std/union).
- `-` for [`subtract`](/docs/kcl-std/subtract).
- `&` for [`intersect`](/docs/kcl-std/intersect)

View File

@ -0,0 +1,30 @@
---
title: "Attributes"
excerpt: "Documentation of the KCL language for the Zoo Design Studio."
layout: manual
---
Attributes are syntax which affects the language item they annotate. In KCL they are indicated using `@`. For example, `@settings` affects the file in which it appears.
There are two kinds of attributes: named and unnamed attributes. Named attributes (e.g., `@settings`) have a name immediately after the `@` (e.g., `settings`) and affect their surrounding scope. Unnamed attributes have no name and affect the following item, e.g.,
```kcl,norun
@(lengthUnit = ft, coords = opengl)
import "tests/inputs/cube.obj"
```
has an unnamed attribute on the `import` statement.
Named and unnamed attributes may take a parenthesized list of arguments (like a function). Named attributes may also appear without any arguments (e.g., `@no_std`).
## Named attributes
The `@settings` attribute affects the current file and accepts the following arguments: `defaultLengthUnit`, `defaultAngleUnit`, and `kclVersion`. See [settings](/docs/kcl-lang/settings) for details.
The `@no_std` attribute affects the current file, takes no arguments, and causes the standard library to not be implicitly available. It can still be used by being explicitly imported.
## Unnamed attributes
Unnamed attributes may be used on `import` statements when importing non-KCL files. See [projects, modules, and imports](/docs/kcl-lang/modules) for details.
Other unnamed attributes are used on functions inside the standard library, but these are not available in user code.

View File

@ -0,0 +1,46 @@
---
title: "Functions"
excerpt: "Documentation of the KCL language for the Zoo Design Studio."
layout: manual
---
We have support for defining your own functions. Functions can take in any
type of argument. Below is an example of the syntax:
```
fn myFn(x) {
return x
}
```
As you can see above `myFn` just returns whatever it is given.
KCL uses keyword arguments:
```
// If you declare a function like this
fn add(left, right) {
return left + right
}
// You can call it like this:
total = add(left = 1, right = 2)
```
Functions can also declare one *unlabeled* arg. If you do want to declare an unlabeled arg, it must
be the first arg declared.
```
// The @ indicates an argument is used without a label.
// Note that only the first argument can use @.
fn increment(@x) {
return x + 1
}
fn add(@x, delta) {
return x + delta
}
two = increment(1)
three = add(1, delta = 2)
```

View File

@ -1,12 +1,20 @@
--- ---
title: "KCL Language Guide" title: "KCL Language Reference"
excerpt: "Documentation of the KCL language for the Zoo Design Studio." excerpt: "Documentation of the KCL language for the Zoo Design Studio."
layout: manual layout: manual
--- ---
This is a reference for KCL. If you are learning KCL, you may prefer the [guide]() which explains
things in a more tutorial fashion.
## Topics ## Topics
* [`Types`](/docs/kcl-lang/types) * [Pipelines](/docs/kcl-lang/pipelines)
* [`Modules`](/docs/kcl-lang/modules) * [Arithmetic and logic](/docs/kcl-lang/arithmetic)
* [`Settings`](/docs/kcl-lang/settings) * [Values and types](/docs/kcl-lang/types)
* [`Known Issues`](/docs/kcl-lang/known-issues) * [Numeric types and units](/docs/kcl-lang/numeric)
* [Functions](/docs/kcl-lang/functions)
* [Projects, modules, and imports](/docs/kcl-lang/modules)
* [Attributes](/docs/kcl-lang/attributes)
* [Settings](/docs/kcl-lang/settings)
* [Known Issues](/docs/kcl-lang/known-issues)

View File

@ -1,6 +1,6 @@
--- ---
title: "Modules" title: "Modules"
excerpt: "Documentation of modules for the KCL language for the Zoo Design Studio." excerpt: "Documentation of the KCL language for the Zoo Design Studio."
layout: manual layout: manual
--- ---

46
docs/kcl-lang/numeric.md Normal file
View File

@ -0,0 +1,46 @@
---
title: "Numeric types and units"
excerpt: "Documentation of the KCL language for the Zoo Design Studio."
layout: manual
---
Numbers and numeric types in KCL include information about the units of the numbers. So rather than just having a number like `42`, we always have information about the units so we don't confuse 42 mm with 42 inches.
## Numeric literals
When writing a number literal, you can use a unit suffix to explicitly state the unit, e.g., `42mm`. The following units are available:
- Length units:
- metric: `mm`, `cm`, `m`
- imperial: `in`, `ft`, `yd`
- Angle units: `deg`, `rad`
- `_` to indicate a unitless number such as a count or ratio.
If you write a numeric literal without a suffix, then the defaults for the current file are used. These defaults are specified using the `@settings` attribute, see [settings](/docs/kcl-lang/settings) for details. Note that if using the defaults, the KCL interpreter won't know whether you intend the number to be a length, angle, or count and will treat it as being possibly any of them.
## Numeric types
Just like numbers carry units information, the `number` type also includes units information. Units are written in parentheses after the type, e.g., `number(mm)`.
Any of the suffixes described above can be used meaning that values with that type have the supplied units. E.g., `number(mm)` is the type of number values with mm units and `number(_)` is the type of number values with no units.
You can also use `number(Length)`, `number(Angle)`, or `number(Count)`. These types mean a number with any length, angle, or unitless (count) units, respectively (note that `number(_)` and `number(Count)` are equivalent since there is only one kind of unitless-ness).
## Function calls
When calling a function with an argument with numeric type, the declared numeric type in the function signature and the units of the argument value used in the function call must be compatible. Units are adjusted automatically. For example, if a function requires an argument with type `number(mm)`, then you can call it with `2in` and the units will be automatically adjusted, but calling it with `90deg` will cause an error.
## Mixing units with arithmetic
When doing arithmetic or comparisons, units will be adjusted as necessary if possible. However, often arithmetic expressions exceed the ability of KCL to accurately choose units which can result in warnings in your code or sometimes errors. In these cases, you will need to give KCL more information. Sometimes this can be done by making units explicit using suffixes. If not, then you will need to use *type ascription*, which asserts that an expression has the supplied type. For example, `(x * y): number(mm)` tells KCL that the units of `x * y` is mm. Note that type ascription does not do any adjustment of the numbers, e.g., `2mm: number(in)` has the value `2in` (note that this would be a very non-idiomatic way to use numeric type ascription, you could simply write `2in`. Usually type ascription is only necessary for supplying type information about the result of computation).
KCL has no support for area, volume, or other higher dimension units. When internal unit tracking requires multiple dimensions, KCL essentially gives up. This is usually where the extra type information described above is needed. If doing computation with higher dimensioned units, you must ensure that all adjustments occur before any computation. E.g., if you want to compute an area with unknown units, you must convert all numbers to the same unit before starting.
## Explicit conversions
You might sometimes need to convert from one unit to another for some calculation. You can do this implicitly when calling a function (see above), but if you can't or don't want to, then you can use the explicit conversion functions in the [`std::units`](/docs/kcl-std/modules/std-units) module.

View File

@ -0,0 +1,66 @@
---
title: "Pipelines"
excerpt: "Documentation of the KCL language for the Zoo Design Studio."
layout: manual
---
It can be hard to read repeated function calls, because of all the nested brackets.
```norun
i = 1
x = h(g(f(i)))
```
You can make this easier to read by breaking it into many declarations, but that is a bit annoying.
```norun
i = 1
x0 = f(i)
x1 = g(x0)
x = h(x1)
```
Instead, you can use the pipeline operator (`|>`) to simplify this.
Basically, `x |> f(%)` is a shorthand for `f(x)`. The left-hand side of the `|>` gets put into
the `%` in the right-hand side.
So, this means `x |> f(%) |> g(%)` is shorthand for `g(f(x))`. The code example above, with its
somewhat-clunky `x0` and `x1` constants could be rewritten as
```norun
i = 1
x = i
|> f(%)
|> g(%)
|> h(%)
```
This helps keep your code neat and avoid unnecessary declarations.
## Pipelines and keyword arguments
Say you have a long pipeline of sketch functions, like this:
```norun
startSketchOn(XZ)
|> line(%, end = [3, 4])
|> line(%, end = [10, 10])
|> line(%, end = [-13, -14])
|> close(%)
```
In this example, each function call outputs a sketch, and it gets put into the next function call via
the `%`, into the first (unlabeled) argument.
If a function call uses an unlabeled first parameter, it will default to `%` if it's not given. This
means that `|> line(%, end = [3, 4])` and `|> line(end = [3, 4])` are equivalent! So the above
could be rewritten as
```norun
startSketchOn(XZ)
|> line(end = [3, 4])
|> line(end = [10, 10])
|> line(end = [-13, -14])
|> close()
```

View File

@ -1,6 +1,6 @@
--- ---
title: "Settings" title: "Settings"
excerpt: "Documentation of settings for the KCL language and Zoo Design Studio." excerpt: "Documentation of the KCL language for the Zoo Design Studio."
layout: manual layout: manual
--- ---

View File

@ -1,6 +1,6 @@
--- ---
title: "Types" title: "Values and types"
excerpt: "Documentation of types for the KCL standard library for the Zoo Design Studio." excerpt: "Documentation of the KCL language for the Zoo Design Studio."
layout: manual layout: manual
--- ---
@ -52,131 +52,6 @@ their internal components. See the [modules and imports docs](modules) for more
detail on importing geometry. detail on importing geometry.
## Binary expressions
You can also do math! Let's show an example below:
```
myMathExpression = 3 + 1 * 2 / 3 - 7
```
You can nest expressions in parenthesis as well:
```
myMathExpression = 3 + (1 * 2 / (3 - 7))
```
## Functions
We also have support for defining your own functions. Functions can take in any
type of argument. Below is an example of the syntax:
```
fn myFn(x) {
return x
}
```
As you can see above `myFn` just returns whatever it is given.
KCL's early drafts used positional arguments, but we now use keyword arguments:
```
// If you declare a function like this
fn add(left, right) {
return left + right
}
// You can call it like this:
total = add(left = 1, right = 2)
```
Functions can also declare one *unlabeled* arg. If you do want to declare an unlabeled arg, it must
be the first arg declared.
```
// The @ indicates an argument can be used without a label.
// Note that only the first argument can use @.
fn increment(@x) {
return x + 1
}
fn add(@x, delta) {
return x + delta
}
two = increment(1)
three = add(1, delta = 2)
```
## Pipelines
It can be hard to read repeated function calls, because of all the nested brackets.
```norun
i = 1
x = h(g(f(i)))
```
You can make this easier to read by breaking it into many declarations, but that is a bit annoying.
```norun
i = 1
x0 = f(i)
x1 = g(x0)
x = h(x1)
```
Instead, you can use the pipeline operator (`|>`) to simplify this.
Basically, `x |> f(%)` is a shorthand for `f(x)`. The left-hand side of the `|>` gets put into
the `%` in the right-hand side.
So, this means `x |> f(%) |> g(%)` is shorthand for `g(f(x))`. The code example above, with its
somewhat-clunky `x0` and `x1` constants could be rewritten as
```norun
i = 1
x = i
|> f(%)
|> g(%)
|> h(%)
```
This helps keep your code neat and avoid unnecessary declarations.
## Pipelines and keyword arguments
Say you have a long pipeline of sketch functions, like this:
```norun
startSketchOn(XZ)
|> line(%, end = [3, 4])
|> line(%, end = [10, 10])
|> line(%, end = [-13, -14])
|> close(%)
```
In this example, each function call outputs a sketch, and it gets put into the next function call via
the `%`, into the first (unlabeled) argument.
If a function call uses an unlabeled first parameter, it will default to `%` if it's not given. This
means that `|> line(%, end = [3, 4])` and `|> line(end = [3, 4])` are equivalent! So the above
could be rewritten as
```norun
startSketchOn(XZ)
|> line(end = [3, 4])
|> line(end = [10, 10])
|> line(end = [-13, -14])
|> close()
```
Note that we are still in the process of migrating KCL's standard library to use keyword arguments. So some
functions are still unfortunately using positional arguments. We're moving them over, so keep checking back.
Some functions are still using the old positional argument syntax.
Check the docs page for each function and look at its examples to see.
## Tags ## Tags
Tags are used to give a name (tag) to a specific path. Tags are used to give a name (tag) to a specific path.
@ -291,7 +166,6 @@ See how we use the tag `rectangleSegmentA001` in the `fillet` function outside
the `rect` function. This is because the `rect` function is returning the the `rect` function. This is because the `rect` function is returning the
sketch group that contains the tags. sketch group that contains the tags.
--- ---
If you find any issues using any of the above expressions or syntax, If you find any issues using any of the above expressions or syntax,

File diff suppressed because one or more lines are too long

View File

@ -1,11 +1,11 @@
--- ---
title: "X" title: "X"
subtitle: "Constant in std" subtitle: "Constant in std"
excerpt: "" excerpt: "The X-axis (can be used in both 2d and 3d contexts)."
layout: manual layout: manual
--- ---
The X-axis (can be used in both 2d and 3d contexts).
```kcl ```kcl
X X

View File

@ -1,11 +1,11 @@
--- ---
title: "XY" title: "XY"
subtitle: "Constant in std" subtitle: "Constant in std"
excerpt: "" excerpt: "An abstract 3d plane aligned with the X and Y axes. Its normal is the positive Z axis."
layout: manual layout: manual
--- ---
An abstract 3d plane aligned with the X and Y axes. Its normal is the positive Z axis.
```kcl ```kcl
XY XY

View File

@ -1,11 +1,11 @@
--- ---
title: "XZ" title: "XZ"
subtitle: "Constant in std" subtitle: "Constant in std"
excerpt: "" excerpt: "An abstract 3d plane aligned with the X and Z axes. Its normal is the negative Y axis."
layout: manual layout: manual
--- ---
An abstract 3d plane aligned with the X and Z axes. Its normal is the negative Y axis.
```kcl ```kcl
XZ XZ

View File

@ -1,11 +1,11 @@
--- ---
title: "Y" title: "Y"
subtitle: "Constant in std" subtitle: "Constant in std"
excerpt: "" excerpt: "The Y-axis (can be used in both 2d and 3d contexts)."
layout: manual layout: manual
--- ---
The Y-axis (can be used in both 2d and 3d contexts).
```kcl ```kcl
Y Y

View File

@ -1,11 +1,11 @@
--- ---
title: "YZ" title: "YZ"
subtitle: "Constant in std" subtitle: "Constant in std"
excerpt: "" excerpt: "An abstract 3d plane aligned with the Y and Z axes. Its normal is the positive X axis."
layout: manual layout: manual
--- ---
An abstract 3d plane aligned with the Y and Z axes. Its normal is the positive X axis.
```kcl ```kcl
YZ YZ

View File

@ -1,11 +1,11 @@
--- ---
title: "Z" title: "Z"
subtitle: "Constant in std" subtitle: "Constant in std"
excerpt: "" excerpt: "The 3D Z-axis."
layout: manual layout: manual
--- ---
The 3D Z-axis.
```kcl ```kcl
Z Z

View File

@ -1,11 +1,11 @@
--- ---
title: "turns::HALF_TURN" title: "turns::HALF_TURN"
subtitle: "Constant in std::turns" subtitle: "Constant in std::turns"
excerpt: "" excerpt: "A half turn, 180 degrees or π radians."
layout: manual layout: manual
--- ---
A half turn, 180 degrees or π radians.
```kcl ```kcl
turns::HALF_TURN: number(deg) = 180deg turns::HALF_TURN: number(deg) = 180deg

View File

@ -1,11 +1,11 @@
--- ---
title: "turns::QUARTER_TURN" title: "turns::QUARTER_TURN"
subtitle: "Constant in std::turns" subtitle: "Constant in std::turns"
excerpt: "" excerpt: "A quarter turn, 90 degrees or π/2 radians."
layout: manual layout: manual
--- ---
A quarter turn, 90 degrees or π/2 radians.
```kcl ```kcl
turns::QUARTER_TURN: number(deg) = 90deg turns::QUARTER_TURN: number(deg) = 90deg

View File

@ -1,11 +1,11 @@
--- ---
title: "turns::THREE_QUARTER_TURN" title: "turns::THREE_QUARTER_TURN"
subtitle: "Constant in std::turns" subtitle: "Constant in std::turns"
excerpt: "" excerpt: "Three quarters of a turn, 270 degrees or 1.5*π radians."
layout: manual layout: manual
--- ---
Three quarters of a turn, 270 degrees or 1.5*π radians.
```kcl ```kcl
turns::THREE_QUARTER_TURN: number(deg) = 270deg turns::THREE_QUARTER_TURN: number(deg) = 270deg

View File

@ -1,11 +1,11 @@
--- ---
title: "turns::ZERO" title: "turns::ZERO"
subtitle: "Constant in std::turns" subtitle: "Constant in std::turns"
excerpt: "" excerpt: "No turn, zero degrees/radians."
layout: manual layout: manual
--- ---
No turn, zero degrees/radians.
```kcl ```kcl
turns::ZERO: number = 0 turns::ZERO: number = 0

View File

@ -35,7 +35,7 @@ helix(
### Returns ### Returns
[`Helix`](/docs/kcl-std/types/std-types-Helix) - A helix. [`Helix`](/docs/kcl-std/types/std-types-Helix) - A helix; created by the `helix` function.
### Examples ### Examples

View File

@ -17,11 +17,11 @@ abs(@input: number): number
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `input` | [`number`](/docs/kcl-std/types/std-types-number) | A number | Yes | | `input` | [`number`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number [`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -17,11 +17,11 @@ acos(@num: number(_)): number(rad)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(_)`](/docs/kcl-std/types/std-types-number) | A number | Yes | | `num` | [`number(_)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns
[`number(rad)`](/docs/kcl-std/types/std-types-number) - A number [`number(rad)`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -17,11 +17,11 @@ asin(@num: number(_)): number(rad)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(_)`](/docs/kcl-std/types/std-types-number) | A number | Yes | | `num` | [`number(_)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns
[`number(rad)`](/docs/kcl-std/types/std-types-number) - A number [`number(rad)`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -17,11 +17,11 @@ Consider using `atan2()` instead for the true inverse of tangent.
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(_)`](/docs/kcl-std/types/std-types-number) | A number | Yes | | `num` | [`number(_)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns
[`number(rad)`](/docs/kcl-std/types/std-types-number) - A number [`number(rad)`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -20,12 +20,12 @@ atan2(
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `y` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | A number | Yes | | `y` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
| `x` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | A number | Yes | | `x` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns
[`number(rad)`](/docs/kcl-std/types/std-types-number) - A number [`number(rad)`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -17,11 +17,11 @@ ceil(@input: number): number
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `input` | [`number`](/docs/kcl-std/types/std-types-number) | A number | Yes | | `input` | [`number`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number [`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -17,11 +17,11 @@ cos(@num: number(Angle)): number(_)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(Angle)`](/docs/kcl-std/types/std-types-number) | A number | Yes | | `num` | [`number(Angle)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns
[`number(_)`](/docs/kcl-std/types/std-types-number) - A number [`number(_)`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -17,11 +17,11 @@ floor(@input: number): number
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `input` | [`number`](/docs/kcl-std/types/std-types-number) | A number | Yes | | `input` | [`number`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number [`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -17,11 +17,11 @@ ln(@input: number): number
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `input` | [`number`](/docs/kcl-std/types/std-types-number) | A number | Yes | | `input` | [`number`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number [`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -27,7 +27,7 @@ and `log10` can produce more accurate results for base 10.
### Returns ### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number [`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -17,11 +17,11 @@ log10(@input: number): number
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `input` | [`number`](/docs/kcl-std/types/std-types-number) | A number | Yes | | `input` | [`number`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number [`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -17,11 +17,11 @@ log2(@input: number): number
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `input` | [`number`](/docs/kcl-std/types/std-types-number) | A number | Yes | | `input` | [`number`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number [`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -21,7 +21,7 @@ max(@input: [number; 1+]): number
### Returns ### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number [`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -21,7 +21,7 @@ min(@input: [number; 1+]): number
### Returns ### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number [`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -21,8 +21,8 @@ cartesian (x/y/z grid) coordinates.
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `angle` | [`number(rad)`](/docs/kcl-std/types/std-types-number) | A number | Yes | | `angle` | [`number(rad)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
| `length` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | A number | Yes | | `length` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns

View File

@ -25,7 +25,7 @@ pow(
### Returns ### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number [`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -26,7 +26,7 @@ If `num` is negative, the result will be too.
### Returns ### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number [`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -17,11 +17,11 @@ round(@input: number): number
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `input` | [`number`](/docs/kcl-std/types/std-types-number) | A number | Yes | | `input` | [`number`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number [`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -17,11 +17,11 @@ sin(@num: number(Angle)): number(_)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(Angle)`](/docs/kcl-std/types/std-types-number) | A number | Yes | | `num` | [`number(Angle)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns
[`number(_)`](/docs/kcl-std/types/std-types-number) - A number [`number(_)`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -17,11 +17,11 @@ sqrt(@input: number): number
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `input` | [`number`](/docs/kcl-std/types/std-types-number) | A number | Yes | | `input` | [`number`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number [`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -17,11 +17,11 @@ tan(@num: number(Angle)): number(_)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(Angle)`](/docs/kcl-std/types/std-types-number) | A number | Yes | | `num` | [`number(Angle)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns
[`number(_)`](/docs/kcl-std/types/std-types-number) - A number [`number(_)`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -26,7 +26,7 @@ plane and 10 units away from it.
### Returns ### Returns
[`Plane`](/docs/kcl-std/types/std-types-Plane) - A plane. [`Plane`](/docs/kcl-std/types/std-types-Plane) - An abstract plane.
### Examples ### Examples

View File

@ -46,7 +46,7 @@ revolved around the same axis.
### Returns ### Returns
[`Solid`](/docs/kcl-std/types/std-types-Solid) - A solid is a collection of extrude surfaces. [`Solid`](/docs/kcl-std/types/std-types-Solid) - A solid is a collection of extruded surfaces.
### Examples ### Examples

View File

@ -31,7 +31,7 @@ a sharp, straight transitional edge.
### Returns ### Returns
[`Solid`](/docs/kcl-std/types/std-types-Solid) - A solid is a collection of extrude surfaces. [`Solid`](/docs/kcl-std/types/std-types-Solid) - A solid is a collection of extruded surfaces.
### Examples ### Examples

View File

@ -33,7 +33,7 @@ will smoothly blend the transition.
### Returns ### Returns
[`Solid`](/docs/kcl-std/types/std-types-Solid) - A solid is a collection of extrude surfaces. [`Solid`](/docs/kcl-std/types/std-types-Solid) - A solid is a collection of extruded surfaces.
### Examples ### Examples

View File

@ -26,7 +26,7 @@ provided thickness remains around the exterior of the shape.
### Returns ### Returns
[`Solid`](/docs/kcl-std/types/std-types-Solid) - A solid is a collection of extrude surfaces. [`Solid`](/docs/kcl-std/types/std-types-Solid) - A solid is a collection of extruded surfaces.
### Examples ### Examples

View File

@ -17,11 +17,11 @@ units::toCentimeters(@num: number(cm)): number(cm)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(cm)`](/docs/kcl-std/types/std-types-number) | A number | Yes | | `num` | [`number(cm)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns
[`number(cm)`](/docs/kcl-std/types/std-types-number) - A number [`number(cm)`](/docs/kcl-std/types/std-types-number) - A number.

View File

@ -17,11 +17,11 @@ units::toDegrees(@num: number(deg)): number(deg)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(deg)`](/docs/kcl-std/types/std-types-number) | A number | Yes | | `num` | [`number(deg)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns
[`number(deg)`](/docs/kcl-std/types/std-types-number) - A number [`number(deg)`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -17,11 +17,11 @@ units::toFeet(@num: number(ft)): number(ft)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(ft)`](/docs/kcl-std/types/std-types-number) | A number | Yes | | `num` | [`number(ft)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns
[`number(ft)`](/docs/kcl-std/types/std-types-number) - A number [`number(ft)`](/docs/kcl-std/types/std-types-number) - A number.

View File

@ -17,11 +17,11 @@ units::toInches(@num: number(in)): number(in)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(in)`](/docs/kcl-std/types/std-types-number) | A number | Yes | | `num` | [`number(in)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns
[`number(in)`](/docs/kcl-std/types/std-types-number) - A number [`number(in)`](/docs/kcl-std/types/std-types-number) - A number.

View File

@ -17,11 +17,11 @@ units::toMeters(@num: number(m)): number(m)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(m)`](/docs/kcl-std/types/std-types-number) | A number | Yes | | `num` | [`number(m)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns
[`number(m)`](/docs/kcl-std/types/std-types-number) - A number [`number(m)`](/docs/kcl-std/types/std-types-number) - A number.

View File

@ -17,11 +17,11 @@ units::toMillimeters(@num: number(mm)): number(mm)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(mm)`](/docs/kcl-std/types/std-types-number) | A number | Yes | | `num` | [`number(mm)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns
[`number(mm)`](/docs/kcl-std/types/std-types-number) - A number [`number(mm)`](/docs/kcl-std/types/std-types-number) - A number.

View File

@ -17,11 +17,11 @@ units::toRadians(@num: number(rad)): number(rad)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(rad)`](/docs/kcl-std/types/std-types-number) | A number | Yes | | `num` | [`number(rad)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns
[`number(rad)`](/docs/kcl-std/types/std-types-number) - A number [`number(rad)`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -17,11 +17,11 @@ units::toYards(@num: number(yd)): number(yd)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(yd)`](/docs/kcl-std/types/std-types-number) | A number | Yes | | `num` | [`number(yd)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns
[`number(yd)`](/docs/kcl-std/types/std-types-number) - A number [`number(yd)`](/docs/kcl-std/types/std-types-number) - A number.

View File

@ -21,7 +21,7 @@ lastSegX(@sketch: Sketch): number
### Returns ### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number [`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -21,7 +21,7 @@ lastSegY(@sketch: Sketch): number
### Returns ### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number [`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -25,7 +25,7 @@ legAngX(
### Returns ### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number [`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -25,7 +25,7 @@ legAngY(
### Returns ### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number [`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -25,7 +25,7 @@ legLen(
### Returns ### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number [`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -35,7 +35,7 @@ The sketches need to closed and on the same plane.
### Returns ### Returns
[`Solid`](/docs/kcl-std/types/std-types-Solid) - A solid is a collection of extrude surfaces. [`Solid`](/docs/kcl-std/types/std-types-Solid) - A solid is a collection of extruded surfaces.
### Examples ### Examples

View File

@ -1,11 +1,11 @@
--- ---
title: "array" title: "array"
subtitle: "Module in std" subtitle: "Module in std"
excerpt: "" excerpt: "Functions for manipulating arrays of values. "
layout: manual layout: manual
--- ---
Functions for manipulating arrays of values.

View File

@ -1,11 +1,11 @@
--- ---
title: "math" title: "math"
subtitle: "Module in std" subtitle: "Module in std"
excerpt: "" excerpt: "Functions for mathematical operations and some useful constants. "
layout: manual layout: manual
--- ---
Functions for mathematical operations and some useful constants.

View File

@ -1,13 +1,13 @@
--- ---
title: "sketch" title: "sketch"
subtitle: "Module in std" subtitle: "Module in std"
excerpt: "" excerpt: "Sketching is the foundational activity for most KCL programs. A sketch is a two-dimensional drawing made from paths or shapes. A sketch is always drawn on a surface (either an abstract plane of a face of a solid). A sketch can be made into a solid by extruding it (or revolving, etc.). "
layout: manual layout: manual
--- ---
Sketching is the foundational activity for most KCL programs. A sketch is a two-dimensional drawing made from paths or shapes. A sketch is always drawn on a surface (either an abstract plane of a face of a solid). A sketch can be made into a solid by extruding it (or revolving, etc.).
This module contains functions for creating and manipulating sketches, and making them into solids.
## Functions and constants ## Functions and constants

View File

@ -1,11 +1,11 @@
--- ---
title: "solid" title: "solid"
subtitle: "Module in std" subtitle: "Module in std"
excerpt: "" excerpt: "This module contains functions for modifying solids, e.g., by adding a fillet or chamfer, or removing part of a solid. "
layout: manual layout: manual
--- ---
This module contains functions for modifying solids, e.g., by adding a fillet or chamfer, or removing part of a solid.

View File

@ -1,11 +1,11 @@
--- ---
title: "transform" title: "transform"
subtitle: "Module in std" subtitle: "Module in std"
excerpt: "" excerpt: "This module contains functions for transforming sketches and solids. "
layout: manual layout: manual
--- ---
This module contains functions for transforming sketches and solids.

View File

@ -1,11 +1,11 @@
--- ---
title: "turns" title: "turns"
subtitle: "Module in std" subtitle: "Module in std"
excerpt: "" excerpt: "This module contains a few handy constants for defining turns. "
layout: manual layout: manual
--- ---
This module contains a few handy constants for defining turns.

View File

@ -1,13 +1,13 @@
--- ---
title: "types" title: "types"
subtitle: "Module in std" subtitle: "Module in std"
excerpt: "" excerpt: "KCL types. This module contains fundamental types like `number`, `string`, `Solid`, and `Sketch`. "
layout: manual layout: manual
--- ---
KCL types. This module contains fundamental types like `number`, `string`, `Solid`, and `Sketch`.
Types can (optionally) be used to describe a function's arguments and returned value. They are checked when a program runs and can help avoid errors. They are also useful to help document what a function does.

View File

@ -7,7 +7,9 @@ layout: manual
Functions for converting numbers to different units. Functions for converting numbers to different units.
All numbers in KCL include units, e.g., the number `42` is always '42 mm' or '42 degrees', etc. it is never just '42'. For more information, see [numeric types](/docs/kcl-lang/numeric).
Note that you only need to explicitly convert the units of a number if you need a specific unit for your own calculations. When calling a function, KCL will convert a number to the required units automatically (where possible, and give an error or warning if it's not possible).
## Functions and constants ## Functions and constants

View File

@ -9,6 +9,8 @@ The KCL standard library
Contains frequently used constants, functions for interacting with the KittyCAD servers to create sketches and geometry, and utility functions. Contains frequently used constants, functions for interacting with the KittyCAD servers to create sketches and geometry, and utility functions.
The standard library is organised into modules (listed below), but most things are always available in KCL programs.
## Modules ## Modules
* [`array`](/docs/kcl-std/modules/std-array) * [`array`](/docs/kcl-std/modules/std-array)

View File

@ -21,7 +21,7 @@ profileStartX(@profile: Sketch): number
### Returns ### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number [`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -21,7 +21,7 @@ profileStartY(@profile: Sketch): number
### Returns ### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number [`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -21,7 +21,7 @@ segAng(@tag: TagIdentifier): number
### Returns ### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number [`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -21,7 +21,7 @@ segEndX(@tag: TagIdentifier): number
### Returns ### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number [`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -21,7 +21,7 @@ segEndY(@tag: TagIdentifier): number
### Returns ### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number [`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -21,7 +21,7 @@ segLen(@tag: TagIdentifier): number
### Returns ### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number [`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -21,7 +21,7 @@ segStartX(@tag: TagIdentifier): number
### Returns ### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number [`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -21,7 +21,7 @@ segStartY(@tag: TagIdentifier): number
### Returns ### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number [`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -21,7 +21,7 @@ tangentToEnd(@tag: TagIdentifier): number
### Returns ### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number [`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples ### Examples

View File

@ -1,11 +1,11 @@
--- ---
title: "Axis2d" title: "Axis2d"
subtitle: "Type in std::types" subtitle: "Type in std::types"
excerpt: "An infinite line in 2d space." excerpt: "An abstract and infinite line in 2d space."
layout: manual layout: manual
--- ---
An infinite line in 2d space. An abstract and infinite line in 2d space.

View File

@ -1,11 +1,11 @@
--- ---
title: "Axis3d" title: "Axis3d"
subtitle: "Type in std::types" subtitle: "Type in std::types"
excerpt: "An infinite line in 3d space." excerpt: "An abstract and infinite line in 3d space."
layout: manual layout: manual
--- ---
An infinite line in 3d space. An abstract and infinite line in 3d space.

View File

@ -1,11 +1,11 @@
--- ---
title: "Edge" title: "Edge"
subtitle: "Type in std::types" subtitle: "Type in std::types"
excerpt: "The edge of a solid." excerpt: "An edge of a solid."
layout: manual layout: manual
--- ---
The edge of a solid. An edge of a solid.

View File

@ -1,11 +1,11 @@
--- ---
title: "Face" title: "Face"
subtitle: "Type in std::types" subtitle: "Type in std::types"
excerpt: "A face." excerpt: "A face of a solid."
layout: manual layout: manual
--- ---
A face. A face of a solid.

View File

@ -1,11 +1,11 @@
--- ---
title: "Helix" title: "Helix"
subtitle: "Type in std::types" subtitle: "Type in std::types"
excerpt: "A helix." excerpt: "A helix; created by the `helix` function."
layout: manual layout: manual
--- ---
A helix. A helix; created by the `helix` function.

View File

@ -1,13 +1,14 @@
--- ---
title: "Plane" title: "Plane"
subtitle: "Type in std::types" subtitle: "Type in std::types"
excerpt: "A plane." excerpt: "An abstract plane."
layout: manual layout: manual
--- ---
A plane. An abstract plane.
A plane has a position and orientation in space defined by its origin and axes. A plane can be used
to sketch on.

View File

@ -1,11 +1,11 @@
--- ---
title: "Solid" title: "Solid"
subtitle: "Type in std::types" subtitle: "Type in std::types"
excerpt: "A solid is a collection of extrude surfaces." excerpt: "A solid is a collection of extruded surfaces."
layout: manual layout: manual
--- ---
A solid is a collection of extrude surfaces. A solid is a collection of extruded surfaces.
When you define a solid to a variable like: When you define a solid to a variable like:

View File

@ -1,13 +1,28 @@
--- ---
title: "any" title: "any"
subtitle: "Type in std::types" subtitle: "Type in std::types"
excerpt: "Any value." excerpt: ""
layout: manual layout: manual
--- ---
Any value.
The [`any`](/docs/kcl-std/types/std-types-any) type is the type of all possible values in KCL. I.e., if a function accepts an argument
with type [`any`](/docs/kcl-std/types/std-types-any), then it can accept any value.
### Examples
```kcl
fn acceptAnything(@input: any) {
return true
}
acceptAnything(42)
acceptAnything('hello')
acceptAnything(XY)
acceptAnything([0, 1, 2])
```

View File

@ -7,7 +7,7 @@ layout: manual
A boolean value. A boolean value.
`true` or `false` `true` or `false`.

View File

@ -1,15 +1,28 @@
--- ---
title: "number" title: "number"
subtitle: "Type in std::types" subtitle: "Type in std::types"
excerpt: "A number" excerpt: "A number."
layout: manual layout: manual
--- ---
A number A number.
May be signed or unsigned, an integer or decimal value. May be signed or unsigned, an integer or decimal value.
You may see a number type with units, e.g., [`number(mm)`](/docs/kcl-std/types/std-types-number). These are currently experimental. KCL numbers always include units, e.g., the number `42` is always '42 mm' or '42 degrees', etc.
it is never just '42'. The [`number`](/docs/kcl-std/types/std-types-number) type may or may not include units, if none are specified, then
it is the type of any number. E.g.,
- [`number`](/docs/kcl-std/types/std-types-number): the type of any numbers,
- [`number(mm)`](/docs/kcl-std/types/std-types-number): the type of numbers in millimeters,
- [`number(in)`](/docs/kcl-std/types/std-types-number): the type of numbers in inches,
- [`number(Length)`](/docs/kcl-std/types/std-types-number): the type of numbers in any length unit,
- [`number(deg)`](/docs/kcl-std/types/std-types-number): the type of numbers in degrees,
- [`number(Angle)`](/docs/kcl-std/types/std-types-number): the type of numbers in any angle unit,
- [`number(_)`](/docs/kcl-std/types/std-types-number) or [`number(Count)`](/docs/kcl-std/types/std-types-number): the type of unit-less numbers, representing a count of things,
or a ratio, etc.
For more information, see [numeric types](/docs/kcl-lang/numeric).

View File

@ -138,7 +138,9 @@ extrude001 = extrude(sketch001, length = 5)`
// Ensure badge is present // Ensure badge is present
const codePaneButtonHolder = page.locator('#code-button-holder') const codePaneButtonHolder = page.locator('#code-button-holder')
await expect(codePaneButtonHolder).toContainText('notification') await expect(codePaneButtonHolder).toContainText('notification', {
timeout: 20_000,
})
// Ensure we have no errors in the gutter, since error out of view. // Ensure we have no errors in the gutter, since error out of view.
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()

View File

@ -1180,6 +1180,8 @@ sketch001 = startSketchOn(XZ)
page, page,
homePage, homePage,
editor, editor,
scene,
cmdBar,
}) => { }) => {
const u = await getUtils(page) const u = await getUtils(page)
await page.addInitScript(async () => { await page.addInitScript(async () => {
@ -1198,9 +1200,7 @@ sketch001 = startSketchOn(XZ)
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await expect( await scene.settled(cmdBar)
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await page.waitForTimeout(100) await page.waitForTimeout(100)
await u.openAndClearDebugPanel() await u.openAndClearDebugPanel()
@ -1236,7 +1236,7 @@ sketch001 = startSketchOn(XZ)
await page.waitForTimeout(400) await page.waitForTimeout(400)
let prevContent = await page.locator('.cm-content').innerText() let prevContent = await page.locator('.cm-content').innerText()
await expect(page.getByTestId('segment-overlay')).toHaveCount(2) await expect(page.getByTestId('segment-overlay')).toHaveCount(3)
// drag startProfileAt handle // drag startProfileAt handle
await page.dragAndDrop('#stream', '#stream', { await page.dragAndDrop('#stream', '#stream', {
@ -1278,9 +1278,9 @@ sketch001 = startSketchOn(XZ)
// expect the code to have changed // expect the code to have changed
await editor.expectEditor.toContain( await editor.expectEditor.toContain(
`sketch001 = startSketchOn(XZ) `sketch001 = startSketchOn(XZ)
|> startProfile(at = [2.71, -2.71]) |> startProfile(at = [5.36, -5.36])
|> line(end = [15.4, -2.78]) |> line(end = [12.73, -0.09])
|> tangentialArc(endAbsolute = [27.6, -3.05]) |> tangentialArc(endAbsolute = [24.95, -0.38])
|> close() |> close()
|> extrude(length = 5)`, |> extrude(length = 5)`,
{ shouldNormalise: true } { shouldNormalise: true }
@ -1294,7 +1294,7 @@ sketch001 = startSketchOn(XZ)
await editor.expectEditor.toContain( await editor.expectEditor.toContain(
`sketch001 = startSketchOn(XZ) `sketch001 = startSketchOn(XZ)
|> startProfile(at = [2.71, -2.71]) |> startProfile(at = [2.71, -2.71])
|> line(end = [15.4, -2.78]) |> line(end = [12.73, -0.09])
|> tangentialArc(endAbsolute = [24.95, -0.38]) |> tangentialArc(endAbsolute = [24.95, -0.38])
|> close() |> close()
|> extrude(length = 5)`, |> extrude(length = 5)`,
@ -1308,7 +1308,7 @@ sketch001 = startSketchOn(XZ)
await editor.expectEditor.toContain( await editor.expectEditor.toContain(
`sketch001 = startSketchOn(XZ) `sketch001 = startSketchOn(XZ)
|> startProfile(at = [2.71, -2.71]) |> startProfile(at = [4.61, -10.01])
|> line(end = [12.73, -0.09]) |> line(end = [12.73, -0.09])
|> tangentialArc(endAbsolute = [24.95, -0.38]) |> tangentialArc(endAbsolute = [24.95, -0.38])
|> close() |> close()
@ -1505,7 +1505,7 @@ sketch001 = startSketchOn(XZ)
await page.waitForTimeout(1000) await page.waitForTimeout(1000)
// Verify segment is selected (you can check for visual indicators or state) // Verify segment is selected (you can check for visual indicators or state)
const element = page.locator('[data-overlay-index="1"]') const element = page.locator('[data-overlay-index="2"]')
await expect(element).toHaveAttribute('data-overlay-visible', 'true') await expect(element).toHaveAttribute('data-overlay-visible', 'true')
}) })

View File

@ -2388,6 +2388,7 @@ fillet001 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg01)])
scene, scene,
editor, editor,
toolbar, toolbar,
cmdBar,
}) => { }) => {
// Code samples // Code samples
const initialCode = `sketch001 = startSketchOn(XY) const initialCode = `sketch001 = startSketchOn(XY)
@ -2401,14 +2402,14 @@ extrude001 = extrude(sketch001, length = -12)
|> fillet(radius = 5, tags = [seg01]) // fillet01 |> fillet(radius = 5, tags = [seg01]) // fillet01
|> fillet(radius = 5, tags = [seg02]) // fillet02 |> fillet(radius = 5, tags = [seg02]) // fillet02
fillet03 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg01)]) fillet03 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg01)])
fillet04 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg02)]) fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg02)])
` `
const pipedFilletDeclaration = 'fillet(radius = 5, tags = [seg01])' const firstPipedFilletDeclaration = 'fillet(radius = 5, tags = [seg01])'
const secondPipedFilletDeclaration = 'fillet(radius = 5, tags = [seg02])' const secondPipedFilletDeclaration = 'fillet(radius = 5, tags = [seg02])'
const standaloneFilletDeclaration = const standaloneAssignedFilletDeclaration =
'fillet03 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg01)])' 'fillet03 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg01)])'
const secondStandaloneFilletDeclaration = const standaloneUnassignedFilletDeclaration =
'fillet04 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg02)])' 'fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg02)])'
// Locators // Locators
const pipedFilletEdgeLocation = { x: 600, y: 193 } const pipedFilletEdgeLocation = { x: 600, y: 193 }
@ -2430,6 +2431,7 @@ fillet04 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg02)])
}, initialCode) }, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.settled(cmdBar)
// verify modeling scene is loaded // verify modeling scene is loaded
await scene.expectPixelColor( await scene.expectPixelColor(
@ -2446,15 +2448,19 @@ fillet04 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg02)])
await test.step('Delete fillet via feature tree selection', async () => { await test.step('Delete fillet via feature tree selection', async () => {
await test.step('Open Feature Tree Pane', async () => { await test.step('Open Feature Tree Pane', async () => {
await toolbar.openPane('feature-tree') await toolbar.openPane('feature-tree')
await page.waitForTimeout(500) await scene.settled(cmdBar)
}) })
await test.step('Delete piped fillet via feature tree selection', async () => { await test.step('Delete piped fillet via feature tree selection', async () => {
await test.step('Verify all fillets are present in the editor', async () => { await test.step('Verify all fillets are present in the editor', async () => {
await editor.expectEditor.toContain(pipedFilletDeclaration) await editor.expectEditor.toContain(firstPipedFilletDeclaration)
await editor.expectEditor.toContain(secondPipedFilletDeclaration) await editor.expectEditor.toContain(secondPipedFilletDeclaration)
await editor.expectEditor.toContain(standaloneFilletDeclaration) await editor.expectEditor.toContain(
await editor.expectEditor.toContain(secondStandaloneFilletDeclaration) standaloneAssignedFilletDeclaration
)
await editor.expectEditor.toContain(
standaloneUnassignedFilletDeclaration
)
}) })
await test.step('Verify test fillets are present in the scene', async () => { await test.step('Verify test fillets are present in the scene', async () => {
await scene.expectPixelColor( await scene.expectPixelColor(
@ -2475,13 +2481,17 @@ fillet04 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg02)])
) )
await operationButton.click({ button: 'left' }) await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete') await page.keyboard.press('Delete')
await page.waitForTimeout(500) await scene.settled(cmdBar)
}) })
await test.step('Verify piped fillet is deleted but other fillets are not (in the editor)', async () => { await test.step('Verify piped fillet is deleted but other fillets are not (in the editor)', async () => {
await editor.expectEditor.not.toContain(pipedFilletDeclaration) await editor.expectEditor.not.toContain(firstPipedFilletDeclaration)
await editor.expectEditor.toContain(secondPipedFilletDeclaration) await editor.expectEditor.toContain(secondPipedFilletDeclaration)
await editor.expectEditor.toContain(standaloneFilletDeclaration) await editor.expectEditor.toContain(
await editor.expectEditor.toContain(secondStandaloneFilletDeclaration) standaloneAssignedFilletDeclaration
)
await editor.expectEditor.toContain(
standaloneUnassignedFilletDeclaration
)
}) })
await test.step('Verify piped fillet is deleted but non-piped is not (in the scene)', async () => { await test.step('Verify piped fillet is deleted but non-piped is not (in the scene)', async () => {
await scene.expectPixelColor( await scene.expectPixelColor(
@ -2497,22 +2507,51 @@ fillet04 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg02)])
}) })
}) })
await test.step('Delete non-piped fillet via feature tree selection', async () => { await test.step('Delete standalone assigned fillet via feature tree selection', async () => {
await test.step('Delete non-piped fillet', async () => { await test.step('Delete standalone assigned fillet', async () => {
const operationButton = await toolbar.getFeatureTreeOperation( const operationButton = await toolbar.getFeatureTreeOperation(
'Fillet', 'Fillet',
1 1
) )
await operationButton.click({ button: 'left' }) await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete') await page.keyboard.press('Delete')
await page.waitForTimeout(500) await scene.settled(cmdBar)
}) })
await test.step('Verify non-piped fillet is deleted but other two fillets are not (in the editor)', async () => { await test.step('Verify standalone assigned fillet is deleted but other two fillets are not (in the editor)', async () => {
await editor.expectEditor.toContain(secondPipedFilletDeclaration) await editor.expectEditor.toContain(secondPipedFilletDeclaration)
await editor.expectEditor.not.toContain(standaloneFilletDeclaration) await editor.expectEditor.not.toContain(
await editor.expectEditor.toContain(secondStandaloneFilletDeclaration) standaloneAssignedFilletDeclaration
)
await editor.expectEditor.toContain(
standaloneUnassignedFilletDeclaration
)
}) })
await test.step('Verify non-piped fillet is deleted but piped is not (in the scene)', async () => { await test.step('Verify standalone assigned fillet is deleted but piped is not (in the scene)', async () => {
await scene.expectPixelColor(
edgeColorWhite,
standaloneFilletEdgeLocation,
lowTolerance
)
})
})
await test.step('Delete standalone unassigned fillet via feature tree selection', async () => {
await test.step('Delete standalone unassigned fillet', async () => {
const operationButton = await toolbar.getFeatureTreeOperation(
'Fillet',
1
)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete')
await scene.settled(cmdBar)
})
await test.step('Verify standalone unassigned fillet is deleted but other fillet is not (in the editor)', async () => {
await editor.expectEditor.toContain(secondPipedFilletDeclaration)
await editor.expectEditor.not.toContain(
standaloneUnassignedFilletDeclaration
)
})
await test.step('Verify standalone unassigned fillet is deleted but piped is not (in the scene)', async () => {
await scene.expectPixelColor( await scene.expectPixelColor(
edgeColorWhite, edgeColorWhite,
standaloneFilletEdgeLocation, standaloneFilletEdgeLocation,
@ -2964,14 +3003,14 @@ extrude001 = extrude(sketch001, length = -12)
|> chamfer(length = 5, tags = [seg01]) // chamfer01 |> chamfer(length = 5, tags = [seg01]) // chamfer01
|> chamfer(length = 5, tags = [seg02]) // chamfer02 |> chamfer(length = 5, tags = [seg02]) // chamfer02
chamfer03 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg01)]) chamfer03 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg01)])
chamfer04 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg02)]) chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg02)])
` `
const pipedChamferDeclaration = 'chamfer(length = 5, tags = [seg01])' const firstPipedChamferDeclaration = 'chamfer(length = 5, tags = [seg01])'
const secondPipedChamferDeclaration = 'chamfer(length = 5, tags = [seg02])' const secondPipedChamferDeclaration = 'chamfer(length = 5, tags = [seg02])'
const standaloneChamferDeclaration = const standaloneAssignedChamferDeclaration =
'chamfer03 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg01)])' 'chamfer03 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg01)])'
const secondStandaloneChamferDeclaration = const standaloneUnassignedChamferDeclaration =
'chamfer04 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg02)])' 'chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg02)])'
// Locators // Locators
const pipedChamferEdgeLocation = { x: 600, y: 193 } const pipedChamferEdgeLocation = { x: 600, y: 193 }
@ -3010,16 +3049,18 @@ chamfer04 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg02)])
await test.step('Delete chamfer via feature tree selection', async () => { await test.step('Delete chamfer via feature tree selection', async () => {
await test.step('Open Feature Tree Pane', async () => { await test.step('Open Feature Tree Pane', async () => {
await toolbar.openPane('feature-tree') await toolbar.openPane('feature-tree')
await page.waitForTimeout(500) await scene.settled(cmdBar)
}) })
await test.step('Delete piped chamfer via feature tree selection', async () => { await test.step('Delete piped chamfer via feature tree selection', async () => {
await test.step('Verify all chamfers are present in the editor', async () => { await test.step('Verify all chamfers are present in the editor', async () => {
await editor.expectEditor.toContain(pipedChamferDeclaration) await editor.expectEditor.toContain(firstPipedChamferDeclaration)
await editor.expectEditor.toContain(secondPipedChamferDeclaration) await editor.expectEditor.toContain(secondPipedChamferDeclaration)
await editor.expectEditor.toContain(standaloneChamferDeclaration)
await editor.expectEditor.toContain( await editor.expectEditor.toContain(
secondStandaloneChamferDeclaration standaloneAssignedChamferDeclaration
)
await editor.expectEditor.toContain(
standaloneUnassignedChamferDeclaration
) )
}) })
await test.step('Verify test chamfers are present in the scene', async () => { await test.step('Verify test chamfers are present in the scene', async () => {
@ -3041,14 +3082,16 @@ chamfer04 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg02)])
) )
await operationButton.click({ button: 'left' }) await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete') await page.keyboard.press('Delete')
await page.waitForTimeout(500) await scene.settled(cmdBar)
}) })
await test.step('Verify piped chamfer is deleted but other chamfers are not (in the editor)', async () => { await test.step('Verify piped chamfer is deleted but other chamfers are not (in the editor)', async () => {
await editor.expectEditor.not.toContain(pipedChamferDeclaration) await editor.expectEditor.not.toContain(firstPipedChamferDeclaration)
await editor.expectEditor.toContain(secondPipedChamferDeclaration) await editor.expectEditor.toContain(secondPipedChamferDeclaration)
await editor.expectEditor.toContain(standaloneChamferDeclaration)
await editor.expectEditor.toContain( await editor.expectEditor.toContain(
secondStandaloneChamferDeclaration standaloneAssignedChamferDeclaration
)
await editor.expectEditor.toContain(
standaloneUnassignedChamferDeclaration
) )
}) })
await test.step('Verify piped chamfer is deleted but non-piped is not (in the scene)', async () => { await test.step('Verify piped chamfer is deleted but non-piped is not (in the scene)', async () => {
@ -3065,24 +3108,51 @@ chamfer04 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg02)])
}) })
}) })
await test.step('Delete non-piped chamfer via feature tree selection', async () => { await test.step('Delete standalone assigned chamfer via feature tree selection', async () => {
await test.step('Delete non-piped chamfer', async () => { await test.step('Delete standalone assigned chamfer', async () => {
const operationButton = await toolbar.getFeatureTreeOperation( const operationButton = await toolbar.getFeatureTreeOperation(
'Chamfer', 'Chamfer',
1 1
) )
await operationButton.click({ button: 'left' }) await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete') await page.keyboard.press('Delete')
await page.waitForTimeout(500) await scene.settled(cmdBar)
}) })
await test.step('Verify non-piped chamfer is deleted but other two chamfers are not (in the editor)', async () => { await test.step('Verify standalone assigned chamfer is deleted but other two chamfers are not (in the editor)', async () => {
await editor.expectEditor.toContain(secondPipedChamferDeclaration) await editor.expectEditor.toContain(secondPipedChamferDeclaration)
await editor.expectEditor.not.toContain(standaloneChamferDeclaration) await editor.expectEditor.not.toContain(
standaloneAssignedChamferDeclaration
)
await editor.expectEditor.toContain( await editor.expectEditor.toContain(
secondStandaloneChamferDeclaration standaloneUnassignedChamferDeclaration
) )
}) })
await test.step('Verify non-piped chamfer is deleted but piped is not (in the scene)', async () => { await test.step('Verify standalone assigned chamfer is deleted but piped is not (in the scene)', async () => {
await scene.expectPixelColor(
edgeColorWhite,
standaloneChamferEdgeLocation,
lowTolerance
)
})
})
await test.step('Delete standalone unassigned chamfer via feature tree selection', async () => {
await test.step('Delete standalone unassigned chamfer', async () => {
const operationButton = await toolbar.getFeatureTreeOperation(
'Chamfer',
1
)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete')
await scene.settled(cmdBar)
})
await test.step('Verify standalone unassigned chamfer is deleted but piped chamfer is not (in the editor)', async () => {
await editor.expectEditor.toContain(secondPipedChamferDeclaration)
await editor.expectEditor.not.toContain(
standaloneUnassignedChamferDeclaration
)
})
await test.step('Verify standalone unassigned chamfer is deleted but piped is not (in the scene)', async () => {
await scene.expectPixelColor( await scene.expectPixelColor(
edgeColorWhite, edgeColorWhite,
standaloneChamferEdgeLocation, standaloneChamferEdgeLocation,

View File

@ -543,6 +543,9 @@ sketch001 = startSketchOn(XZ)
page, page,
homePage, homePage,
editor, editor,
toolbar,
scene,
cmdBar,
}) => { }) => {
const u = await getUtils(page) const u = await getUtils(page)
await page.addInitScript(async () => { await page.addInitScript(async () => {
@ -559,10 +562,12 @@ sketch001 = startSketchOn(XZ)
}) })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await toolbar.waitForFeatureTreeToBeBuilt()
await scene.settled(cmdBar)
await expect( await expect(
page.getByRole('button', { name: 'Start Sketch' }) page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled() ).not.toBeDisabled({ timeout: 10_000 })
await page.waitForTimeout(100) await page.waitForTimeout(100)
await u.openAndClearDebugPanel() await u.openAndClearDebugPanel()
@ -598,7 +603,7 @@ sketch001 = startSketchOn(XZ)
await page.waitForTimeout(400) await page.waitForTimeout(400)
let prevContent = await page.locator('.cm-content').innerText() let prevContent = await page.locator('.cm-content').innerText()
await expect(page.getByTestId('segment-overlay')).toHaveCount(2) await expect(page.getByTestId('segment-overlay')).toHaveCount(3)
// drag startProfileAt handle // drag startProfileAt handle
await page.dragAndDrop('#stream', '#stream', { await page.dragAndDrop('#stream', '#stream', {
@ -612,7 +617,7 @@ sketch001 = startSketchOn(XZ)
// drag line handle // drag line handle
await page.waitForTimeout(100) await page.waitForTimeout(100)
const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]') const lineEnd = await u.getBoundingBox('[data-overlay-index="1"]')
await page.dragAndDrop('#stream', '#stream', { await page.dragAndDrop('#stream', '#stream', {
sourcePosition: { x: lineEnd.x - 15, y: lineEnd.y }, sourcePosition: { x: lineEnd.x - 15, y: lineEnd.y },
targetPosition: { x: lineEnd.x, y: lineEnd.y + 15 }, targetPosition: { x: lineEnd.x, y: lineEnd.y + 15 },
@ -622,7 +627,7 @@ sketch001 = startSketchOn(XZ)
prevContent = await page.locator('.cm-content').innerText() prevContent = await page.locator('.cm-content').innerText()
// drag tangentialArc handle // drag tangentialArc handle
const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]') const tangentEnd = await u.getBoundingBox('[data-overlay-index="0"]')
await page.dragAndDrop('#stream', '#stream', { await page.dragAndDrop('#stream', '#stream', {
sourcePosition: { x: tangentEnd.x + 10, y: tangentEnd.y - 5 }, sourcePosition: { x: tangentEnd.x + 10, y: tangentEnd.y - 5 },
targetPosition: { targetPosition: {
@ -638,7 +643,7 @@ sketch001 = startSketchOn(XZ)
`sketch001 = startSketchOn(XZ) `sketch001 = startSketchOn(XZ)
|> startProfile(at = [7.12, -12.68]) |> startProfile(at = [7.12, -12.68])
|> line(end = [12.68, -1.09]) |> line(end = [12.68, -1.09])
|> tangentialArc(endAbsolute = [24.89, 0.68]) |> tangentialArc(endAbsolute = [24.95, -0.38])
|> close() |> close()
|> extrude(length = 5)`, |> extrude(length = 5)`,
{ shouldNormalise: true } { shouldNormalise: true }
@ -709,7 +714,7 @@ sketch001 = startSketchOn(XZ)
const step5 = { steps: 5 } const step5 = { steps: 5 }
await expect(page.getByTestId('segment-overlay')).toHaveCount(2) await expect(page.getByTestId('segment-overlay')).toHaveCount(3)
// drag startProfileAt handle // drag startProfileAt handle
await page.mouse.move(startPX[0], startPX[1]) await page.mouse.move(startPX[0], startPX[1])
@ -744,10 +749,10 @@ sketch001 = startSketchOn(XZ)
// expect the code to have changed // expect the code to have changed
await editor.expectEditor.toContain( await editor.expectEditor.toContain(
`sketch001 = startSketchOn(XZ) `sketch001 = startSketchOn(XZ)
|> startProfile(at = [6.44, -12.07]) |> startProfile(at = [8.41, -9.97])
|> line(end = [14.72, 1.97]) |> line(end = [12.73, -0.09])
|> line(end = [1.99, 2.06])
|> tangentialArc(endAbsolute = [24.95, -5.38]) |> tangentialArc(endAbsolute = [24.95, -5.38])
|> line(end = [1.97, 2.06])
|> close() |> close()
|> revolve(axis = X)`, |> revolve(axis = X)`,
{ shouldNormalise: true } { shouldNormalise: true }

View File

@ -108,7 +108,7 @@ test.describe('Testing constraints', () => {
// Wait for overlays to populate // Wait for overlays to populate
await page.waitForTimeout(1000) await page.waitForTimeout(1000)
const line3 = await u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`) const line3 = await u.getSegmentBodyCoords(`[data-overlay-index="${3}"]`)
await page.mouse.click(line3.x, line3.y) await page.mouse.click(line3.x, line3.y)
await page.waitForTimeout(100) // this wait is needed for webkit - not sure why await page.waitForTimeout(100) // this wait is needed for webkit - not sure why
@ -127,7 +127,7 @@ test.describe('Testing constraints', () => {
) )
// checking the count of the overlays is a good proxy check that the client sketch scene is in a good state // checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
await expect(page.getByTestId('segment-overlay')).toHaveCount(4) await expect(page.getByTestId('segment-overlay')).toHaveCount(5)
}) })
test.describe('Test perpendicular distance constraint', () => { test.describe('Test perpendicular distance constraint', () => {
const cases = [ const cases = [
@ -174,8 +174,8 @@ test.describe('Testing constraints', () => {
await page.waitForTimeout(1000) await page.waitForTimeout(1000)
const [line1, line3] = await Promise.all([ const [line1, line3] = await Promise.all([
u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`), u.getSegmentBodyCoords(`[data-overlay-index="${1}"]`),
u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`), u.getSegmentBodyCoords(`[data-overlay-index="${3}"]`),
]) ])
await page.mouse.click(line1.x, line1.y) await page.mouse.click(line1.x, line1.y)
@ -223,7 +223,7 @@ test.describe('Testing constraints', () => {
) )
// checking the count of the overlays is a good proxy check that the client sketch scene is in a good state // checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
await expect(page.getByTestId('segment-overlay')).toHaveCount(4) await expect(page.getByTestId('segment-overlay')).toHaveCount(5)
}) })
} }
}) })
@ -289,8 +289,8 @@ test.describe('Testing constraints', () => {
await page.waitForTimeout(1000) await page.waitForTimeout(1000)
const [line1, line3] = await Promise.all([ const [line1, line3] = await Promise.all([
u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`), u.getSegmentBodyCoords(`[data-overlay-index="${1}"]`),
u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`), u.getSegmentBodyCoords(`[data-overlay-index="${3}"]`),
]) ])
await page.mouse.click(line1.x, line1.y) await page.mouse.click(line1.x, line1.y)
@ -335,7 +335,7 @@ test.describe('Testing constraints', () => {
) )
// checking the count of the overlays is a good proxy check that the client sketch scene is in a good state // checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
await expect(page.getByTestId('segment-overlay')).toHaveCount(4) await expect(page.getByTestId('segment-overlay')).toHaveCount(5)
}) })
} }
}) })
@ -405,7 +405,7 @@ test.describe('Testing constraints', () => {
await page.waitForTimeout(1000) await page.waitForTimeout(1000)
const [line3] = await Promise.all([ const [line3] = await Promise.all([
u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`), u.getSegmentBodyCoords(`[data-overlay-index="${3}"]`),
]) ])
if (constraint === 'Absolute X') { if (constraint === 'Absolute X') {
@ -454,7 +454,7 @@ test.describe('Testing constraints', () => {
) )
// checking the count of the overlays is a good proxy check that the client sketch scene is in a good state // checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
await expect(page.getByTestId('segment-overlay')).toHaveCount(4) await expect(page.getByTestId('segment-overlay')).toHaveCount(5)
}) })
} }
}) })
@ -519,8 +519,8 @@ test.describe('Testing constraints', () => {
await page.waitForTimeout(1000) await page.waitForTimeout(1000)
const [line1, line3] = await Promise.all([ const [line1, line3] = await Promise.all([
u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`), u.getSegmentBodyCoords(`[data-overlay-index="${1}"]`),
u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`), u.getSegmentBodyCoords(`[data-overlay-index="${3}"]`),
]) ])
if (axisSelect) { if (axisSelect) {
@ -569,7 +569,7 @@ test.describe('Testing constraints', () => {
) )
// checking the count of the overlays is a good proxy check that the client sketch scene is in a good state // checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
await expect(page.getByTestId('segment-overlay')).toHaveCount(4) await expect(page.getByTestId('segment-overlay')).toHaveCount(5)
}) })
} }
}) })
@ -622,7 +622,7 @@ test.describe('Testing constraints', () => {
await page.waitForTimeout(1000) await page.waitForTimeout(1000)
const line3 = await u.getSegmentBodyCoords( const line3 = await u.getSegmentBodyCoords(
`[data-overlay-index="${2}"]` `[data-overlay-index="${3}"]`
) )
await page.mouse.click(line3.x, line3.y) await page.mouse.click(line3.x, line3.y)
@ -647,7 +647,7 @@ test.describe('Testing constraints', () => {
await expect(page.locator('.cm-activeLine')).toHaveText(changedCode) await expect(page.locator('.cm-activeLine')).toHaveText(changedCode)
// checking the count of the overlays is a good proxy check that the client sketch scene is in a good state // checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
await expect(page.getByTestId('segment-overlay')).toHaveCount(4) await expect(page.getByTestId('segment-overlay')).toHaveCount(5)
}) })
} }
}) })
@ -715,7 +715,7 @@ part002 = startSketchOn(XZ)
await page.getByRole('button', { name: 'Edit Sketch' }).click() await page.getByRole('button', { name: 'Edit Sketch' }).click()
const line3 = await u.getSegmentBodyCoords( const line3 = await u.getSegmentBodyCoords(
`[data-overlay-index="${2}"]` `[data-overlay-index="${3}"]`
) )
await page.mouse.click(line3.x, line3.y) await page.mouse.click(line3.x, line3.y)
@ -733,16 +733,17 @@ part002 = startSketchOn(XZ)
}) })
} }
await expect(cmdBarKclInput).toHaveText('78.33') await expect(cmdBarKclInput).toHaveText('78.33')
await cmdBarSubmitButton.click() await page.waitForTimeout(500)
const [ang, len] = value.split(', ') const [ang, len] = value.split(', ')
const changedCode = `|> angledLine(angle = ${ang}, length = ${len})` const changedCode = `|> angledLine(angle = ${ang}, length = ${len})`
await cmdBarSubmitButton.click()
await expect(page.locator('.cm-content')).toContainText(changedCode) await expect(page.locator('.cm-content')).toContainText(changedCode)
// checking active assures the cursor is where it should be // checking active assures the cursor is where it should be
await expect(page.locator('.cm-activeLine')).toHaveText(changedCode) await expect(page.locator('.cm-activeLine')).toHaveText(changedCode)
// checking the count of the overlays is a good proxy check that the client sketch scene is in a good state // checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
await expect(page.getByTestId('segment-overlay')).toHaveCount(4) await expect(page.getByTestId('segment-overlay')).toHaveCount(5)
}) })
} }
}) })
@ -799,13 +800,13 @@ part002 = startSketchOn(XZ)
await page.waitForTimeout(1000) await page.waitForTimeout(1000)
const line1 = await u.getSegmentBodyCoords( const line1 = await u.getSegmentBodyCoords(
`[data-overlay-index="${0}"]` `[data-overlay-index="${1}"]`
) )
const line3 = await u.getSegmentBodyCoords( const line3 = await u.getSegmentBodyCoords(
`[data-overlay-index="${2}"]` `[data-overlay-index="${3}"]`
) )
const line4 = await u.getSegmentBodyCoords( const line4 = await u.getSegmentBodyCoords(
`[data-overlay-index="${3}"]` `[data-overlay-index="${4}"]`
) )
// select two segments by holding down shift // select two segments by holding down shift
@ -899,8 +900,8 @@ part002 = startSketchOn(XZ)
// Wait for overlays to populate // Wait for overlays to populate
await page.waitForTimeout(1000) await page.waitForTimeout(1000)
const line1 = await u.getBoundingBox(`[data-overlay-index="${0}"]`) const line1 = await u.getBoundingBox(`[data-overlay-index="${1}"]`)
const line3 = await u.getBoundingBox(`[data-overlay-index="${2}"]`) const line3 = await u.getBoundingBox(`[data-overlay-index="${3}"]`)
// select two segments by holding down shift // select two segments by holding down shift
await page.mouse.click(line1.x - 20, line1.y + 20) await page.mouse.click(line1.x - 20, line1.y + 20)
@ -981,7 +982,7 @@ part002 = startSketchOn(XZ)
// Wait for overlays to populate // Wait for overlays to populate
await page.waitForTimeout(1000) await page.waitForTimeout(1000)
const line3 = await u.getBoundingBox(`[data-overlay-index="${2}"]`) const line3 = await u.getBoundingBox(`[data-overlay-index="${3}"]`)
// select segment and axis by holding down shift // select segment and axis by holding down shift
await page.mouse.click(line3.x - 3, line3.y + 20) await page.mouse.click(line3.x - 3, line3.y + 20)
@ -1044,7 +1045,7 @@ part002 = startSketchOn(XZ)
await page.waitForTimeout(100) await page.waitForTimeout(100)
const lineBefore = await u.getSegmentBodyCoords( const lineBefore = await u.getSegmentBodyCoords(
`[data-overlay-index="1"]`, `[data-overlay-index="2"]`,
0 0
) )
expect( expect(
@ -1075,15 +1076,15 @@ part002 = startSketchOn(XZ)
// If the overlay-angle is updated the THREE.js scene is in a good state // If the overlay-angle is updated the THREE.js scene is in a good state
await expect( await expect(
await page.locator('[data-overlay-index="1"]') await page.locator('[data-overlay-index="2"]')
).toHaveAttribute('data-overlay-angle', '0') ).toHaveAttribute('data-overlay-angle', '0')
const lineAfter = await u.getSegmentBodyCoords( const lineAfter = await u.getSegmentBodyCoords(
`[data-overlay-index="1"]`, `[data-overlay-index="2"]`,
0 0
) )
const linebb = await u.getBoundingBox('[data-overlay-index="1"]') const linebb = await u.getBoundingBox('[data-overlay-index="2"]')
await page.mouse.move(linebb.x, linebb.y, { steps: 25 }) await page.mouse.move(linebb.x, linebb.y, { steps: 25 })
await page.mouse.click(linebb.x, linebb.y) await page.mouse.click(linebb.x, linebb.y)
@ -1097,6 +1098,7 @@ part002 = startSketchOn(XZ)
await page.waitForTimeout(200) await page.waitForTimeout(200)
// await page.getByRole('button', { name: 'length', exact: true }).click() // await page.getByRole('button', { name: 'length', exact: true }).click()
await page.getByTestId('constraint-length').click() await page.getByTestId('constraint-length').click()
await page.waitForTimeout(500)
await page.getByTestId('cmd-bar-arg-value').getByRole('textbox').fill('10') await page.getByTestId('cmd-bar-arg-value').getByRole('textbox').fill('10')
await page await page
@ -1112,7 +1114,7 @@ part002 = startSketchOn(XZ)
) )
// checking the count of the overlays is a good proxy check that the client sketch scene is in a good state // checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
await expect(page.getByTestId('segment-overlay')).toHaveCount(2) await expect(page.getByTestId('segment-overlay')).toHaveCount(3)
}) })
}) })
test.describe('Electron constraint tests', () => { test.describe('Electron constraint tests', () => {

View File

@ -691,7 +691,13 @@ test.describe('Testing segment overlays', () => {
locator: '[data-overlay-toolbar-index="11"]', locator: '[data-overlay-toolbar-index="11"]',
}) })
}) })
test('for segment [tangentialArc]', async ({ page, editor, homePage }) => { test('for segment [tangentialArc]', async ({
page,
editor,
homePage,
scene,
cmdBar,
}) => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
@ -719,6 +725,7 @@ test.describe('Testing segment overlays', () => {
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.settled(cmdBar)
// wait for execution done // wait for execution done
await u.openDebugPanel() await u.openDebugPanel()
@ -730,13 +737,13 @@ test.describe('Testing segment overlays', () => {
await page.getByRole('button', { name: 'Edit Sketch' }).click() await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(500) await page.waitForTimeout(500)
await expect(page.getByTestId('segment-overlay')).toHaveCount(13) await expect(page.getByTestId('segment-overlay')).toHaveCount(14)
const clickUnconstrained = _clickUnconstrained(page, editor) const clickUnconstrained = _clickUnconstrained(page, editor)
const clickConstrained = _clickConstrained(page, editor) const clickConstrained = _clickConstrained(page, editor)
const tangentialArc = await u.getBoundingBox('[data-overlay-index="12"]') const tangentialArc = await u.getBoundingBox('[data-overlay-index="13"]')
let ang = await u.getAngle('[data-overlay-index="12"]') let ang = await u.getAngle('[data-overlay-index="13"]')
console.log('tangentialArc') console.log('tangentialArc')
await clickConstrained({ await clickConstrained({
hoverPos: { x: tangentialArc.x, y: tangentialArc.y }, hoverPos: { x: tangentialArc.x, y: tangentialArc.y },
@ -747,7 +754,7 @@ test.describe('Testing segment overlays', () => {
expectFinal: 'tangentialArc(endAbsolute = [xAbs001, -3.14])', expectFinal: 'tangentialArc(endAbsolute = [xAbs001, -3.14])',
ang: ang + 180, ang: ang + 180,
steps: 6, steps: 6,
locator: '[data-overlay-toolbar-index="12"]', locator: '[data-overlay-toolbar-index="13"]',
}) })
console.log('tangentialArc2') console.log('tangentialArc2')
await clickUnconstrained({ await clickUnconstrained({
@ -760,7 +767,7 @@ test.describe('Testing segment overlays', () => {
expectFinal: 'tangentialArc(endAbsolute = [xAbs001, -3.14])', expectFinal: 'tangentialArc(endAbsolute = [xAbs001, -3.14])',
ang: ang + 180, ang: ang + 180,
steps: 10, steps: 10,
locator: '[data-overlay-toolbar-index="12"]', locator: '[data-overlay-toolbar-index="13"]',
}) })
}) })
test('for segment [arcTo]', async ({ test('for segment [arcTo]', async ({
@ -983,7 +990,7 @@ part001 = startSketchOn(XZ)
'persistCode', 'persistCode',
`@settings(defaultLengthUnit = in) `@settings(defaultLengthUnit = in)
part001 = startSketchOn(XZ) part001 = startSketchOn(XZ)
|>startProfile(at = [0, 0]) |> startProfile(at = [0, 0])
|> line(end = [0.5, -14 + 0]) |> line(end = [0.5, -14 + 0])
|> angledLine(angle = 3 + 0, length = 32 + 0) |> angledLine(angle = 3 + 0, length = 32 + 0)
|> line(endAbsolute = [33, 11.5 + 0]) |> line(endAbsolute = [33, 11.5 + 0])
@ -1017,141 +1024,167 @@ part001 = startSketchOn(XZ)
await page.getByRole('button', { name: 'Edit Sketch' }).click() await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(500) await page.waitForTimeout(500)
await expect(page.getByTestId('segment-overlay')).toHaveCount(16) await expect(page.getByTestId('segment-overlay')).toHaveCount(17)
const deleteSegmentSequence = _deleteSegmentSequence(page, editor) const deleteSegmentSequence = _deleteSegmentSequence(page, editor)
let segmentToDelete let segmentToDelete
let ang = 0
const getOverlayByIndex = (index: number) => const getOverlayByIndex = (index: number) =>
u.getBoundingBox(`[data-overlay-index="${index}"]`) u.getBoundingBox(`[data-overlay-index="${index}"]`)
segmentToDelete = await getOverlayByIndex(14) let overlayIndex = 15
let ang = await u.getAngle('[data-overlay-index="14"]')
await editor.scrollToText('angleEnd')
segmentToDelete = await getOverlayByIndex(overlayIndex)
ang = await u.getAngle(`[data-overlay-index="${overlayIndex}"]`)
await deleteSegmentSequence({ await deleteSegmentSequence({
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y }, hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
codeToBeDeleted: `arc(angleStart = 40.27, angleEnd = -38.05, radius = 9.03)`, codeToBeDeleted: `arc(angleStart = 40.27, angleEnd = -38.05, radius = 9.03)`,
stdLibFnName: 'arc', stdLibFnName: 'arc',
ang: ang + 180, ang: ang + 180,
steps: 6, steps: 6,
locator: '[data-overlay-toolbar-index="14"]', locator: `[data-overlay-toolbar-index="${overlayIndex}"]`,
}) })
segmentToDelete = await getOverlayByIndex(13)
ang = await u.getAngle('[data-overlay-index="13"]') overlayIndex--
segmentToDelete = await getOverlayByIndex(overlayIndex)
ang = await u.getAngle(`[data-overlay-index="${overlayIndex}"]`)
await deleteSegmentSequence({ await deleteSegmentSequence({
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y }, hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
codeToBeDeleted: `arc(interiorAbsolute = [16.25, 5.12], endAbsolute = [21.61, 4.15])`, codeToBeDeleted: `arc(interiorAbsolute = [16.25, 5.12], endAbsolute = [21.61, 4.15])`,
stdLibFnName: 'arc', stdLibFnName: 'arc',
ang: ang, ang: ang,
steps: 6, steps: 6,
locator: '[data-overlay-toolbar-index="13"]', locator: `[data-overlay-toolbar-index="${overlayIndex}"]`,
}) })
segmentToDelete = await getOverlayByIndex(12)
ang = await u.getAngle('[data-overlay-index="12"]') overlayIndex--
segmentToDelete = await getOverlayByIndex(overlayIndex)
ang = await u.getAngle(`[data-overlay-index="${overlayIndex}"]`)
await deleteSegmentSequence({ await deleteSegmentSequence({
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y }, hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
codeToBeDeleted: 'tangentialArc(endAbsolute = [3.14 + 13, 1.14])', codeToBeDeleted: 'tangentialArc(endAbsolute = [3.14 + 13, 1.14])',
stdLibFnName: 'tangentialArc', stdLibFnName: 'tangentialArc',
ang: ang + 180, ang: ang + 180,
steps: 6, steps: 6,
locator: '[data-overlay-toolbar-index="12"]', locator: `[data-overlay-toolbar-index="${overlayIndex}"]`,
}) })
segmentToDelete = await getOverlayByIndex(11) overlayIndex--
ang = await u.getAngle('[data-overlay-index="11"]')
segmentToDelete = await getOverlayByIndex(overlayIndex)
ang = await u.getAngle(`[data-overlay-index="${overlayIndex}"]`)
await deleteSegmentSequence({ await deleteSegmentSequence({
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y }, hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
codeToBeDeleted: `angledLineThatIntersects(angle = 4.14, intersectTag = a, offset = 9)`, codeToBeDeleted: `angledLineThatIntersects(angle = 4.14, intersectTag = a, offset = 9)`,
stdLibFnName: 'angledLineThatIntersects', stdLibFnName: 'angledLineThatIntersects',
ang: ang + 180, ang: ang + 180,
steps: 7, steps: 7,
locator: '[data-overlay-toolbar-index="11"]', locator: `[data-overlay-toolbar-index="${overlayIndex}"]`,
}) })
segmentToDelete = await getOverlayByIndex(10) overlayIndex--
ang = await u.getAngle('[data-overlay-index="10"]')
segmentToDelete = await getOverlayByIndex(overlayIndex)
ang = await u.getAngle(`[data-overlay-index="${overlayIndex}"]`)
await deleteSegmentSequence({ await deleteSegmentSequence({
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y }, hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
codeToBeDeleted: 'angledLine(angle = 89, endAbsoluteY = 9.14 + 0)', codeToBeDeleted: 'angledLine(angle = 89, endAbsoluteY = 9.14 + 0)',
stdLibFnName: 'angledLineToY', stdLibFnName: 'angledLineToY',
ang: ang + 180, ang: ang + 180,
locator: '[data-overlay-toolbar-index="10"]', locator: `[data-overlay-toolbar-index="${overlayIndex}"]`,
}) })
segmentToDelete = await getOverlayByIndex(9) overlayIndex--
ang = await u.getAngle('[data-overlay-index="9"]')
segmentToDelete = await getOverlayByIndex(overlayIndex)
ang = await u.getAngle(`[data-overlay-index="${overlayIndex}"]`)
await deleteSegmentSequence({ await deleteSegmentSequence({
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y }, hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
codeToBeDeleted: 'angledLine(angle = 3 + 0, endAbsoluteX = 26)', codeToBeDeleted: 'angledLine(angle = 3 + 0, endAbsoluteX = 26)',
stdLibFnName: 'angledLineToX', stdLibFnName: 'angledLineToX',
ang: ang + 180, ang: ang + 180,
locator: '[data-overlay-toolbar-index="9"]', locator: `[data-overlay-toolbar-index="${overlayIndex}"]`,
}) })
segmentToDelete = await getOverlayByIndex(8) overlayIndex--
ang = await u.getAngle('[data-overlay-index="8"]')
segmentToDelete = await getOverlayByIndex(overlayIndex)
ang = await u.getAngle(`[data-overlay-index="${overlayIndex}"]`)
await deleteSegmentSequence({ await deleteSegmentSequence({
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y }, hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
codeToBeDeleted: 'angledLine(angle = -91, lengthY = 19 + 0)', codeToBeDeleted: 'angledLine(angle = -91, lengthY = 19 + 0)',
stdLibFnName: 'angledLineOfYLength', stdLibFnName: 'angledLineOfYLength',
ang: ang + 180, ang: ang + 180,
locator: '[data-overlay-toolbar-index="8"]', locator: `[data-overlay-toolbar-index="${overlayIndex}"]`,
}) })
segmentToDelete = await getOverlayByIndex(7) overlayIndex--
ang = await u.getAngle('[data-overlay-index="7"]')
segmentToDelete = await getOverlayByIndex(overlayIndex)
ang = await u.getAngle(`[data-overlay-index="${overlayIndex}"]`)
await deleteSegmentSequence({ await deleteSegmentSequence({
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y }, hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
codeToBeDeleted: 'angledLine(angle = 181 + 0, lengthX = 23.14)', codeToBeDeleted: 'angledLine(angle = 181 + 0, lengthX = 23.14)',
stdLibFnName: 'angledLineOfXLength', stdLibFnName: 'angledLineOfXLength',
ang: ang + 180, ang: ang + 180,
locator: '[data-overlay-toolbar-index="7"]', locator: `[data-overlay-toolbar-index="${overlayIndex}"]`,
}) })
segmentToDelete = await getOverlayByIndex(6) overlayIndex--
ang = await u.getAngle('[data-overlay-index="6"]')
segmentToDelete = await getOverlayByIndex(overlayIndex)
ang = await u.getAngle(`[data-overlay-index="${overlayIndex}"]`)
await deleteSegmentSequence({ await deleteSegmentSequence({
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y }, hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
codeToBeDeleted: 'yLine(length = 21.14 + 0)', codeToBeDeleted: 'yLine(length = 21.14 + 0)',
stdLibFnName: 'yLine', stdLibFnName: 'yLine',
ang: ang + 180, ang: ang + 180,
locator: '[data-overlay-toolbar-index="6"]', locator: `[data-overlay-toolbar-index="${overlayIndex}"]`,
}) })
segmentToDelete = await getOverlayByIndex(5) overlayIndex--
ang = await u.getAngle('[data-overlay-index="5"]')
segmentToDelete = await getOverlayByIndex(overlayIndex)
ang = await u.getAngle(`[data-overlay-index="${overlayIndex}"]`)
await deleteSegmentSequence({ await deleteSegmentSequence({
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y }, hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
codeToBeDeleted: 'xLine(length = 26.04)', codeToBeDeleted: 'xLine(length = 26.04)',
stdLibFnName: 'xLine', stdLibFnName: 'xLine',
ang: ang + 180, ang: ang + 180,
locator: '[data-overlay-toolbar-index="5"]', locator: `[data-overlay-toolbar-index="${overlayIndex}"]`,
}) })
segmentToDelete = await getOverlayByIndex(4) overlayIndex--
ang = await u.getAngle('[data-overlay-index="4"]')
segmentToDelete = await getOverlayByIndex(overlayIndex)
ang = await u.getAngle(`[data-overlay-index="${overlayIndex}"]`)
await deleteSegmentSequence({ await deleteSegmentSequence({
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y }, hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
codeToBeDeleted: 'yLine(endAbsolute = -10.77, tag = $a)', codeToBeDeleted: 'yLine(endAbsolute = -10.77, tag = $a)',
stdLibFnName: 'yLineTo', stdLibFnName: 'yLineTo',
ang: ang + 180, ang: ang + 180,
locator: '[data-overlay-toolbar-index="4"]', locator: `[data-overlay-toolbar-index="${overlayIndex}"]`,
}) })
segmentToDelete = await getOverlayByIndex(3) overlayIndex--
ang = await u.getAngle('[data-overlay-index="3"]')
segmentToDelete = await getOverlayByIndex(overlayIndex)
ang = await u.getAngle(`[data-overlay-index="${overlayIndex}"]`)
await deleteSegmentSequence({ await deleteSegmentSequence({
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y }, hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
codeToBeDeleted: 'xLine(endAbsolute = 9 - 5)', codeToBeDeleted: 'xLine(endAbsolute = 9 - 5)',
stdLibFnName: 'xLineTo', stdLibFnName: 'xLineTo',
ang: ang + 180, ang: ang + 180,
locator: '[data-overlay-toolbar-index="3"]', locator: `[data-overlay-toolbar-index="${overlayIndex}"]`,
}) })
segmentToDelete = await getOverlayByIndex(2) overlayIndex--
ang = await u.getAngle('[data-overlay-index="2"]')
segmentToDelete = await getOverlayByIndex(overlayIndex)
ang = await u.getAngle(`[data-overlay-index="${overlayIndex}"]`)
await expect(page.getByText('Added variable')).not.toBeVisible() await expect(page.getByText('Added variable')).not.toBeVisible()
const hoverPos = { x: segmentToDelete.x, y: segmentToDelete.y } const hoverPos = { x: segmentToDelete.x, y: segmentToDelete.y }
@ -1167,7 +1200,7 @@ part001 = startSketchOn(XZ)
ang, ang,
10, 10,
5, 5,
'[data-overlay-toolbar-index="2"]' `[data-overlay-toolbar-index="${overlayIndex}"]`
) )
await page.mouse.move(hoverPos.x, hoverPos.y) await page.mouse.move(hoverPos.x, hoverPos.y)
@ -1183,18 +1216,22 @@ part001 = startSketchOn(XZ)
shouldNormalise: true, shouldNormalise: true,
}) })
segmentToDelete = await getOverlayByIndex(1) overlayIndex--
ang = await u.getAngle('[data-overlay-index="1"]')
segmentToDelete = await getOverlayByIndex(overlayIndex)
ang = await u.getAngle(`[data-overlay-index="${overlayIndex}"]`)
await deleteSegmentSequence({ await deleteSegmentSequence({
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y }, hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
codeToBeDeleted: 'angledLine(angle = 3 + 0, length = 32 + 0)', codeToBeDeleted: 'angledLine(angle = 3 + 0, length = 32 + 0)',
stdLibFnName: 'angledLine', stdLibFnName: 'angledLine',
ang: ang + 180, ang: ang + 180,
locator: '[data-overlay-toolbar-index="1"]', locator: `[data-overlay-toolbar-index="${overlayIndex}"]`,
}) })
segmentToDelete = await getOverlayByIndex(0) overlayIndex--
ang = await u.getAngle('[data-overlay-index="0"]')
segmentToDelete = await getOverlayByIndex(overlayIndex)
ang = await u.getAngle(`[data-overlay-index="${overlayIndex}"]`)
await deleteSegmentSequence({ await deleteSegmentSequence({
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y }, hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
codeToBeDeleted: 'line(end = [0.5, -14 + 0])', codeToBeDeleted: 'line(end = [0.5, -14 + 0])',
@ -1414,11 +1451,11 @@ part001 = startSketchOn(XZ)
await page.getByRole('button', { name: 'Edit Sketch' }).click() await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(500) await page.waitForTimeout(500)
await expect(page.getByTestId('segment-overlay')).toHaveCount(3) await expect(page.getByTestId('segment-overlay')).toHaveCount(4)
await expect(page.getByText('Added variable')).not.toBeVisible() await expect(page.getByText('Added variable')).not.toBeVisible()
const hoverPos = await u.getBoundingBox(`[data-overlay-index="0"]`) const hoverPos = await u.getBoundingBox(`[data-overlay-index="1"]`)
let ang = await u.getAngle('[data-overlay-index="0"]') let ang = await u.getAngle('[data-overlay-index="1"]')
ang += 180 ang += 180
await page.mouse.move(0, 0) await page.mouse.move(0, 0)
@ -1437,7 +1474,7 @@ part001 = startSketchOn(XZ)
ang, ang,
10, 10,
5, 5,
'[data-overlay-toolbar-index="0"]' '[data-overlay-toolbar-index="1"]'
) )
await page.mouse.move(x, y) await page.mouse.move(x, y)
@ -1445,13 +1482,13 @@ part001 = startSketchOn(XZ)
await page.getByTestId('overlay-menu').click() await page.getByTestId('overlay-menu').click()
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.getByText('Remove constraints').click() await page.getByRole('button', { name: 'Remove constraints' }).click()
await editor.expectEditor.toContain(after, { shouldNormalise: true }) await editor.expectEditor.toContain(after, { shouldNormalise: true })
// check the cursor was left in the correct place after transform // check the cursor was left in the correct place after transform
await expect(page.locator('.cm-activeLine')).toHaveText('|> ' + after) await expect(page.locator('.cm-activeLine')).toHaveText('|> ' + after)
await expect(page.getByTestId('segment-overlay')).toHaveCount(3) await expect(page.getByTestId('segment-overlay')).toHaveCount(4)
}) })
} }
}) })
@ -1587,6 +1624,87 @@ profile002 = circle(sketch001, center = [345, 0], radius = 238.38)
) )
}) })
}) })
test('startProfile x y overlays', async ({
page,
editor,
homePage,
scene,
cmdBar,
toolbar,
}) => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`sketch001 = startSketchOn(XZ)
profile001 = startProfile(sketch001, at = [15, 15])
|> line(end = [114.78, 232])
|> line(end = [228.75, -208.39])
`
)
// Set flag to always show overlays without hover
localStorage.setItem('showAllOverlays', 'true')
})
await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene()
await scene.connectionEstablished()
await scene.settled(cmdBar)
await toolbar.waitForFeatureTreeToBeBuilt()
await toolbar.editSketch(0)
await page.waitForTimeout(600)
await expect(page.getByTestId('segment-overlay')).toHaveCount(3)
// 1. constrain x coordinate
const xConstraintBtn = page.locator(
'[data-constraint-type="xAbsolute"][data-is-constrained="false"]'
)
await expect(xConstraintBtn).toBeVisible()
await xConstraintBtn.click()
await expect(
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused()
await cmdBar.progressCmdBar()
await editor.expectEditor.toContain('at = [xAbs001, 15]', {
shouldNormalise: true,
})
// 2. constrain y coordinate
const yConstraintBtn = page.locator(
'[data-constraint-type="yAbsolute"][data-is-constrained="false"]'
)
await expect(yConstraintBtn).toBeVisible()
await yConstraintBtn.click()
await expect(
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused()
await cmdBar.progressCmdBar()
await editor.expectEditor.toContain('at = [xAbs001, yAbs001]', {
shouldNormalise: true,
})
// 3. unconstrain x coordinate
const constrainedXBtn = page.locator(
'[data-constraint-type="xAbsolute"][data-is-constrained="true"]'
)
await expect(constrainedXBtn).toBeVisible()
await constrainedXBtn.click()
await editor.expectEditor.toContain('at = [15, yAbs001]', {
shouldNormalise: true,
})
// 4. unconstrain y coordinate
const constrainedYBtn = page.locator(
'[data-constraint-type="yAbsolute"][data-is-constrained="true"]'
)
await expect(constrainedYBtn).toBeVisible()
await constrainedYBtn.click()
await editor.expectEditor.toContain('at = [15, 15]', {
shouldNormalise: true,
})
})
test('arc with interiorAbsolute and endAbsolute kwargs overlay constraints', async ({ test('arc with interiorAbsolute and endAbsolute kwargs overlay constraints', async ({
page, page,
editor, editor,
@ -1605,7 +1723,7 @@ profile001 = circleThreePoint(
p2 = [445.16, 116.92], p2 = [445.16, 116.92],
p3 = [546.85, 103], p3 = [546.85, 103],
) )
profile003 = startProfileAt([64.39, 35.16], sketch001) profile003 = startProfile(sketch001, at = [64.39, 35.16])
|> line(end = [60.69, 23.02]) |> line(end = [60.69, 23.02])
|> arc(interiorAbsolute = [159.26, 100.58], endAbsolute = [237.05, 84.07]) |> arc(interiorAbsolute = [159.26, 100.58], endAbsolute = [237.05, 84.07])
|> line(end = [70.31, 42.28])` |> line(end = [70.31, 42.28])`
@ -1628,7 +1746,7 @@ profile003 = startProfileAt([64.39, 35.16], sketch001)
// Verify overlays are visible // Verify overlays are visible
// 3 for the three point arc, and 4 for the 3 segments (arc has two) // 3 for the three point arc, and 4 for the 3 segments (arc has two)
await expect(page.getByTestId('segment-overlay')).toHaveCount(7) await expect(page.getByTestId('segment-overlay')).toHaveCount(8)
// ---- Testing interior point constraints ---- // ---- Testing interior point constraints ----
@ -1637,7 +1755,7 @@ profile003 = startProfileAt([64.39, 35.16], sketch001)
.locator( .locator(
'[data-constraint-type="xAbsolute"][data-is-constrained="false"]' '[data-constraint-type="xAbsolute"][data-is-constrained="false"]'
) )
.nth(3) .nth(4)
await expect(interiorXConstraintBtn).toBeVisible() await expect(interiorXConstraintBtn).toBeVisible()
await interiorXConstraintBtn.click() await interiorXConstraintBtn.click()
@ -1660,7 +1778,7 @@ profile003 = startProfileAt([64.39, 35.16], sketch001)
.locator( .locator(
'[data-constraint-type="yAbsolute"][data-is-constrained="false"]' '[data-constraint-type="yAbsolute"][data-is-constrained="false"]'
) )
.nth(3) .nth(4)
await expect(interiorYConstraintBtn).toBeVisible() await expect(interiorYConstraintBtn).toBeVisible()
await interiorYConstraintBtn.click() await interiorYConstraintBtn.click()
@ -1685,7 +1803,7 @@ profile003 = startProfileAt([64.39, 35.16], sketch001)
.locator( .locator(
'[data-constraint-type="xAbsolute"][data-is-constrained="false"]' '[data-constraint-type="xAbsolute"][data-is-constrained="false"]'
) )
.nth(3) // still number 3 because the interior ones are now constrained .nth(4) // still number 3 because the interior ones are now constrained
await expect(endXConstraintBtn).toBeVisible() await expect(endXConstraintBtn).toBeVisible()
await endXConstraintBtn.click() await endXConstraintBtn.click()
@ -1705,7 +1823,7 @@ profile003 = startProfileAt([64.39, 35.16], sketch001)
.locator( .locator(
'[data-constraint-type="yAbsolute"][data-is-constrained="false"]' '[data-constraint-type="yAbsolute"][data-is-constrained="false"]'
) )
.nth(3) // still number 3 because the interior ones are now constrained .nth(4) // still number 3 because the interior ones are now constrained
await expect(endYConstraintBtn).toBeVisible() await expect(endYConstraintBtn).toBeVisible()
await endYConstraintBtn.click() await endYConstraintBtn.click()

View File

@ -797,7 +797,7 @@ test.describe(
// We use the line tool as a proxy for sketch mode // We use the line tool as a proxy for sketch mode
await expect(lineToolButton).toBeVisible() await expect(lineToolButton).toBeVisible()
await expect(segmentOverlays).toHaveCount(4) await expect(segmentOverlays).toHaveCount(5)
// but we allow more time to pass for animating to the sketch // but we allow more time to pass for animating to the sketch
await page.waitForTimeout(1000) await page.waitForTimeout(1000)
}) })

Some files were not shown because too many files have changed in this diff Show More