Merge remote-tracking branch 'origin/main' into kurt-scale-sketch
@ -10,9 +10,11 @@ Extend the current sketch with a new involute circular curve.
|
|||||||
```kcl
|
```kcl
|
||||||
involuteCircular(
|
involuteCircular(
|
||||||
@sketch: Sketch,
|
@sketch: Sketch,
|
||||||
startRadius: number(Length),
|
|
||||||
endRadius: number(Length),
|
|
||||||
angle: number(Angle),
|
angle: number(Angle),
|
||||||
|
startRadius?: number(Length),
|
||||||
|
endRadius?: number(Length),
|
||||||
|
startDiameter?: number(Length),
|
||||||
|
endDiameter?: number(Length),
|
||||||
reverse?: bool,
|
reverse?: bool,
|
||||||
tag?: TagDecl,
|
tag?: TagDecl,
|
||||||
): Sketch
|
): Sketch
|
||||||
@ -25,9 +27,11 @@ involuteCircular(
|
|||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `sketch` | [`Sketch`](/docs/kcl-std/types/std-types-Sketch) | Which sketch should this path be added to? | Yes |
|
| `sketch` | [`Sketch`](/docs/kcl-std/types/std-types-Sketch) | Which sketch should this path be added to? | Yes |
|
||||||
| `startRadius` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The involute is described between two circles, start_radius is the radius of the inner circle. | Yes |
|
|
||||||
| `endRadius` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The involute is described between two circles, end_radius is the radius of the outer circle. | Yes |
|
|
||||||
| `angle` | [`number(Angle)`](/docs/kcl-std/types/std-types-number) | The angle to rotate the involute by. A value of zero will produce a curve with a tangent along the x-axis at the start point of the curve. | Yes |
|
| `angle` | [`number(Angle)`](/docs/kcl-std/types/std-types-number) | The angle to rotate the involute by. A value of zero will produce a curve with a tangent along the x-axis at the start point of the curve. | Yes |
|
||||||
|
| `startRadius` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The involute is described between two circles, startRadius is the radius of the inner circle. Either `startRadius` or `startDiameter` must be given (but not both). | No |
|
||||||
|
| `endRadius` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The involute is described between two circles, endRadius is the radius of the outer circle. Either `endRadius` or `endDiameter` must be given (but not both). | No |
|
||||||
|
| `startDiameter` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The involute is described between two circles, startDiameter describes the inner circle. Either `startRadius` or `startDiameter` must be given (but not both). | No |
|
||||||
|
| `endDiameter` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The involute is described between two circles, endDiameter describes the outer circle. Either `endRadius` or `endDiameter` must be given (but not both). | No |
|
||||||
| `reverse` | [`bool`](/docs/kcl-std/types/std-types-bool) | If reverse is true, the segment will start from the end of the involute, otherwise it will start from that start. | No |
|
| `reverse` | [`bool`](/docs/kcl-std/types/std-types-bool) | If reverse is true, the segment will start from the end of the involute, otherwise it will start from that start. | No |
|
||||||
| `tag` | [`TagDecl`](/docs/kcl-std/types/std-types-TagDecl) | Create a new tag which refers to this line. | No |
|
| `tag` | [`TagDecl`](/docs/kcl-std/types/std-types-TagDecl) | Create a new tag which refers to this line. | No |
|
||||||
|
|
||||||
|
|||||||
@ -29,7 +29,7 @@ The sketches need to be closed and on different planes that are parallel.
|
|||||||
| `vDegree` | [`number(_)`](/docs/kcl-std/types/std-types-number) | Degree of the interpolation. Must be greater than zero. For example, use 2 for quadratic, or 3 for cubic interpolation in the V direction. | No |
|
| `vDegree` | [`number(_)`](/docs/kcl-std/types/std-types-number) | Degree of the interpolation. Must be greater than zero. For example, use 2 for quadratic, or 3 for cubic interpolation in the V direction. | No |
|
||||||
| `bezApproximateRational` | [`bool`](/docs/kcl-std/types/std-types-bool) | Attempt to approximate rational curves (such as arcs) using a bezier. This will remove banding around interpolations between arcs and non-arcs. It may produce errors in other scenarios. Over time, this field won't be necessary. | No |
|
| `bezApproximateRational` | [`bool`](/docs/kcl-std/types/std-types-bool) | Attempt to approximate rational curves (such as arcs) using a bezier. This will remove banding around interpolations between arcs and non-arcs. It may produce errors in other scenarios. Over time, this field won't be necessary. | No |
|
||||||
| `baseCurveIndex` | [`number(_)`](/docs/kcl-std/types/std-types-number) | This can be set to override the automatically determined topological base curve, which is usually the first section encountered. | No |
|
| `baseCurveIndex` | [`number(_)`](/docs/kcl-std/types/std-types-number) | This can be set to override the automatically determined topological base curve, which is usually the first section encountered. | No |
|
||||||
| `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | Tolerance for the loft operation. | No |
|
| `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters. | No |
|
||||||
| `tagStart` | [`TagDecl`](/docs/kcl-std/types/std-types-TagDecl) | A named tag for the face at the start of the loft, i.e. the original sketch. | No |
|
| `tagStart` | [`TagDecl`](/docs/kcl-std/types/std-types-TagDecl) | A named tag for the face at the start of the loft, i.e. the original sketch. | No |
|
||||||
| `tagEnd` | [`TagDecl`](/docs/kcl-std/types/std-types-TagDecl) | A named tag for the face at the end of the loft. | No |
|
| `tagEnd` | [`TagDecl`](/docs/kcl-std/types/std-types-TagDecl) | A named tag for the face at the end of the loft. | No |
|
||||||
|
|
||||||
|
|||||||
@ -38,7 +38,7 @@ revolved around the same axis.
|
|||||||
| `sketches` | [`[Sketch; 1+]`](/docs/kcl-std/types/std-types-Sketch) | The sketch or set of sketches that should be revolved | Yes |
|
| `sketches` | [`[Sketch; 1+]`](/docs/kcl-std/types/std-types-Sketch) | The sketch or set of sketches that should be revolved | Yes |
|
||||||
| `axis` | [`Axis2d`](/docs/kcl-std/types/std-types-Axis2d) or [`Edge`](/docs/kcl-std/types/std-types-Edge) | Axis of revolution. | Yes |
|
| `axis` | [`Axis2d`](/docs/kcl-std/types/std-types-Axis2d) or [`Edge`](/docs/kcl-std/types/std-types-Edge) | Axis of revolution. | Yes |
|
||||||
| `angle` | [`number(Angle)`](/docs/kcl-std/types/std-types-number) | Angle to revolve (in degrees). Default is 360. | No |
|
| `angle` | [`number(Angle)`](/docs/kcl-std/types/std-types-number) | Angle to revolve (in degrees). Default is 360. | No |
|
||||||
| `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | Tolerance for the revolve operation. | No |
|
| `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters. | No |
|
||||||
| `symmetric` | [`bool`](/docs/kcl-std/types/std-types-bool) | If true, the extrusion will happen symmetrically around the sketch. Otherwise, the extrusion will happen on only one side of the sketch. | No |
|
| `symmetric` | [`bool`](/docs/kcl-std/types/std-types-bool) | If true, the extrusion will happen symmetrically around the sketch. Otherwise, the extrusion will happen on only one side of the sketch. | No |
|
||||||
| `bidirectionalAngle` | [`number(Angle)`](/docs/kcl-std/types/std-types-number) | If specified, will also revolve in the opposite direction to 'angle' to the specified angle. If 'symmetric' is true, this value is ignored. | No |
|
| `bidirectionalAngle` | [`number(Angle)`](/docs/kcl-std/types/std-types-number) | If specified, will also revolve in the opposite direction to 'angle' to the specified angle. If 'symmetric' is true, this value is ignored. | No |
|
||||||
| `tagStart` | [`TagDecl`](/docs/kcl-std/types/std-types-TagDecl) | A named tag for the face at the start of the revolve, i.e. the original sketch. | No |
|
| `tagStart` | [`TagDecl`](/docs/kcl-std/types/std-types-TagDecl) | A named tag for the face at the start of the revolve, i.e. the original sketch. | No |
|
||||||
|
|||||||
@ -35,7 +35,7 @@ swept along the same path.
|
|||||||
| `sketches` | [`[Sketch; 1+]`](/docs/kcl-std/types/std-types-Sketch) | The sketch or set of sketches that should be swept in space. | Yes |
|
| `sketches` | [`[Sketch; 1+]`](/docs/kcl-std/types/std-types-Sketch) | The sketch or set of sketches that should be swept in space. | Yes |
|
||||||
| `path` | [`Sketch`](/docs/kcl-std/types/std-types-Sketch) or [`Helix`](/docs/kcl-std/types/std-types-Helix) | The path to sweep the sketch along. | Yes |
|
| `path` | [`Sketch`](/docs/kcl-std/types/std-types-Sketch) or [`Helix`](/docs/kcl-std/types/std-types-Helix) | The path to sweep the sketch along. | Yes |
|
||||||
| `sectional` | [`bool`](/docs/kcl-std/types/std-types-bool) | If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components. | No |
|
| `sectional` | [`bool`](/docs/kcl-std/types/std-types-bool) | If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components. | No |
|
||||||
| `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | Tolerance for this operation. | No |
|
| `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters. | No |
|
||||||
| `relativeTo` | [`string`](/docs/kcl-std/types/std-types-string) | What is the sweep relative to? Can be either 'sketchPlane' or 'trajectoryCurve'. | No |
|
| `relativeTo` | [`string`](/docs/kcl-std/types/std-types-string) | What is the sweep relative to? Can be either 'sketchPlane' or 'trajectoryCurve'. | No |
|
||||||
| `tagStart` | [`TagDecl`](/docs/kcl-std/types/std-types-TagDecl) | A named tag for the face at the start of the sweep, i.e. the original sketch. | No |
|
| `tagStart` | [`TagDecl`](/docs/kcl-std/types/std-types-TagDecl) | A named tag for the face at the start of the sweep, i.e. the original sketch. | No |
|
||||||
| `tagEnd` | [`TagDecl`](/docs/kcl-std/types/std-types-TagDecl) | A named tag for the face at the end of the sweep. | No |
|
| `tagEnd` | [`TagDecl`](/docs/kcl-std/types/std-types-TagDecl) | A named tag for the face at the end of the sweep. | No |
|
||||||
|
|||||||
@ -28,7 +28,7 @@ will smoothly blend the transition.
|
|||||||
| `solid` | [`Solid`](/docs/kcl-std/types/std-types-Solid) | The solid whose edges should be filletted | Yes |
|
| `solid` | [`Solid`](/docs/kcl-std/types/std-types-Solid) | The solid whose edges should be filletted | Yes |
|
||||||
| `radius` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The radius of the fillet | Yes |
|
| `radius` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The radius of the fillet | Yes |
|
||||||
| `tags` | [`[Edge; 1+]`](/docs/kcl-std/types/std-types-Edge) | The paths you want to fillet | Yes |
|
| `tags` | [`[Edge; 1+]`](/docs/kcl-std/types/std-types-Edge) | The paths you want to fillet | Yes |
|
||||||
| `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The tolerance for this fillet | No |
|
| `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters. | No |
|
||||||
| `tag` | [`TagDecl`](/docs/kcl-std/types/std-types-TagDecl) | Create a new tag which refers to this fillet | No |
|
| `tag` | [`TagDecl`](/docs/kcl-std/types/std-types-TagDecl) | Create a new tag which refers to this fillet | No |
|
||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
|
|||||||
@ -24,7 +24,7 @@ verifying fit, and analyzing overlapping geometries in assemblies.
|
|||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `solids` | `[Solid; 2+]` | The solids to intersect. | Yes |
|
| `solids` | `[Solid; 2+]` | The solids to intersect. | Yes |
|
||||||
| `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The tolerance to use for the intersection operation. | No |
|
| `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters. | No |
|
||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
|
|
||||||
|
|||||||
@ -27,7 +27,7 @@ and complex multi-body part modeling.
|
|||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `solids` | [`[Solid; 1+]`](/docs/kcl-std/types/std-types-Solid) | The solids to use as the base to subtract from. | Yes |
|
| `solids` | [`[Solid; 1+]`](/docs/kcl-std/types/std-types-Solid) | The solids to use as the base to subtract from. | Yes |
|
||||||
| `tools` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) | The solids to subtract. | Yes |
|
| `tools` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) | The solids to subtract. | Yes |
|
||||||
| `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The tolerance to use for the subtraction operation. | No |
|
| `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters. | No |
|
||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
|
|
||||||
|
|||||||
@ -21,7 +21,7 @@ union(
|
|||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `solids` | `[Solid; 2+]` | The solids to union. | Yes |
|
| `solids` | `[Solid; 2+]` | The solids to union. | Yes |
|
||||||
| `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The tolerance to use for the union operation. | No |
|
| `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters. | No |
|
||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
|
|
||||||
|
|||||||
@ -307,7 +307,7 @@ test.describe('Command bar tests', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const continueButton = page.getByRole('button', { name: 'Continue' })
|
const continueButton = page.getByRole('button', { name: 'Continue' })
|
||||||
const submitButton = page.getByRole('button', { name: 'Submit command' })
|
const submitButton = page.getByTestId('command-bar-submit')
|
||||||
await continueButton.click()
|
await continueButton.click()
|
||||||
|
|
||||||
// Review step and argument hotkeys
|
// Review step and argument hotkeys
|
||||||
|
|||||||
@ -54,9 +54,7 @@ test(
|
|||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
// Click the checkbox
|
// Click the checkbox
|
||||||
const submitButton = page.getByText('Confirm Export')
|
await cmdBar.submit()
|
||||||
await expect(submitButton).toBeVisible()
|
|
||||||
await page.keyboard.press('Enter')
|
|
||||||
|
|
||||||
// Expect it to succeed
|
// Expect it to succeed
|
||||||
const errorToastMessage = page.getByText(`Error while exporting`)
|
const errorToastMessage = page.getByText(`Error while exporting`)
|
||||||
@ -119,9 +117,7 @@ test(
|
|||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
// Click the checkbox
|
// Click the checkbox
|
||||||
const submitButton = page.getByText('Confirm Export')
|
await cmdBar.submit()
|
||||||
await expect(submitButton).toBeVisible()
|
|
||||||
await page.keyboard.press('Enter')
|
|
||||||
|
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
const exportingToastMessage = page.getByText(`Exporting...`)
|
const exportingToastMessage = page.getByText(`Exporting...`)
|
||||||
|
|||||||
@ -229,11 +229,12 @@ test.describe('Feature Tree pane', () => {
|
|||||||
const initialCode = `sketch001 = startSketchOn(XZ)
|
const initialCode = `sketch001 = startSketchOn(XZ)
|
||||||
|> circle(center = [0, 0], radius = 5)
|
|> circle(center = [0, 0], radius = 5)
|
||||||
renamedExtrude = extrude(sketch001, length = ${initialInput})`
|
renamedExtrude = extrude(sketch001, length = ${initialInput})`
|
||||||
const newConstantName = 'length001'
|
const newParameterName = 'length001'
|
||||||
const expectedCode = `${newConstantName} = 23
|
const expectedCode = `${newParameterName} = 23
|
||||||
sketch001 = startSketchOn(XZ)
|
sketch001 = startSketchOn(XZ)
|
||||||
|> circle(center = [0, 0], radius = 5)
|
|> circle(center = [0, 0], radius = 5)
|
||||||
renamedExtrude = extrude(sketch001, length = ${newConstantName})`
|
renamedExtrude = extrude(sketch001, length = ${newParameterName})`
|
||||||
|
const editedParameterValue = '23 * 2'
|
||||||
|
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const testDir = join(dir, 'test-sample')
|
const testDir = join(dir, 'test-sample')
|
||||||
@ -279,7 +280,7 @@ test.describe('Feature Tree pane', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Add a named constant for distance argument and submit', async () => {
|
await test.step('Add a parameter for distance argument and submit', async () => {
|
||||||
await expect(cmdBar.currentArgumentInput).toBeVisible()
|
await expect(cmdBar.currentArgumentInput).toBeVisible()
|
||||||
await cmdBar.variableCheckbox.click()
|
await cmdBar.variableCheckbox.click()
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
@ -296,13 +297,43 @@ test.describe('Feature Tree pane', () => {
|
|||||||
highlightedCode: '',
|
highlightedCode: '',
|
||||||
diagnostics: [],
|
diagnostics: [],
|
||||||
activeLines: [
|
activeLines: [
|
||||||
`renamedExtrude = extrude(sketch001, length = ${newConstantName})`,
|
`renamedExtrude = extrude(sketch001, length = ${newParameterName})`,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
await editor.expectEditor.toContain(expectedCode, {
|
await editor.expectEditor.toContain(expectedCode, {
|
||||||
shouldNormalise: true,
|
shouldNormalise: true,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await test.step('Edit the parameter via the feature tree', async () => {
|
||||||
|
const parameter = await toolbar.getFeatureTreeOperation('Parameter', 0)
|
||||||
|
await parameter.dblclick()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
commandName: 'Edit parameter',
|
||||||
|
currentArgKey: 'value',
|
||||||
|
currentArgValue: '23',
|
||||||
|
headerArguments: {
|
||||||
|
Name: newParameterName,
|
||||||
|
Value: '23',
|
||||||
|
},
|
||||||
|
stage: 'arguments',
|
||||||
|
highlightedHeaderArg: 'value',
|
||||||
|
})
|
||||||
|
await cmdBar.argumentInput
|
||||||
|
.locator('[contenteditable]')
|
||||||
|
.fill(editedParameterValue)
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'review',
|
||||||
|
commandName: 'Edit parameter',
|
||||||
|
headerArguments: {
|
||||||
|
Name: newParameterName,
|
||||||
|
Value: '46', // Shows calculated result
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await editor.expectEditor.toContain(editedParameterValue)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
test(`User can edit an offset plane operation from the feature tree`, async ({
|
test(`User can edit an offset plane operation from the feature tree`, async ({
|
||||||
context,
|
context,
|
||||||
|
|||||||
@ -118,15 +118,11 @@ export class CmdBarFixture {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const arrowButton = this.page.getByRole('button', {
|
const arrowButton = this.page.getByTestId('command-bar-continue')
|
||||||
name: 'arrow right Continue',
|
|
||||||
})
|
|
||||||
if (await arrowButton.isVisible()) {
|
if (await arrowButton.isVisible()) {
|
||||||
await arrowButton.click()
|
await this.continue()
|
||||||
} else {
|
} else {
|
||||||
await this.page
|
await this.submit()
|
||||||
.getByRole('button', { name: 'checkmark Submit command' })
|
|
||||||
.click()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -183,14 +183,15 @@ export class EditorFixture {
|
|||||||
scrollToText(text: string, placeCursor?: boolean) {
|
scrollToText(text: string, placeCursor?: boolean) {
|
||||||
return this.page.evaluate(
|
return this.page.evaluate(
|
||||||
(args: { text: string; placeCursor?: boolean }) => {
|
(args: { text: string; placeCursor?: boolean }) => {
|
||||||
|
const editorView = window.editorManager.getEditorView()
|
||||||
// error TS2339: Property 'docView' does not exist on type 'EditorView'.
|
// error TS2339: Property 'docView' does not exist on type 'EditorView'.
|
||||||
// Except it does so :shrug:
|
// Except it does so :shrug:
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
let index = window.editorManager._editorView?.docView.view.state.doc
|
const index = editorView?.docView.view.state.doc
|
||||||
.toString()
|
.toString()
|
||||||
.indexOf(args.text)
|
.indexOf(args.text)
|
||||||
window.editorManager._editorView?.focus()
|
editorView?.focus()
|
||||||
window.editorManager._editorView?.dispatch({
|
editorView?.dispatch({
|
||||||
selection: window.EditorSelection.create([
|
selection: window.EditorSelection.create([
|
||||||
window.EditorSelection.cursor(index),
|
window.EditorSelection.cursor(index),
|
||||||
]),
|
]),
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import type {
|
|||||||
FullResult,
|
FullResult,
|
||||||
} from '@playwright/test/reporter'
|
} from '@playwright/test/reporter'
|
||||||
|
|
||||||
class MyAPIReporter implements Reporter {
|
class APIReporter implements Reporter {
|
||||||
private pendingRequests: Promise<void>[] = []
|
private pendingRequests: Promise<void>[] = []
|
||||||
private allResults: Record<string, any>[] = []
|
private allResults: Record<string, any>[] = []
|
||||||
private blockingResults: Record<string, any>[] = []
|
private blockingResults: Record<string, any>[] = []
|
||||||
@ -32,7 +32,7 @@ class MyAPIReporter implements Reporter {
|
|||||||
'X-API-Key': process.env.TAB_API_KEY || '',
|
'X-API-Key': process.env.TAB_API_KEY || '',
|
||||||
}),
|
}),
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
project: 'https://github.com/KittyCAD/modeling-app',
|
project: `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}`,
|
||||||
branch:
|
branch:
|
||||||
process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || '',
|
process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || '',
|
||||||
commit: process.env.CI_COMMIT_SHA || process.env.GITHUB_SHA || '',
|
commit: process.env.CI_COMMIT_SHA || process.env.GITHUB_SHA || '',
|
||||||
@ -60,7 +60,7 @@ class MyAPIReporter implements Reporter {
|
|||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
// Required information
|
// Required information
|
||||||
project: 'https://github.com/KittyCAD/modeling-app',
|
project: `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}`,
|
||||||
suite: process.env.CI_SUITE || 'e2e',
|
suite: process.env.CI_SUITE || 'e2e',
|
||||||
branch: process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || '',
|
branch: process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || '',
|
||||||
commit: process.env.CI_COMMIT_SHA || process.env.GITHUB_SHA || '',
|
commit: process.env.CI_COMMIT_SHA || process.env.GITHUB_SHA || '',
|
||||||
@ -124,4 +124,4 @@ class MyAPIReporter implements Reporter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MyAPIReporter
|
export default APIReporter
|
||||||
|
|||||||
@ -1083,14 +1083,13 @@ openSketch = startSketchOn(XY)
|
|||||||
cmdBar,
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
// One dumb hardcoded screen pixel value
|
// One dumb hardcoded screen pixel value
|
||||||
const testPoint = { x: 700, y: 150 }
|
const testPoint = { x: 700, y: 200 }
|
||||||
|
// TODO: replace the testPoint selection with a feature tree click once that's supported #7544
|
||||||
const [clickOnXzPlane] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
const [clickOnXzPlane] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||||
const expectedOutput = `plane001 = offsetPlane(XZ, offset = 5)`
|
const expectedOutput = `plane001 = offsetPlane(XZ, offset = 5)`
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
// FIXME: Since there is no KCL code loaded. We need to wait for the scene to load before we continue.
|
await scene.settled(cmdBar)
|
||||||
// The engine may not be connected
|
|
||||||
await page.waitForTimeout(15000)
|
|
||||||
|
|
||||||
await test.step(`Look for the blue of the XZ plane`, async () => {
|
await test.step(`Look for the blue of the XZ plane`, async () => {
|
||||||
//await scene.expectPixelColor([50, 51, 96], testPoint, 15) // FIXME
|
//await scene.expectPixelColor([50, 51, 96], testPoint, 15) // FIXME
|
||||||
@ -1829,7 +1828,6 @@ profile002 = startProfile(sketch002, at = [0, 0])
|
|||||||
currentArgKey: 'sketches',
|
currentArgKey: 'sketches',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sectional: '',
|
|
||||||
Profiles: '',
|
Profiles: '',
|
||||||
Path: '',
|
Path: '',
|
||||||
},
|
},
|
||||||
@ -1843,7 +1841,6 @@ profile002 = startProfile(sketch002, at = [0, 0])
|
|||||||
currentArgKey: 'path',
|
currentArgKey: 'path',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sectional: '',
|
|
||||||
Profiles: '1 profile',
|
Profiles: '1 profile',
|
||||||
Path: '',
|
Path: '',
|
||||||
},
|
},
|
||||||
@ -1856,7 +1853,6 @@ profile002 = startProfile(sketch002, at = [0, 0])
|
|||||||
currentArgKey: 'path',
|
currentArgKey: 'path',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sectional: '',
|
|
||||||
Profiles: '1 profile',
|
Profiles: '1 profile',
|
||||||
Path: '',
|
Path: '',
|
||||||
},
|
},
|
||||||
@ -1869,7 +1865,6 @@ profile002 = startProfile(sketch002, at = [0, 0])
|
|||||||
headerArguments: {
|
headerArguments: {
|
||||||
Profiles: '1 profile',
|
Profiles: '1 profile',
|
||||||
Path: '1 segment',
|
Path: '1 segment',
|
||||||
Sectional: '',
|
|
||||||
},
|
},
|
||||||
stage: 'review',
|
stage: 'review',
|
||||||
})
|
})
|
||||||
@ -1894,6 +1889,9 @@ profile002 = startProfile(sketch002, at = [0, 0])
|
|||||||
0
|
0
|
||||||
)
|
)
|
||||||
await operationButton.dblclick({ button: 'left' })
|
await operationButton.dblclick({ button: 'left' })
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: 'sectional', exact: false })
|
||||||
|
.click()
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
commandName: 'Sweep',
|
commandName: 'Sweep',
|
||||||
currentArgKey: 'sectional',
|
currentArgKey: 'sectional',
|
||||||
@ -1971,7 +1969,6 @@ profile001 = ${circleCode}`
|
|||||||
currentArgKey: 'sketches',
|
currentArgKey: 'sketches',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sectional: '',
|
|
||||||
Profiles: '',
|
Profiles: '',
|
||||||
Path: '',
|
Path: '',
|
||||||
},
|
},
|
||||||
@ -1986,7 +1983,6 @@ profile001 = ${circleCode}`
|
|||||||
currentArgKey: 'path',
|
currentArgKey: 'path',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sectional: '',
|
|
||||||
Profiles: '1 profile',
|
Profiles: '1 profile',
|
||||||
Path: '',
|
Path: '',
|
||||||
},
|
},
|
||||||
@ -2000,7 +1996,6 @@ profile001 = ${circleCode}`
|
|||||||
currentArgKey: 'path',
|
currentArgKey: 'path',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sectional: '',
|
|
||||||
Profiles: '1 profile',
|
Profiles: '1 profile',
|
||||||
Path: '',
|
Path: '',
|
||||||
},
|
},
|
||||||
@ -2013,7 +2008,6 @@ profile001 = ${circleCode}`
|
|||||||
headerArguments: {
|
headerArguments: {
|
||||||
Profiles: '1 profile',
|
Profiles: '1 profile',
|
||||||
Path: '1 helix',
|
Path: '1 helix',
|
||||||
Sectional: '',
|
|
||||||
},
|
},
|
||||||
stage: 'review',
|
stage: 'review',
|
||||||
})
|
})
|
||||||
@ -4734,7 +4728,6 @@ path001 = startProfile(sketch001, at = [0, 0])
|
|||||||
headerArguments: {
|
headerArguments: {
|
||||||
Profiles: '',
|
Profiles: '',
|
||||||
Path: '',
|
Path: '',
|
||||||
Sectional: '',
|
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'Profiles',
|
highlightedHeaderArg: 'Profiles',
|
||||||
commandName: 'Sweep',
|
commandName: 'Sweep',
|
||||||
@ -4747,7 +4740,6 @@ path001 = startProfile(sketch001, at = [0, 0])
|
|||||||
headerArguments: {
|
headerArguments: {
|
||||||
Profiles: '2 profiles',
|
Profiles: '2 profiles',
|
||||||
Path: '',
|
Path: '',
|
||||||
Sectional: '',
|
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'path',
|
highlightedHeaderArg: 'path',
|
||||||
commandName: 'Sweep',
|
commandName: 'Sweep',
|
||||||
@ -4760,7 +4752,6 @@ path001 = startProfile(sketch001, at = [0, 0])
|
|||||||
headerArguments: {
|
headerArguments: {
|
||||||
Profiles: '2 profiles',
|
Profiles: '2 profiles',
|
||||||
Path: '1 segment',
|
Path: '1 segment',
|
||||||
Sectional: '',
|
|
||||||
},
|
},
|
||||||
commandName: 'Sweep',
|
commandName: 'Sweep',
|
||||||
})
|
})
|
||||||
|
|||||||
@ -475,6 +475,7 @@ test.describe('Can export from electron app', () => {
|
|||||||
},
|
},
|
||||||
tronApp.projectDirName,
|
tronApp.projectDirName,
|
||||||
page,
|
page,
|
||||||
|
cmdBar,
|
||||||
method
|
method
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -779,9 +780,6 @@ test.describe(`Project management commands`, () => {
|
|||||||
const commandContinueButton = page.getByRole('button', {
|
const commandContinueButton = page.getByRole('button', {
|
||||||
name: 'Continue',
|
name: 'Continue',
|
||||||
})
|
})
|
||||||
const commandSubmitButton = page.getByRole('button', {
|
|
||||||
name: 'Submit command',
|
|
||||||
})
|
|
||||||
const toastMessage = page.getByText(`Successfully renamed`)
|
const toastMessage = page.getByText(`Successfully renamed`)
|
||||||
|
|
||||||
await test.step(`Setup`, async () => {
|
await test.step(`Setup`, async () => {
|
||||||
@ -800,8 +798,7 @@ test.describe(`Project management commands`, () => {
|
|||||||
await expect(commandContinueButton).toBeVisible()
|
await expect(commandContinueButton).toBeVisible()
|
||||||
await commandContinueButton.click()
|
await commandContinueButton.click()
|
||||||
|
|
||||||
await expect(commandSubmitButton).toBeVisible()
|
await cmdBar.submit()
|
||||||
await commandSubmitButton.click()
|
|
||||||
|
|
||||||
await expect(toastMessage).toBeVisible()
|
await expect(toastMessage).toBeVisible()
|
||||||
})
|
})
|
||||||
@ -837,9 +834,6 @@ test.describe(`Project management commands`, () => {
|
|||||||
})
|
})
|
||||||
const projectNameOption = page.getByRole('option', { name: projectName })
|
const projectNameOption = page.getByRole('option', { name: projectName })
|
||||||
const commandWarning = page.getByText('Are you sure you want to delete?')
|
const commandWarning = page.getByText('Are you sure you want to delete?')
|
||||||
const commandSubmitButton = page.getByRole('button', {
|
|
||||||
name: 'Submit command',
|
|
||||||
})
|
|
||||||
const toastMessage = page.getByText(`Successfully deleted`)
|
const toastMessage = page.getByText(`Successfully deleted`)
|
||||||
const noProjectsMessage = page.getByText('No projects found')
|
const noProjectsMessage = page.getByText('No projects found')
|
||||||
|
|
||||||
@ -859,8 +853,7 @@ test.describe(`Project management commands`, () => {
|
|||||||
await projectNameOption.click()
|
await projectNameOption.click()
|
||||||
|
|
||||||
await expect(commandWarning).toBeVisible()
|
await expect(commandWarning).toBeVisible()
|
||||||
await expect(commandSubmitButton).toBeVisible()
|
await cmdBar.submit()
|
||||||
await commandSubmitButton.click()
|
|
||||||
|
|
||||||
await expect(toastMessage).toBeVisible()
|
await expect(toastMessage).toBeVisible()
|
||||||
})
|
})
|
||||||
@ -894,9 +887,6 @@ test.describe(`Project management commands`, () => {
|
|||||||
const commandContinueButton = page.getByRole('button', {
|
const commandContinueButton = page.getByRole('button', {
|
||||||
name: 'Continue',
|
name: 'Continue',
|
||||||
})
|
})
|
||||||
const commandSubmitButton = page.getByRole('button', {
|
|
||||||
name: 'Submit command',
|
|
||||||
})
|
|
||||||
const toastMessage = page.getByText(`Successfully renamed`)
|
const toastMessage = page.getByText(`Successfully renamed`)
|
||||||
|
|
||||||
await test.step(`Setup`, async () => {
|
await test.step(`Setup`, async () => {
|
||||||
@ -914,8 +904,7 @@ test.describe(`Project management commands`, () => {
|
|||||||
await expect(commandContinueButton).toBeVisible()
|
await expect(commandContinueButton).toBeVisible()
|
||||||
await commandContinueButton.click()
|
await commandContinueButton.click()
|
||||||
|
|
||||||
await expect(commandSubmitButton).toBeVisible()
|
await cmdBar.submit()
|
||||||
await commandSubmitButton.click()
|
|
||||||
|
|
||||||
await expect(toastMessage).toBeVisible()
|
await expect(toastMessage).toBeVisible()
|
||||||
})
|
})
|
||||||
@ -949,9 +938,6 @@ test.describe(`Project management commands`, () => {
|
|||||||
})
|
})
|
||||||
const projectNameOption = page.getByRole('option', { name: projectName })
|
const projectNameOption = page.getByRole('option', { name: projectName })
|
||||||
const commandWarning = page.getByText('Are you sure you want to delete?')
|
const commandWarning = page.getByText('Are you sure you want to delete?')
|
||||||
const commandSubmitButton = page.getByRole('button', {
|
|
||||||
name: 'Submit command',
|
|
||||||
})
|
|
||||||
const toastMessage = page.getByText(`Successfully deleted`)
|
const toastMessage = page.getByText(`Successfully deleted`)
|
||||||
const noProjectsMessage = page.getByText('No projects found')
|
const noProjectsMessage = page.getByText('No projects found')
|
||||||
|
|
||||||
@ -967,8 +953,7 @@ test.describe(`Project management commands`, () => {
|
|||||||
await projectNameOption.click()
|
await projectNameOption.click()
|
||||||
|
|
||||||
await expect(commandWarning).toBeVisible()
|
await expect(commandWarning).toBeVisible()
|
||||||
await expect(commandSubmitButton).toBeVisible()
|
await cmdBar.submit()
|
||||||
await commandSubmitButton.click()
|
|
||||||
|
|
||||||
await expect(toastMessage).toBeVisible()
|
await expect(toastMessage).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { bracket } from '@e2e/playwright/fixtures/bracket'
|
import { bracket } from '@e2e/playwright/fixtures/bracket'
|
||||||
import type { Page } from '@playwright/test'
|
import type { Page } from '@playwright/test'
|
||||||
|
import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
|
||||||
import { reportRejection } from '@src/lib/trap'
|
import { reportRejection } from '@src/lib/trap'
|
||||||
import * as fsp from 'fs/promises'
|
import * as fsp from 'fs/promises'
|
||||||
|
|
||||||
@ -421,10 +422,7 @@ extrude002 = extrude(profile002, length = 150)
|
|||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
// Click the checkbox
|
// Click the checkbox
|
||||||
const submitButton = page.getByText('Confirm Export')
|
await cmdBar.submit()
|
||||||
await expect(submitButton).toBeVisible()
|
|
||||||
|
|
||||||
await page.keyboard.press('Enter')
|
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -461,8 +459,7 @@ extrude002 = extrude(profile002, length = 150)
|
|||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
// Click the checkbox
|
// Click the checkbox
|
||||||
await expect(submitButton).toBeVisible()
|
await cmdBar.submit()
|
||||||
await page.keyboard.press('Enter')
|
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -482,6 +479,7 @@ extrude002 = extrude(profile002, length = 150)
|
|||||||
test('ensure you CAN export while an export is already going', async ({
|
test('ensure you CAN export while an export is already going', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await test.step('Set up the code and durations', async () => {
|
await test.step('Set up the code and durations', async () => {
|
||||||
@ -516,11 +514,11 @@ extrude002 = extrude(profile002, length = 150)
|
|||||||
const successToastMessage = page.getByText(`Exported successfully`)
|
const successToastMessage = page.getByText(`Exported successfully`)
|
||||||
|
|
||||||
await test.step('second export', async () => {
|
await test.step('second export', async () => {
|
||||||
await clickExportButton(page)
|
await clickExportButton(page, cmdBar)
|
||||||
|
|
||||||
await expect(exportingToastMessage).toBeVisible()
|
await expect(exportingToastMessage).toBeVisible()
|
||||||
|
|
||||||
await clickExportButton(page)
|
await clickExportButton(page, cmdBar)
|
||||||
|
|
||||||
await test.step('The first export still succeeds', async () => {
|
await test.step('The first export still succeeds', async () => {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
@ -537,7 +535,7 @@ extrude002 = extrude(profile002, length = 150)
|
|||||||
|
|
||||||
await test.step('Successful, unblocked export', async () => {
|
await test.step('Successful, unblocked export', async () => {
|
||||||
// Try exporting again.
|
// Try exporting again.
|
||||||
await clickExportButton(page)
|
await clickExportButton(page, cmdBar)
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -880,7 +878,7 @@ s2 = startSketchOn(XY)
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
async function clickExportButton(page: Page) {
|
async function clickExportButton(page: Page, cmdBar: CmdBarFixture) {
|
||||||
await test.step('Running export flow', async () => {
|
await test.step('Running export flow', async () => {
|
||||||
// export the model
|
// export the model
|
||||||
const exportButton = page.getByTestId('export-pane-button')
|
const exportButton = page.getByTestId('export-pane-button')
|
||||||
@ -896,9 +894,6 @@ async function clickExportButton(page: Page) {
|
|||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
// Click the checkbox
|
// Click the checkbox
|
||||||
const submitButton = page.getByText('Confirm Export')
|
await cmdBar.submit()
|
||||||
await expect(submitButton).toBeVisible()
|
|
||||||
|
|
||||||
await page.keyboard.press('Enter')
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1478,6 +1478,7 @@ sketch001 = startSketchOn(XZ)
|
|||||||
await page.mouse.move(1200, 139)
|
await page.mouse.move(1200, 139)
|
||||||
await page.mouse.down()
|
await page.mouse.down()
|
||||||
await page.mouse.move(870, 250)
|
await page.mouse.move(870, 250)
|
||||||
|
await page.mouse.up()
|
||||||
|
|
||||||
await page.waitForTimeout(200)
|
await page.waitForTimeout(200)
|
||||||
|
|
||||||
@ -1487,6 +1488,60 @@ sketch001 = startSketchOn(XZ)
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Can undo with closed code pane', async ({
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
editor,
|
||||||
|
toolbar,
|
||||||
|
scene,
|
||||||
|
cmdBar,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
|
||||||
|
const viewportSize = { width: 1500, height: 750 }
|
||||||
|
await page.setBodyDimensions(viewportSize)
|
||||||
|
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`@settings(defaultLengthUnit=in)
|
||||||
|
sketch001 = startSketchOn(XZ)
|
||||||
|
|> startProfile(at = [-10, -10])
|
||||||
|
|> line(end = [20.0, 10.0])
|
||||||
|
|> tangentialArc(end = [5.49, 8.37])`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
await toolbar.waitForFeatureTreeToBeBuilt()
|
||||||
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
|
await (await toolbar.getFeatureTreeOperation('Sketch', 0)).dblclick()
|
||||||
|
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
|
await page.mouse.move(1200, 139)
|
||||||
|
await page.mouse.down()
|
||||||
|
await page.mouse.move(870, 250)
|
||||||
|
await page.mouse.up()
|
||||||
|
|
||||||
|
await editor.expectEditor.toContain(`tangentialArc(end=[-5.85,4.32])`, {
|
||||||
|
shouldNormalise: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
await u.closeKclCodePanel()
|
||||||
|
|
||||||
|
// Undo the last change
|
||||||
|
await page.keyboard.down('Control')
|
||||||
|
await page.keyboard.press('KeyZ')
|
||||||
|
await page.keyboard.up('Control')
|
||||||
|
|
||||||
|
await u.openKclCodePanel()
|
||||||
|
await editor.expectEditor.toContain(`tangentialArc(end = [5.49, 8.37])`, {
|
||||||
|
shouldNormalise: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test('Can delete a single segment line with keyboard', async ({
|
test('Can delete a single segment line with keyboard', async ({
|
||||||
page,
|
page,
|
||||||
scene,
|
scene,
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 135 KiB |
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 135 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 65 KiB |
@ -22,6 +22,7 @@ export const token = process.env.token || ''
|
|||||||
import type { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfiguration'
|
import type { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfiguration'
|
||||||
|
|
||||||
import type { ElectronZoo } from '@e2e/playwright/fixtures/fixtureSetup'
|
import type { ElectronZoo } from '@e2e/playwright/fixtures/fixtureSetup'
|
||||||
|
import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
|
||||||
import { isErrorWhitelisted } from '@e2e/playwright/lib/console-error-whitelist'
|
import { isErrorWhitelisted } from '@e2e/playwright/lib/console-error-whitelist'
|
||||||
import { TEST_SETTINGS, TEST_SETTINGS_KEY } from '@e2e/playwright/storageStates'
|
import { TEST_SETTINGS, TEST_SETTINGS_KEY } from '@e2e/playwright/storageStates'
|
||||||
import { test } from '@e2e/playwright/zoo-test'
|
import { test } from '@e2e/playwright/zoo-test'
|
||||||
@ -158,10 +159,10 @@ async function openKclCodePanel(page: Page) {
|
|||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
// editorManager is available on the window object.
|
// editorManager is available on the window object.
|
||||||
//@ts-ignore this is in an entirely different context that tsc can't see.
|
//@ts-ignore this is in an entirely different context that tsc can't see.
|
||||||
editorManager._editorView.dispatch({
|
editorManager.getEditorView().dispatch({
|
||||||
selection: {
|
selection: {
|
||||||
//@ts-ignore this is in an entirely different context that tsc can't see.
|
//@ts-ignore this is in an entirely different context that tsc can't see.
|
||||||
anchor: editorManager._editorView.docView.length,
|
anchor: editorManager.getEditorView().docView.length,
|
||||||
},
|
},
|
||||||
scrollIntoView: true,
|
scrollIntoView: true,
|
||||||
})
|
})
|
||||||
@ -737,6 +738,7 @@ export const doExport = async (
|
|||||||
output: Models['OutputFormat3d_type'],
|
output: Models['OutputFormat3d_type'],
|
||||||
rootDir: string,
|
rootDir: string,
|
||||||
page: Page,
|
page: Page,
|
||||||
|
cmdBar: CmdBarFixture,
|
||||||
exportFrom: 'dropdown' | 'sidebarButton' | 'commandBar' = 'dropdown'
|
exportFrom: 'dropdown' | 'sidebarButton' | 'commandBar' = 'dropdown'
|
||||||
): Promise<Paths> => {
|
): Promise<Paths> => {
|
||||||
if (exportFrom === 'dropdown') {
|
if (exportFrom === 'dropdown') {
|
||||||
@ -780,9 +782,7 @@ export const doExport = async (
|
|||||||
.click()
|
.click()
|
||||||
await page.locator('#arg-form').waitFor({ state: 'detached' })
|
await page.locator('#arg-form').waitFor({ state: 'detached' })
|
||||||
}
|
}
|
||||||
await expect(page.getByText('Confirm Export')).toBeVisible()
|
await cmdBar.submit()
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Submit command' }).click()
|
|
||||||
|
|
||||||
await expect(page.getByText('Exported successfully')).toBeVisible()
|
await expect(page.getByText('Exported successfully')).toBeVisible()
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import {
|
|||||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||||
|
|
||||||
test.describe('Testing constraints', () => {
|
test.describe('Testing constraints', () => {
|
||||||
test('Can constrain line length', async ({ page, homePage }) => {
|
test('Can constrain line length', async ({ page, homePage, cmdBar }) => {
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
@ -50,11 +50,7 @@ test.describe('Testing constraints', () => {
|
|||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page.getByTestId('constraint-length').click()
|
await page.getByTestId('constraint-length').click()
|
||||||
await page.getByTestId('cmd-bar-arg-value').getByRole('textbox').fill('20')
|
await page.getByTestId('cmd-bar-arg-value').getByRole('textbox').fill('20')
|
||||||
await page
|
await cmdBar.continue()
|
||||||
.getByRole('button', {
|
|
||||||
name: 'arrow right Continue',
|
|
||||||
})
|
|
||||||
.click()
|
|
||||||
|
|
||||||
await expect(page.locator('.cm-content')).toHaveText(
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
`length001 = 20sketch001 = startSketchOn(XY) |> startProfile(at = [-10, -10]) |> line(end = [20, 0]) |> angledLine(angle = 90, length = length001) |> xLine(length = -20)`
|
`length001 = 20sketch001 = startSketchOn(XY) |> startProfile(at = [-10, -10]) |> line(end = [20, 0]) |> angledLine(angle = 90, length = length001) |> xLine(length = -20)`
|
||||||
@ -681,9 +677,6 @@ test.describe('Testing constraints', () => {
|
|||||||
.getByRole('textbox')
|
.getByRole('textbox')
|
||||||
const cmdBarKclVariableNameInput =
|
const cmdBarKclVariableNameInput =
|
||||||
page.getByPlaceholder('Variable name')
|
page.getByPlaceholder('Variable name')
|
||||||
const cmdBarSubmitButton = page.getByRole('button', {
|
|
||||||
name: 'arrow right Continue',
|
|
||||||
})
|
|
||||||
|
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
@ -736,7 +729,7 @@ part002 = startSketchOn(XZ)
|
|||||||
await page.waitForTimeout(500)
|
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 cmdBar.continue()
|
||||||
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
|
||||||
@ -1101,11 +1094,7 @@ part002 = startSketchOn(XZ)
|
|||||||
await page.waitForTimeout(500)
|
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 cmdBar.continue()
|
||||||
.getByRole('button', {
|
|
||||||
name: 'arrow right Continue',
|
|
||||||
})
|
|
||||||
.click()
|
|
||||||
|
|
||||||
await pollEditorLinesSelectedLength(page, 1)
|
await pollEditorLinesSelectedLength(page, 1)
|
||||||
activeLinesContent = await page.locator('.cm-activeLine').all()
|
activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||||
|
|||||||
@ -21,7 +21,7 @@ test.describe('Testing loading external models', () => {
|
|||||||
// We have no more web tests
|
// We have no more web tests
|
||||||
test.fail(
|
test.fail(
|
||||||
'Web: should overwrite current code, cannot create new file',
|
'Web: should overwrite current code, cannot create new file',
|
||||||
async ({ editor, context, page, homePage }) => {
|
async ({ editor, context, page, homePage, cmdBar }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await test.step(`Test setup`, async () => {
|
await test.step(`Test setup`, async () => {
|
||||||
await context.addInitScript((code) => {
|
await context.addInitScript((code) => {
|
||||||
@ -52,9 +52,6 @@ test.describe('Testing loading external models', () => {
|
|||||||
name,
|
name,
|
||||||
})
|
})
|
||||||
const warningText = page.getByText('Overwrite current file with sample?')
|
const warningText = page.getByText('Overwrite current file with sample?')
|
||||||
const confirmButton = page.getByRole('button', {
|
|
||||||
name: 'Submit command',
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step(`Precondition: check the initial code`, async () => {
|
await test.step(`Precondition: check the initial code`, async () => {
|
||||||
await u.openKclCodePanel()
|
await u.openKclCodePanel()
|
||||||
@ -70,7 +67,7 @@ test.describe('Testing loading external models', () => {
|
|||||||
await expect(commandMethodOption('Create new file')).not.toBeVisible()
|
await expect(commandMethodOption('Create new file')).not.toBeVisible()
|
||||||
await commandMethodOption('Overwrite').click()
|
await commandMethodOption('Overwrite').click()
|
||||||
await expect(warningText).toBeVisible()
|
await expect(warningText).toBeVisible()
|
||||||
await confirmButton.click()
|
await cmdBar.submit()
|
||||||
|
|
||||||
await editor.expectEditor.toContain('// ' + newSample.title)
|
await editor.expectEditor.toContain('// ' + newSample.title)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import type { LineInputsType } from '@src/lang/std/sketchcombos'
|
|||||||
import { uuidv4 } from '@src/lib/utils'
|
import { uuidv4 } from '@src/lib/utils'
|
||||||
|
|
||||||
import type { EditorFixture } from '@e2e/playwright/fixtures/editorFixture'
|
import type { EditorFixture } from '@e2e/playwright/fixtures/editorFixture'
|
||||||
|
import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
|
||||||
import { deg, getUtils, wiggleMove } from '@e2e/playwright/test-utils'
|
import { deg, getUtils, wiggleMove } from '@e2e/playwright/test-utils'
|
||||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||||
|
|
||||||
@ -18,7 +19,7 @@ test.describe('Testing segment overlays', () => {
|
|||||||
* @param {number} options.steps - The number of steps to perform
|
* @param {number} options.steps - The number of steps to perform
|
||||||
*/
|
*/
|
||||||
const _clickConstrained =
|
const _clickConstrained =
|
||||||
(page: Page, editor: EditorFixture) =>
|
(page: Page, editor: EditorFixture, cmdBar: CmdBarFixture) =>
|
||||||
async ({
|
async ({
|
||||||
hoverPos,
|
hoverPos,
|
||||||
constraintType,
|
constraintType,
|
||||||
@ -93,11 +94,7 @@ test.describe('Testing segment overlays', () => {
|
|||||||
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
|
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
|
||||||
).toBeFocused()
|
).toBeFocused()
|
||||||
await page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
await page
|
await cmdBar.continue()
|
||||||
.getByRole('button', {
|
|
||||||
name: 'arrow right Continue',
|
|
||||||
})
|
|
||||||
.click()
|
|
||||||
await editor.expectEditor.toContain(expectFinal, {
|
await editor.expectEditor.toContain(expectFinal, {
|
||||||
shouldNormalise: true,
|
shouldNormalise: true,
|
||||||
})
|
})
|
||||||
@ -113,7 +110,7 @@ test.describe('Testing segment overlays', () => {
|
|||||||
* @param {number} options.steps - The number of steps to perform
|
* @param {number} options.steps - The number of steps to perform
|
||||||
*/
|
*/
|
||||||
const _clickUnconstrained =
|
const _clickUnconstrained =
|
||||||
(page: Page, editor: EditorFixture) =>
|
(page: Page, editor: EditorFixture, cmdBar: CmdBarFixture) =>
|
||||||
async ({
|
async ({
|
||||||
hoverPos,
|
hoverPos,
|
||||||
constraintType,
|
constraintType,
|
||||||
@ -163,11 +160,7 @@ test.describe('Testing segment overlays', () => {
|
|||||||
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
|
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
|
||||||
).toBeFocused()
|
).toBeFocused()
|
||||||
await page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
await page
|
await cmdBar.continue()
|
||||||
.getByRole('button', {
|
|
||||||
name: 'arrow right Continue',
|
|
||||||
})
|
|
||||||
.click()
|
|
||||||
await editor.expectEditor.toContain(expectAfterUnconstrained, {
|
await editor.expectEditor.toContain(expectAfterUnconstrained, {
|
||||||
shouldNormalise: true,
|
shouldNormalise: true,
|
||||||
})
|
})
|
||||||
@ -239,8 +232,8 @@ test.describe('Testing segment overlays', () => {
|
|||||||
|
|
||||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(14)
|
await expect(page.getByTestId('segment-overlay')).toHaveCount(14)
|
||||||
|
|
||||||
const clickUnconstrained = _clickUnconstrained(page, editor)
|
const clickUnconstrained = _clickUnconstrained(page, editor, cmdBar)
|
||||||
const clickConstrained = _clickConstrained(page, editor)
|
const clickConstrained = _clickConstrained(page, editor, cmdBar)
|
||||||
|
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
await u.sendCustomCmd({
|
await u.sendCustomCmd({
|
||||||
@ -664,7 +657,7 @@ profile002 = circle(sketch001, center = [345, 0], radius = 238.38)
|
|||||||
await expect(
|
await expect(
|
||||||
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
|
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
|
||||||
).toBeFocused()
|
).toBeFocused()
|
||||||
await page.getByRole('button', { name: 'arrow right Continue' }).click()
|
await cmdBar.continue()
|
||||||
|
|
||||||
// Verify the X constraint was added
|
// Verify the X constraint was added
|
||||||
await editor.expectEditor.toContain('center = [xAbs001, 0]', {
|
await editor.expectEditor.toContain('center = [xAbs001, 0]', {
|
||||||
@ -682,7 +675,7 @@ profile002 = circle(sketch001, center = [345, 0], radius = 238.38)
|
|||||||
await expect(
|
await expect(
|
||||||
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
|
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
|
||||||
).toBeFocused()
|
).toBeFocused()
|
||||||
await page.getByRole('button', { name: 'arrow right Continue' }).click()
|
await cmdBar.continue()
|
||||||
|
|
||||||
// Verify the Y constraint was added
|
// Verify the Y constraint was added
|
||||||
await editor.expectEditor.toContain('center = [xAbs001, yAbs001]', {
|
await editor.expectEditor.toContain('center = [xAbs001, yAbs001]', {
|
||||||
@ -700,7 +693,7 @@ profile002 = circle(sketch001, center = [345, 0], radius = 238.38)
|
|||||||
await expect(
|
await expect(
|
||||||
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
|
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
|
||||||
).toBeFocused()
|
).toBeFocused()
|
||||||
await page.getByRole('button', { name: 'arrow right Continue' }).click()
|
await cmdBar.continue()
|
||||||
|
|
||||||
// Verify all constraints were added
|
// Verify all constraints were added
|
||||||
await editor.expectEditor.toContain(
|
await editor.expectEditor.toContain(
|
||||||
@ -887,7 +880,7 @@ profile003 = startProfile(sketch001, at = [64.39, 35.16])
|
|||||||
await expect(
|
await expect(
|
||||||
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
|
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
|
||||||
).toBeFocused()
|
).toBeFocused()
|
||||||
await page.getByRole('button', { name: 'arrow right Continue' }).click()
|
await cmdBar.continue()
|
||||||
|
|
||||||
// Verify the constraint was added
|
// Verify the constraint was added
|
||||||
await editor.expectEditor.toContain(
|
await editor.expectEditor.toContain(
|
||||||
@ -910,7 +903,7 @@ profile003 = startProfile(sketch001, at = [64.39, 35.16])
|
|||||||
await expect(
|
await expect(
|
||||||
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
|
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
|
||||||
).toBeFocused()
|
).toBeFocused()
|
||||||
await page.getByRole('button', { name: 'arrow right Continue' }).click()
|
await cmdBar.continue()
|
||||||
|
|
||||||
// Verify both constraints were added
|
// Verify both constraints were added
|
||||||
await editor.expectEditor.toContain(
|
await editor.expectEditor.toContain(
|
||||||
@ -935,7 +928,7 @@ profile003 = startProfile(sketch001, at = [64.39, 35.16])
|
|||||||
await expect(
|
await expect(
|
||||||
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
|
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
|
||||||
).toBeFocused()
|
).toBeFocused()
|
||||||
await page.getByRole('button', { name: 'arrow right Continue' }).click()
|
await cmdBar.continue()
|
||||||
|
|
||||||
// Verify the constraint was added
|
// Verify the constraint was added
|
||||||
await editor.expectEditor.toContain('endAbsolute = [xAbs002, 84.07]', {
|
await editor.expectEditor.toContain('endAbsolute = [xAbs002, 84.07]', {
|
||||||
@ -955,7 +948,7 @@ profile003 = startProfile(sketch001, at = [64.39, 35.16])
|
|||||||
await expect(
|
await expect(
|
||||||
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
|
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
|
||||||
).toBeFocused()
|
).toBeFocused()
|
||||||
await page.getByRole('button', { name: 'arrow right Continue' }).click()
|
await cmdBar.continue()
|
||||||
|
|
||||||
// Verify all constraints were added
|
// Verify all constraints were added
|
||||||
await editor.expectEditor.toContain(
|
await editor.expectEditor.toContain(
|
||||||
|
|||||||
@ -32,7 +32,7 @@ test('Units menu', async ({ page, homePage }) => {
|
|||||||
test(
|
test(
|
||||||
'Successful export shows a success toast',
|
'Successful export shows a success toast',
|
||||||
{ tag: '@skipLocalEngine' },
|
{ tag: '@skipLocalEngine' },
|
||||||
async ({ page, homePage, tronApp }) => {
|
async ({ page, homePage, cmdBar, tronApp }) => {
|
||||||
// FYI this test doesn't work with only engine running locally
|
// FYI this test doesn't work with only engine running locally
|
||||||
// And you will need to have the KittyCAD CLI installed
|
// And you will need to have the KittyCAD CLI installed
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
@ -94,7 +94,8 @@ part001 = startSketchOn(-XZ)
|
|||||||
presentation: 'pretty',
|
presentation: 'pretty',
|
||||||
},
|
},
|
||||||
tronApp?.projectDirName,
|
tronApp?.projectDirName,
|
||||||
page
|
page,
|
||||||
|
cmdBar
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -254,6 +255,7 @@ test('First escape in tool pops you out of tool, second exits sketch mode', asyn
|
|||||||
test('Basic default modeling and sketch hotkeys work', async ({
|
test('Basic default modeling and sketch hotkeys work', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await test.step(`Set up test`, async () => {
|
await test.step(`Set up test`, async () => {
|
||||||
@ -397,11 +399,8 @@ test('Basic default modeling and sketch hotkeys work', async ({
|
|||||||
await expect(page.getByRole('button', { name: 'Continue' })).toBeVisible({
|
await expect(page.getByRole('button', { name: 'Continue' })).toBeVisible({
|
||||||
timeout: 20_000,
|
timeout: 20_000,
|
||||||
})
|
})
|
||||||
await page.getByRole('button', { name: 'Continue' }).click()
|
await cmdBar.continue()
|
||||||
await expect(
|
await cmdBar.submit()
|
||||||
page.getByRole('button', { name: 'Submit command' })
|
|
||||||
).toBeVisible()
|
|
||||||
await page.getByRole('button', { name: 'Submit command' }).click()
|
|
||||||
await expect(page.locator('.cm-content')).toContainText('extrude(')
|
await expect(page.locator('.cm-content')).toContainText('extrude(')
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -575,8 +574,7 @@ profile001 = startProfile(sketch002, at = [-12.34, 12.34])
|
|||||||
|
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await expect(page.getByText('Confirm Extrude')).toBeVisible()
|
await cmdBar.submit()
|
||||||
await cmdBar.progressCmdBar()
|
|
||||||
|
|
||||||
const result2 = result.genNext`
|
const result2 = result.genNext`
|
||||||
const sketch002 = extrude(sketch002, length = ${[5, 5]} + 7)`
|
const sketch002 = extrude(sketch002, length = ${[5, 5]} + 7)`
|
||||||
|
|||||||
@ -111,7 +111,8 @@ commaSep1NoTrailingComma<term> { term ("," term)* }
|
|||||||
|
|
||||||
PipeSubstitution { "%" }
|
PipeSubstitution { "%" }
|
||||||
|
|
||||||
identifier { (@asciiLetter | "_") (@asciiLetter | @digit | "_")* }
|
// Includes non-whitespace unicode characters.
|
||||||
|
identifier { $[a-zA-Z_\u{a1}-\u{167f}\u{1681}-\u{1fff}\u{200e}-\u{2027}\u{202a}-\u{202e}\u{2030}-\u{205e}\u{2061}-\u{2fff}\u{3001}-\u{fefe}\u{ff00}-\u{10ffff}] $[a-zA-Z0-9_\u{a1}-\u{167f}\u{1681}-\u{1fff}\u{200e}-\u{2027}\u{202a}-\u{202e}\u{2030}-\u{205e}\u{2061}-\u{2fff}\u{3001}-\u{fefe}\u{ff00}-\u{10ffff}]* }
|
||||||
AnnotationName { "@" identifier? }
|
AnnotationName { "@" identifier? }
|
||||||
PropertyName { identifier }
|
PropertyName { identifier }
|
||||||
TagDeclarator { "$" identifier }
|
TagDeclarator { "$" identifier }
|
||||||
|
|||||||
@ -42,15 +42,15 @@ fn helicalGear(nTeeth, module, pressureAngle, helixAngle, gearHeight) {
|
|||||||
helicalGearSketch = startSketchOn(offsetPlane(XY, offset = offsetHeight))
|
helicalGearSketch = startSketchOn(offsetPlane(XY, offset = offsetHeight))
|
||||||
|> startProfile(at = polar(angle = helixCalc, length = baseDiameter / 2))
|
|> startProfile(at = polar(angle = helixCalc, length = baseDiameter / 2))
|
||||||
|> involuteCircular(
|
|> involuteCircular(
|
||||||
startRadius = baseDiameter / 2,
|
startDiameter = baseDiameter,
|
||||||
endRadius = tipDiameter / 2,
|
endDiameter = tipDiameter,
|
||||||
angle = helixCalc,
|
angle = helixCalc,
|
||||||
tag = $seg01,
|
tag = $seg01,
|
||||||
)
|
)
|
||||||
|> line(endAbsolute = polar(angle = 160 / nTeeth + helixCalc, length = tipDiameter / 2))
|
|> line(endAbsolute = polar(angle = 160 / nTeeth + helixCalc, length = tipDiameter / 2))
|
||||||
|> involuteCircular(
|
|> involuteCircular(
|
||||||
startRadius = baseDiameter / 2,
|
startDiameter = baseDiameter,
|
||||||
endRadius = tipDiameter / 2,
|
endDiameter = tipDiameter,
|
||||||
angle = -(4 * atan(segEndY(seg01) / segEndX(seg01)) - (3 * helixCalc)),
|
angle = -(4 * atan(segEndY(seg01) / segEndX(seg01)) - (3 * helixCalc)),
|
||||||
reverse = true,
|
reverse = true,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -3334,7 +3334,7 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
parsing::ast::types::{BodyItem, Expr, VariableKind},
|
parsing::ast::types::{BodyItem, Expr, VariableKind},
|
||||||
KclError, ModuleId,
|
ModuleId,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn assert_reserved(word: &str) {
|
fn assert_reserved(word: &str) {
|
||||||
@ -4398,14 +4398,10 @@ secondExtrude = startSketchOn(XY)
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_parse_parens_unicode() {
|
fn test_parse_parens_unicode() {
|
||||||
let result = crate::parsing::top_level_parse("(ޜ");
|
let result = crate::parsing::top_level_parse("(ޜ");
|
||||||
let KclError::Lexical { details } = result.0.unwrap_err() else {
|
let details = result.0.unwrap().1.pop().unwrap();
|
||||||
panic!();
|
// TODO: Highlight where the unmatched open parenthesis is.
|
||||||
};
|
|
||||||
// TODO: Better errors when program cannot tokenize.
|
|
||||||
// https://github.com/KittyCAD/modeling-app/issues/696
|
// https://github.com/KittyCAD/modeling-app/issues/696
|
||||||
assert_eq!(details.message, "found unknown token 'ޜ'");
|
assert_eq!(details.message, "Unexpected end of file. The compiler expected )");
|
||||||
assert_eq!(details.source_ranges[0].start(), 1);
|
|
||||||
assert_eq!(details.source_ranges[0].end(), 2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -6,7 +6,7 @@ use winnow::{
|
|||||||
error::{ContextError, ParseError},
|
error::{ContextError, ParseError},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
stream::{Location, Stream},
|
stream::{Location, Stream},
|
||||||
token::{any, none_of, one_of, take_till, take_until},
|
token::{any, none_of, take_till, take_until, take_while},
|
||||||
LocatingSlice, Stateful,
|
LocatingSlice, Stateful,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -163,8 +163,8 @@ fn whitespace(i: &mut Input<'_>) -> ModalResult<Token> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn inner_word(i: &mut Input<'_>) -> ModalResult<()> {
|
fn inner_word(i: &mut Input<'_>) -> ModalResult<()> {
|
||||||
one_of(('a'..='z', 'A'..='Z', '_')).parse_next(i)?;
|
take_while(1.., |c: char| c.is_alphabetic() || c == '_').parse_next(i)?;
|
||||||
repeat::<_, _, (), _, _>(0.., one_of(('a'..='z', 'A'..='Z', '0'..='9', '_'))).parse_next(i)?;
|
take_while(0.., |c: char| c.is_alphabetic() || c.is_ascii_digit() || c == '_').parse_next(i)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -786,6 +786,7 @@ const things = "things"
|
|||||||
};
|
};
|
||||||
assert_eq!(actual.tokens[0], expected);
|
assert_eq!(actual.tokens[0], expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_word_starting_with_keyword() {
|
fn test_word_starting_with_keyword() {
|
||||||
let module_id = ModuleId::default();
|
let module_id = ModuleId::default();
|
||||||
@ -799,4 +800,18 @@ const things = "things"
|
|||||||
};
|
};
|
||||||
assert_eq!(actual.tokens[0], expected);
|
assert_eq!(actual.tokens[0], expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn non_english_identifiers() {
|
||||||
|
let module_id = ModuleId::default();
|
||||||
|
let actual = lex("亞當", module_id).unwrap();
|
||||||
|
let expected = Token {
|
||||||
|
token_type: TokenType::Word,
|
||||||
|
value: "亞當".to_owned(),
|
||||||
|
start: 0,
|
||||||
|
end: 6,
|
||||||
|
module_id,
|
||||||
|
};
|
||||||
|
assert_eq!(actual.tokens[0], expected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3605,3 +3605,24 @@ mod user_reported_union_2_bug {
|
|||||||
super::execute(TEST_NAME, false).await
|
super::execute(TEST_NAME, false).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mod non_english_identifiers {
|
||||||
|
const TEST_NAME: &str = "non_english_identifiers";
|
||||||
|
|
||||||
|
/// Test parsing KCL.
|
||||||
|
#[test]
|
||||||
|
fn parse() {
|
||||||
|
super::parse(TEST_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn unparse() {
|
||||||
|
super::unparse(TEST_NAME).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that KCL is executed correctly.
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn kcl_test_execute() {
|
||||||
|
super::execute(TEST_NAME, true).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@ use kittycad_modeling_cmds::{
|
|||||||
websocket::OkWebSocketResponseData,
|
websocket::OkWebSocketResponseData,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{args::TyF64, DEFAULT_TOLERANCE};
|
use super::{args::TyF64, DEFAULT_TOLERANCE_MM};
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
execution::{types::RuntimeType, ExecState, KclValue, ModelingCmdMeta, Solid},
|
execution::{types::RuntimeType, ExecState, KclValue, ModelingCmdMeta, Solid},
|
||||||
@ -57,7 +57,7 @@ pub(crate) async fn inner_union(
|
|||||||
ModelingCmdMeta::from_args_id(&args, solid_out_id),
|
ModelingCmdMeta::from_args_id(&args, solid_out_id),
|
||||||
ModelingCmd::from(mcmd::BooleanUnion {
|
ModelingCmd::from(mcmd::BooleanUnion {
|
||||||
solid_ids: solids.iter().map(|s| s.id).collect(),
|
solid_ids: solids.iter().map(|s| s.id).collect(),
|
||||||
tolerance: LengthUnit(tolerance.map(|t| t.n).unwrap_or(DEFAULT_TOLERANCE)),
|
tolerance: LengthUnit(tolerance.map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE_MM)),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@ -122,7 +122,7 @@ pub(crate) async fn inner_intersect(
|
|||||||
ModelingCmdMeta::from_args_id(&args, solid_out_id),
|
ModelingCmdMeta::from_args_id(&args, solid_out_id),
|
||||||
ModelingCmd::from(mcmd::BooleanIntersection {
|
ModelingCmd::from(mcmd::BooleanIntersection {
|
||||||
solid_ids: solids.iter().map(|s| s.id).collect(),
|
solid_ids: solids.iter().map(|s| s.id).collect(),
|
||||||
tolerance: LengthUnit(tolerance.map(|t| t.n).unwrap_or(DEFAULT_TOLERANCE)),
|
tolerance: LengthUnit(tolerance.map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE_MM)),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@ -186,7 +186,7 @@ pub(crate) async fn inner_subtract(
|
|||||||
ModelingCmd::from(mcmd::BooleanSubtract {
|
ModelingCmd::from(mcmd::BooleanSubtract {
|
||||||
target_ids: solids.iter().map(|s| s.id).collect(),
|
target_ids: solids.iter().map(|s| s.id).collect(),
|
||||||
tool_ids: tools.iter().map(|s| s.id).collect(),
|
tool_ids: tools.iter().map(|s| s.id).collect(),
|
||||||
tolerance: LengthUnit(tolerance.map(|t| t.n).unwrap_or(DEFAULT_TOLERANCE)),
|
tolerance: LengthUnit(tolerance.map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE_MM)),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
@ -18,7 +18,7 @@ use kittycad_modeling_cmds::{
|
|||||||
};
|
};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::{args::TyF64, utils::point_to_mm, DEFAULT_TOLERANCE};
|
use super::{args::TyF64, utils::point_to_mm, DEFAULT_TOLERANCE_MM};
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
execution::{
|
execution::{
|
||||||
@ -79,7 +79,7 @@ async fn inner_extrude(
|
|||||||
) -> Result<Vec<Solid>, KclError> {
|
) -> Result<Vec<Solid>, KclError> {
|
||||||
// Extrude the element(s).
|
// Extrude the element(s).
|
||||||
let mut solids = Vec::new();
|
let mut solids = Vec::new();
|
||||||
let tolerance = LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE));
|
let tolerance = LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE_MM));
|
||||||
|
|
||||||
if symmetric.unwrap_or(false) && bidirectional_length.is_some() {
|
if symmetric.unwrap_or(false) && bidirectional_length.is_some() {
|
||||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||||
|
|||||||
@ -6,7 +6,7 @@ use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, shared::CutType, ModelingC
|
|||||||
use kittycad_modeling_cmds as kcmc;
|
use kittycad_modeling_cmds as kcmc;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::{args::TyF64, DEFAULT_TOLERANCE};
|
use super::{args::TyF64, DEFAULT_TOLERANCE_MM};
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
execution::{
|
execution::{
|
||||||
@ -122,7 +122,7 @@ async fn inner_fillet(
|
|||||||
strategy: Default::default(),
|
strategy: Default::default(),
|
||||||
object_id: solid.id,
|
object_id: solid.id,
|
||||||
radius: LengthUnit(radius.to_mm()),
|
radius: LengthUnit(radius.to_mm()),
|
||||||
tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)),
|
tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE_MM)),
|
||||||
cut_type: CutType::Fillet,
|
cut_type: CutType::Fillet,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -6,7 +6,7 @@ use anyhow::Result;
|
|||||||
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, ModelingCmd};
|
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, ModelingCmd};
|
||||||
use kittycad_modeling_cmds as kcmc;
|
use kittycad_modeling_cmds as kcmc;
|
||||||
|
|
||||||
use super::{args::TyF64, DEFAULT_TOLERANCE};
|
use super::{args::TyF64, DEFAULT_TOLERANCE_MM};
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
execution::{
|
execution::{
|
||||||
@ -84,7 +84,7 @@ async fn inner_loft(
|
|||||||
section_ids: sketches.iter().map(|group| group.id).collect(),
|
section_ids: sketches.iter().map(|group| group.id).collect(),
|
||||||
base_curve_index,
|
base_curve_index,
|
||||||
bez_approximate_rational,
|
bez_approximate_rational,
|
||||||
tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)),
|
tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE_MM)),
|
||||||
v_degree,
|
v_degree,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -442,5 +442,5 @@ pub(crate) fn std_ty(path: &str, fn_name: &str) -> (PrimitiveType, StdFnProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The default tolerance for modeling commands in [`kittycad_modeling_cmds::length_unit::LengthUnit`].
|
/// The default tolerance for modeling commands in millimeters.
|
||||||
const DEFAULT_TOLERANCE: f64 = 0.0000001;
|
const DEFAULT_TOLERANCE_MM: f64 = 0.0000001;
|
||||||
|
|||||||
@ -9,7 +9,7 @@ use kcmc::{
|
|||||||
};
|
};
|
||||||
use kittycad_modeling_cmds::{self as kcmc, shared::Point3d};
|
use kittycad_modeling_cmds::{self as kcmc, shared::Point3d};
|
||||||
|
|
||||||
use super::{args::TyF64, DEFAULT_TOLERANCE};
|
use super::{args::TyF64, DEFAULT_TOLERANCE_MM};
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
execution::{
|
execution::{
|
||||||
@ -133,7 +133,7 @@ async fn inner_revolve(
|
|||||||
let mut solids = Vec::new();
|
let mut solids = Vec::new();
|
||||||
for sketch in &sketches {
|
for sketch in &sketches {
|
||||||
let id = exec_state.next_uuid();
|
let id = exec_state.next_uuid();
|
||||||
let tolerance = tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE);
|
let tolerance = tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE_MM);
|
||||||
|
|
||||||
let direction = match &axis {
|
let direction = match &axis {
|
||||||
Axis2dOrEdgeReference::Axis { direction, origin } => {
|
Axis2dOrEdgeReference::Axis { direction, origin } => {
|
||||||
|
|||||||
@ -415,16 +415,26 @@ pub(crate) fn get_radius(
|
|||||||
radius: Option<TyF64>,
|
radius: Option<TyF64>,
|
||||||
diameter: Option<TyF64>,
|
diameter: Option<TyF64>,
|
||||||
source_range: SourceRange,
|
source_range: SourceRange,
|
||||||
|
) -> Result<TyF64, KclError> {
|
||||||
|
get_radius_labelled(radius, diameter, source_range, "radius", "diameter")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_radius_labelled(
|
||||||
|
radius: Option<TyF64>,
|
||||||
|
diameter: Option<TyF64>,
|
||||||
|
source_range: SourceRange,
|
||||||
|
label_radius: &'static str,
|
||||||
|
label_diameter: &'static str,
|
||||||
) -> Result<TyF64, KclError> {
|
) -> Result<TyF64, KclError> {
|
||||||
match (radius, diameter) {
|
match (radius, diameter) {
|
||||||
(Some(radius), None) => Ok(radius),
|
(Some(radius), None) => Ok(radius),
|
||||||
(None, Some(diameter)) => Ok(TyF64::new(diameter.n / 2.0, diameter.ty)),
|
(None, Some(diameter)) => Ok(TyF64::new(diameter.n / 2.0, diameter.ty)),
|
||||||
(None, None) => Err(KclError::new_type(KclErrorDetails::new(
|
(None, None) => Err(KclError::new_type(KclErrorDetails::new(
|
||||||
"This function needs either `diameter` or `radius`".to_string(),
|
format!("This function needs either `{label_diameter}` or `{label_radius}`"),
|
||||||
vec![source_range],
|
vec![source_range],
|
||||||
))),
|
))),
|
||||||
(Some(_), Some(_)) => Err(KclError::new_type(KclErrorDetails::new(
|
(Some(_), Some(_)) => Err(KclError::new_type(KclErrorDetails::new(
|
||||||
"You cannot specify both `diameter` and `radius`, please remove one".to_string(),
|
format!("You cannot specify both `{label_diameter}` and `{label_radius}`, please remove one"),
|
||||||
vec![source_range],
|
vec![source_range],
|
||||||
))),
|
))),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ use parse_display::{Display, FromStr};
|
|||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::shapes::get_radius;
|
use super::shapes::{get_radius, get_radius_labelled};
|
||||||
#[cfg(feature = "artifact-graph")]
|
#[cfg(feature = "artifact-graph")]
|
||||||
use crate::execution::{Artifact, ArtifactId, CodeRef, StartSketchOnFace, StartSketchOnPlane};
|
use crate::execution::{Artifact, ArtifactId, CodeRef, StartSketchOnFace, StartSketchOnPlane};
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -101,13 +101,26 @@ pub const NEW_TAG_KW: &str = "tag";
|
|||||||
pub async fn involute_circular(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
pub async fn involute_circular(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
let sketch = args.get_unlabeled_kw_arg("sketch", &RuntimeType::sketch(), exec_state)?;
|
let sketch = args.get_unlabeled_kw_arg("sketch", &RuntimeType::sketch(), exec_state)?;
|
||||||
|
|
||||||
let start_radius: TyF64 = args.get_kw_arg("startRadius", &RuntimeType::length(), exec_state)?;
|
let start_radius: Option<TyF64> = args.get_kw_arg_opt("startRadius", &RuntimeType::length(), exec_state)?;
|
||||||
let end_radius: TyF64 = args.get_kw_arg("endRadius", &RuntimeType::length(), exec_state)?;
|
let end_radius: Option<TyF64> = args.get_kw_arg_opt("endRadius", &RuntimeType::length(), exec_state)?;
|
||||||
|
let start_diameter: Option<TyF64> = args.get_kw_arg_opt("startDiameter", &RuntimeType::length(), exec_state)?;
|
||||||
|
let end_diameter: Option<TyF64> = args.get_kw_arg_opt("endDiameter", &RuntimeType::length(), exec_state)?;
|
||||||
let angle: TyF64 = args.get_kw_arg("angle", &RuntimeType::angle(), exec_state)?;
|
let angle: TyF64 = args.get_kw_arg("angle", &RuntimeType::angle(), exec_state)?;
|
||||||
let reverse = args.get_kw_arg_opt("reverse", &RuntimeType::bool(), exec_state)?;
|
let reverse = args.get_kw_arg_opt("reverse", &RuntimeType::bool(), exec_state)?;
|
||||||
let tag = args.get_kw_arg_opt("tag", &RuntimeType::tag_decl(), exec_state)?;
|
let tag = args.get_kw_arg_opt("tag", &RuntimeType::tag_decl(), exec_state)?;
|
||||||
let new_sketch =
|
let new_sketch = inner_involute_circular(
|
||||||
inner_involute_circular(sketch, start_radius, end_radius, angle, reverse, tag, exec_state, args).await?;
|
sketch,
|
||||||
|
start_radius,
|
||||||
|
end_radius,
|
||||||
|
start_diameter,
|
||||||
|
end_diameter,
|
||||||
|
angle,
|
||||||
|
reverse,
|
||||||
|
tag,
|
||||||
|
exec_state,
|
||||||
|
args,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
Ok(KclValue::Sketch {
|
Ok(KclValue::Sketch {
|
||||||
value: Box::new(new_sketch),
|
value: Box::new(new_sketch),
|
||||||
})
|
})
|
||||||
@ -123,8 +136,10 @@ fn involute_curve(radius: f64, angle: f64) -> (f64, f64) {
|
|||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn inner_involute_circular(
|
async fn inner_involute_circular(
|
||||||
sketch: Sketch,
|
sketch: Sketch,
|
||||||
start_radius: TyF64,
|
start_radius: Option<TyF64>,
|
||||||
end_radius: TyF64,
|
end_radius: Option<TyF64>,
|
||||||
|
start_diameter: Option<TyF64>,
|
||||||
|
end_diameter: Option<TyF64>,
|
||||||
angle: TyF64,
|
angle: TyF64,
|
||||||
reverse: Option<bool>,
|
reverse: Option<bool>,
|
||||||
tag: Option<TagNode>,
|
tag: Option<TagNode>,
|
||||||
@ -133,6 +148,22 @@ async fn inner_involute_circular(
|
|||||||
) -> Result<Sketch, KclError> {
|
) -> Result<Sketch, KclError> {
|
||||||
let id = exec_state.next_uuid();
|
let id = exec_state.next_uuid();
|
||||||
|
|
||||||
|
let longer_args_dot_source_range = args.source_range;
|
||||||
|
let start_radius = get_radius_labelled(
|
||||||
|
start_radius,
|
||||||
|
start_diameter,
|
||||||
|
args.source_range,
|
||||||
|
"startRadius",
|
||||||
|
"startDiameter",
|
||||||
|
)?;
|
||||||
|
let end_radius = get_radius_labelled(
|
||||||
|
end_radius,
|
||||||
|
end_diameter,
|
||||||
|
longer_args_dot_source_range,
|
||||||
|
"endRadius",
|
||||||
|
"endDiameter",
|
||||||
|
)?;
|
||||||
|
|
||||||
exec_state
|
exec_state
|
||||||
.batch_modeling_cmd(
|
.batch_modeling_cmd(
|
||||||
ModelingCmdMeta::from_args_id(&args, id),
|
ModelingCmdMeta::from_args_id(&args, id),
|
||||||
|
|||||||
@ -6,7 +6,7 @@ use kittycad_modeling_cmds::{self as kcmc, shared::RelativeTo};
|
|||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use super::{args::TyF64, DEFAULT_TOLERANCE};
|
use super::{args::TyF64, DEFAULT_TOLERANCE_MM};
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::KclError,
|
errors::KclError,
|
||||||
execution::{
|
execution::{
|
||||||
@ -93,7 +93,7 @@ async fn inner_sweep(
|
|||||||
target: sketch.id.into(),
|
target: sketch.id.into(),
|
||||||
trajectory,
|
trajectory,
|
||||||
sectional: sectional.unwrap_or(false),
|
sectional: sectional.unwrap_or(false),
|
||||||
tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)),
|
tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE_MM)),
|
||||||
relative_to,
|
relative_to,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -622,7 +622,7 @@ export fn revolve(
|
|||||||
axis: Axis2d | Edge,
|
axis: Axis2d | Edge,
|
||||||
/// Angle to revolve (in degrees). Default is 360.
|
/// Angle to revolve (in degrees). Default is 360.
|
||||||
angle?: number(Angle),
|
angle?: number(Angle),
|
||||||
/// Tolerance for the revolve operation.
|
/// Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters.
|
||||||
tolerance?: number(Length),
|
tolerance?: number(Length),
|
||||||
/// If true, the extrusion will happen symmetrically around the sketch. Otherwise, the extrusion will happen on only one side of the sketch.
|
/// If true, the extrusion will happen symmetrically around the sketch. Otherwise, the extrusion will happen on only one side of the sketch.
|
||||||
symmetric?: bool,
|
symmetric?: bool,
|
||||||
@ -961,7 +961,7 @@ export fn sweep(
|
|||||||
path: Sketch | Helix,
|
path: Sketch | Helix,
|
||||||
/// If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components.
|
/// If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components.
|
||||||
sectional?: bool,
|
sectional?: bool,
|
||||||
/// Tolerance for this operation.
|
/// Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters.
|
||||||
tolerance?: number(Length),
|
tolerance?: number(Length),
|
||||||
/// What is the sweep relative to? Can be either 'sketchPlane' or 'trajectoryCurve'.
|
/// What is the sweep relative to? Can be either 'sketchPlane' or 'trajectoryCurve'.
|
||||||
relativeTo?: string = 'trajectoryCurve',
|
relativeTo?: string = 'trajectoryCurve',
|
||||||
@ -1047,7 +1047,7 @@ export fn loft(
|
|||||||
bezApproximateRational?: bool = false,
|
bezApproximateRational?: bool = false,
|
||||||
/// This can be set to override the automatically determined topological base curve, which is usually the first section encountered.
|
/// This can be set to override the automatically determined topological base curve, which is usually the first section encountered.
|
||||||
baseCurveIndex?: number(Count),
|
baseCurveIndex?: number(Count),
|
||||||
/// Tolerance for the loft operation.
|
/// Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters.
|
||||||
tolerance?: number(Length),
|
tolerance?: number(Length),
|
||||||
/// A named tag for the face at the start of the loft, i.e. the original sketch.
|
/// A named tag for the face at the start of the loft, i.e. the original sketch.
|
||||||
tagStart?: TagDecl,
|
tagStart?: TagDecl,
|
||||||
@ -1499,12 +1499,20 @@ export fn profileStartY(
|
|||||||
export fn involuteCircular(
|
export fn involuteCircular(
|
||||||
/// Which sketch should this path be added to?
|
/// Which sketch should this path be added to?
|
||||||
@sketch: Sketch,
|
@sketch: Sketch,
|
||||||
/// The involute is described between two circles, start_radius is the radius of the inner circle.
|
|
||||||
startRadius: number(Length),
|
|
||||||
/// The involute is described between two circles, end_radius is the radius of the outer circle.
|
|
||||||
endRadius: number(Length),
|
|
||||||
/// The angle to rotate the involute by. A value of zero will produce a curve with a tangent along the x-axis at the start point of the curve.
|
/// The angle to rotate the involute by. A value of zero will produce a curve with a tangent along the x-axis at the start point of the curve.
|
||||||
angle: number(Angle),
|
angle: number(Angle),
|
||||||
|
/// The involute is described between two circles, startRadius is the radius of the inner circle.
|
||||||
|
/// Either `startRadius` or `startDiameter` must be given (but not both).
|
||||||
|
startRadius?: number(Length),
|
||||||
|
/// The involute is described between two circles, endRadius is the radius of the outer circle.
|
||||||
|
/// Either `endRadius` or `endDiameter` must be given (but not both).
|
||||||
|
endRadius?: number(Length),
|
||||||
|
/// The involute is described between two circles, startDiameter describes the inner circle.
|
||||||
|
/// Either `startRadius` or `startDiameter` must be given (but not both).
|
||||||
|
startDiameter?: number(Length),
|
||||||
|
/// The involute is described between two circles, endDiameter describes the outer circle.
|
||||||
|
/// Either `endRadius` or `endDiameter` must be given (but not both).
|
||||||
|
endDiameter?: number(Length),
|
||||||
/// If reverse is true, the segment will start from the end of the involute, otherwise it will start from that start.
|
/// If reverse is true, the segment will start from the end of the involute, otherwise it will start from that start.
|
||||||
reverse?: bool = false,
|
reverse?: bool = false,
|
||||||
/// Create a new tag which refers to this line.
|
/// Create a new tag which refers to this line.
|
||||||
|
|||||||
@ -70,7 +70,7 @@ export fn fillet(
|
|||||||
radius: number(Length),
|
radius: number(Length),
|
||||||
/// The paths you want to fillet
|
/// The paths you want to fillet
|
||||||
tags: [Edge; 1+],
|
tags: [Edge; 1+],
|
||||||
/// The tolerance for this fillet
|
/// Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters.
|
||||||
tolerance?: number(Length),
|
tolerance?: number(Length),
|
||||||
/// Create a new tag which refers to this fillet
|
/// Create a new tag which refers to this fillet
|
||||||
tag?: TagDecl,
|
tag?: TagDecl,
|
||||||
@ -799,7 +799,7 @@ export fn patternCircular3d(
|
|||||||
export fn union(
|
export fn union(
|
||||||
/// The solids to union.
|
/// The solids to union.
|
||||||
@solids: [Solid; 2+],
|
@solids: [Solid; 2+],
|
||||||
/// The tolerance to use for the union operation.
|
/// Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters.
|
||||||
tolerance?: number(Length),
|
tolerance?: number(Length),
|
||||||
): [Solid; 1+] {}
|
): [Solid; 1+] {}
|
||||||
|
|
||||||
@ -857,7 +857,7 @@ export fn union(
|
|||||||
export fn intersect(
|
export fn intersect(
|
||||||
/// The solids to intersect.
|
/// The solids to intersect.
|
||||||
@solids: [Solid; 2+],
|
@solids: [Solid; 2+],
|
||||||
/// The tolerance to use for the intersection operation.
|
/// Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters.
|
||||||
tolerance?: number(Length),
|
tolerance?: number(Length),
|
||||||
): [Solid; 1+] {}
|
): [Solid; 1+] {}
|
||||||
|
|
||||||
@ -917,7 +917,7 @@ export fn subtract(
|
|||||||
@solids: [Solid; 1+],
|
@solids: [Solid; 1+],
|
||||||
/// The solids to subtract.
|
/// The solids to subtract.
|
||||||
tools: [Solid],
|
tools: [Solid],
|
||||||
/// The tolerance to use for the subtraction operation.
|
/// Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters.
|
||||||
tolerance?: number(Length),
|
tolerance?: number(Length),
|
||||||
): [Solid; 1+] {}
|
): [Solid; 1+] {}
|
||||||
|
|
||||||
|
|||||||
@ -20,37 +20,37 @@ flowchart LR
|
|||||||
subgraph path11 [Path]
|
subgraph path11 [Path]
|
||||||
11["Path<br>[1779, 1849, 0]"]
|
11["Path<br>[1779, 1849, 0]"]
|
||||||
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 11 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 1 }]
|
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 11 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 1 }]
|
||||||
12["Segment<br>[1859, 2025, 0]"]
|
12["Segment<br>[1859, 2021, 0]"]
|
||||||
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 11 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 2 }]
|
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 11 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 2 }]
|
||||||
13["Segment<br>[2035, 2120, 0]"]
|
13["Segment<br>[2031, 2116, 0]"]
|
||||||
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 11 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 3 }]
|
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 11 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 3 }]
|
||||||
14["Segment<br>[2130, 2351, 0]"]
|
14["Segment<br>[2126, 2343, 0]"]
|
||||||
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 11 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 4 }]
|
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 11 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 4 }]
|
||||||
15["Segment<br>[2438, 2524, 0]"]
|
15["Segment<br>[2430, 2516, 0]"]
|
||||||
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 11 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 5 }]
|
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 11 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 5 }]
|
||||||
16["Segment<br>[2813, 2820, 0]"]
|
16["Segment<br>[2805, 2812, 0]"]
|
||||||
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 11 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 7 }]
|
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 11 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 7 }]
|
||||||
17[Solid2d]
|
17[Solid2d]
|
||||||
end
|
end
|
||||||
subgraph path19 [Path]
|
subgraph path19 [Path]
|
||||||
19["Path<br>[1779, 1849, 0]"]
|
19["Path<br>[1779, 1849, 0]"]
|
||||||
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 11 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 1 }]
|
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 11 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 1 }]
|
||||||
20["Segment<br>[1859, 2025, 0]"]
|
20["Segment<br>[1859, 2021, 0]"]
|
||||||
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 11 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 2 }]
|
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 11 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 2 }]
|
||||||
21["Segment<br>[2035, 2120, 0]"]
|
21["Segment<br>[2031, 2116, 0]"]
|
||||||
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 11 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 3 }]
|
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 11 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 3 }]
|
||||||
22["Segment<br>[2130, 2351, 0]"]
|
22["Segment<br>[2126, 2343, 0]"]
|
||||||
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 11 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 4 }]
|
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 11 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 4 }]
|
||||||
23["Segment<br>[2438, 2524, 0]"]
|
23["Segment<br>[2430, 2516, 0]"]
|
||||||
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 11 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 5 }]
|
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 11 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 5 }]
|
||||||
24["Segment<br>[2813, 2820, 0]"]
|
24["Segment<br>[2805, 2812, 0]"]
|
||||||
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 11 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 7 }]
|
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 11 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 7 }]
|
||||||
25[Solid2d]
|
25[Solid2d]
|
||||||
end
|
end
|
||||||
subgraph path27 [Path]
|
subgraph path27 [Path]
|
||||||
27["Path<br>[1779, 1849, 0]"]
|
27["Path<br>[1779, 1849, 0]"]
|
||||||
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 11 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 1 }]
|
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 11 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 1 }]
|
||||||
32["Segment<br>[2813, 2820, 0]"]
|
32["Segment<br>[2805, 2812, 0]"]
|
||||||
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 11 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 7 }]
|
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 11 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 7 }]
|
||||||
33[Solid2d]
|
33[Solid2d]
|
||||||
end
|
end
|
||||||
@ -66,7 +66,7 @@ flowchart LR
|
|||||||
29["SweepEdge Opposite"]
|
29["SweepEdge Opposite"]
|
||||||
30["SweepEdge Opposite"]
|
30["SweepEdge Opposite"]
|
||||||
31["SweepEdge Opposite"]
|
31["SweepEdge Opposite"]
|
||||||
34["Sweep Loft<br>[3337, 3404, 0]"]
|
34["Sweep Loft<br>[3329, 3396, 0]"]
|
||||||
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 15 }, VariableDeclarationDeclaration, VariableDeclarationInit]
|
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 15 }, VariableDeclarationDeclaration, VariableDeclarationInit]
|
||||||
35[Wall]
|
35[Wall]
|
||||||
%% face_code_ref=Missing NodePath
|
%% face_code_ref=Missing NodePath
|
||||||
|
|||||||
@ -1735,45 +1735,25 @@ description: Result of parsing helical-gear.kcl
|
|||||||
"label": {
|
"label": {
|
||||||
"commentStart": 0,
|
"commentStart": 0,
|
||||||
"end": 0,
|
"end": 0,
|
||||||
"name": "startRadius",
|
"name": "startDiameter",
|
||||||
"start": 0,
|
"start": 0,
|
||||||
"type": "Identifier"
|
"type": "Identifier"
|
||||||
},
|
},
|
||||||
"arg": {
|
"arg": {
|
||||||
|
"abs_path": false,
|
||||||
"commentStart": 0,
|
"commentStart": 0,
|
||||||
"end": 0,
|
"end": 0,
|
||||||
"left": {
|
"name": {
|
||||||
"abs_path": false,
|
|
||||||
"commentStart": 0,
|
"commentStart": 0,
|
||||||
"end": 0,
|
"end": 0,
|
||||||
"name": {
|
"name": "baseDiameter",
|
||||||
"commentStart": 0,
|
|
||||||
"end": 0,
|
|
||||||
"name": "baseDiameter",
|
|
||||||
"start": 0,
|
|
||||||
"type": "Identifier"
|
|
||||||
},
|
|
||||||
"path": [],
|
|
||||||
"start": 0,
|
"start": 0,
|
||||||
"type": "Name",
|
"type": "Identifier"
|
||||||
"type": "Name"
|
|
||||||
},
|
|
||||||
"operator": "/",
|
|
||||||
"right": {
|
|
||||||
"commentStart": 0,
|
|
||||||
"end": 0,
|
|
||||||
"raw": "2",
|
|
||||||
"start": 0,
|
|
||||||
"type": "Literal",
|
|
||||||
"type": "Literal",
|
|
||||||
"value": {
|
|
||||||
"value": 2.0,
|
|
||||||
"suffix": "None"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
"path": [],
|
||||||
"start": 0,
|
"start": 0,
|
||||||
"type": "BinaryExpression",
|
"type": "Name",
|
||||||
"type": "BinaryExpression"
|
"type": "Name"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1781,45 +1761,25 @@ description: Result of parsing helical-gear.kcl
|
|||||||
"label": {
|
"label": {
|
||||||
"commentStart": 0,
|
"commentStart": 0,
|
||||||
"end": 0,
|
"end": 0,
|
||||||
"name": "endRadius",
|
"name": "endDiameter",
|
||||||
"start": 0,
|
"start": 0,
|
||||||
"type": "Identifier"
|
"type": "Identifier"
|
||||||
},
|
},
|
||||||
"arg": {
|
"arg": {
|
||||||
|
"abs_path": false,
|
||||||
"commentStart": 0,
|
"commentStart": 0,
|
||||||
"end": 0,
|
"end": 0,
|
||||||
"left": {
|
"name": {
|
||||||
"abs_path": false,
|
|
||||||
"commentStart": 0,
|
"commentStart": 0,
|
||||||
"end": 0,
|
"end": 0,
|
||||||
"name": {
|
"name": "tipDiameter",
|
||||||
"commentStart": 0,
|
|
||||||
"end": 0,
|
|
||||||
"name": "tipDiameter",
|
|
||||||
"start": 0,
|
|
||||||
"type": "Identifier"
|
|
||||||
},
|
|
||||||
"path": [],
|
|
||||||
"start": 0,
|
"start": 0,
|
||||||
"type": "Name",
|
"type": "Identifier"
|
||||||
"type": "Name"
|
|
||||||
},
|
|
||||||
"operator": "/",
|
|
||||||
"right": {
|
|
||||||
"commentStart": 0,
|
|
||||||
"end": 0,
|
|
||||||
"raw": "2",
|
|
||||||
"start": 0,
|
|
||||||
"type": "Literal",
|
|
||||||
"type": "Literal",
|
|
||||||
"value": {
|
|
||||||
"value": 2.0,
|
|
||||||
"suffix": "None"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
"path": [],
|
||||||
"start": 0,
|
"start": 0,
|
||||||
"type": "BinaryExpression",
|
"type": "Name",
|
||||||
"type": "BinaryExpression"
|
"type": "Name"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -2072,45 +2032,25 @@ description: Result of parsing helical-gear.kcl
|
|||||||
"label": {
|
"label": {
|
||||||
"commentStart": 0,
|
"commentStart": 0,
|
||||||
"end": 0,
|
"end": 0,
|
||||||
"name": "startRadius",
|
"name": "startDiameter",
|
||||||
"start": 0,
|
"start": 0,
|
||||||
"type": "Identifier"
|
"type": "Identifier"
|
||||||
},
|
},
|
||||||
"arg": {
|
"arg": {
|
||||||
|
"abs_path": false,
|
||||||
"commentStart": 0,
|
"commentStart": 0,
|
||||||
"end": 0,
|
"end": 0,
|
||||||
"left": {
|
"name": {
|
||||||
"abs_path": false,
|
|
||||||
"commentStart": 0,
|
"commentStart": 0,
|
||||||
"end": 0,
|
"end": 0,
|
||||||
"name": {
|
"name": "baseDiameter",
|
||||||
"commentStart": 0,
|
|
||||||
"end": 0,
|
|
||||||
"name": "baseDiameter",
|
|
||||||
"start": 0,
|
|
||||||
"type": "Identifier"
|
|
||||||
},
|
|
||||||
"path": [],
|
|
||||||
"start": 0,
|
"start": 0,
|
||||||
"type": "Name",
|
"type": "Identifier"
|
||||||
"type": "Name"
|
|
||||||
},
|
|
||||||
"operator": "/",
|
|
||||||
"right": {
|
|
||||||
"commentStart": 0,
|
|
||||||
"end": 0,
|
|
||||||
"raw": "2",
|
|
||||||
"start": 0,
|
|
||||||
"type": "Literal",
|
|
||||||
"type": "Literal",
|
|
||||||
"value": {
|
|
||||||
"value": 2.0,
|
|
||||||
"suffix": "None"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
"path": [],
|
||||||
"start": 0,
|
"start": 0,
|
||||||
"type": "BinaryExpression",
|
"type": "Name",
|
||||||
"type": "BinaryExpression"
|
"type": "Name"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -2118,45 +2058,25 @@ description: Result of parsing helical-gear.kcl
|
|||||||
"label": {
|
"label": {
|
||||||
"commentStart": 0,
|
"commentStart": 0,
|
||||||
"end": 0,
|
"end": 0,
|
||||||
"name": "endRadius",
|
"name": "endDiameter",
|
||||||
"start": 0,
|
"start": 0,
|
||||||
"type": "Identifier"
|
"type": "Identifier"
|
||||||
},
|
},
|
||||||
"arg": {
|
"arg": {
|
||||||
|
"abs_path": false,
|
||||||
"commentStart": 0,
|
"commentStart": 0,
|
||||||
"end": 0,
|
"end": 0,
|
||||||
"left": {
|
"name": {
|
||||||
"abs_path": false,
|
|
||||||
"commentStart": 0,
|
"commentStart": 0,
|
||||||
"end": 0,
|
"end": 0,
|
||||||
"name": {
|
"name": "tipDiameter",
|
||||||
"commentStart": 0,
|
|
||||||
"end": 0,
|
|
||||||
"name": "tipDiameter",
|
|
||||||
"start": 0,
|
|
||||||
"type": "Identifier"
|
|
||||||
},
|
|
||||||
"path": [],
|
|
||||||
"start": 0,
|
"start": 0,
|
||||||
"type": "Name",
|
"type": "Identifier"
|
||||||
"type": "Name"
|
|
||||||
},
|
|
||||||
"operator": "/",
|
|
||||||
"right": {
|
|
||||||
"commentStart": 0,
|
|
||||||
"end": 0,
|
|
||||||
"raw": "2",
|
|
||||||
"start": 0,
|
|
||||||
"type": "Literal",
|
|
||||||
"type": "Literal",
|
|
||||||
"value": {
|
|
||||||
"value": 2.0,
|
|
||||||
"suffix": "None"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
"path": [],
|
||||||
"start": 0,
|
"start": 0,
|
||||||
"type": "BinaryExpression",
|
"type": "Name",
|
||||||
"type": "BinaryExpression"
|
"type": "Name"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
source: kcl-lib/src/simulation_tests.rs
|
||||||
|
description: Artifact commands non_english_identifiers.kcl
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"rust/kcl-lib/tests/non_english_identifiers/input.kcl": [],
|
||||||
|
"std::appearance": [],
|
||||||
|
"std::array": [],
|
||||||
|
"std::math": [],
|
||||||
|
"std::prelude": [],
|
||||||
|
"std::sketch": [],
|
||||||
|
"std::solid": [],
|
||||||
|
"std::sweep": [],
|
||||||
|
"std::transform": [],
|
||||||
|
"std::turns": [],
|
||||||
|
"std::types": [],
|
||||||
|
"std::units": []
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
source: kcl-lib/src/simulation_tests.rs
|
||||||
|
description: Artifact graph flowchart non_english_identifiers.kcl
|
||||||
|
extension: md
|
||||||
|
snapshot_kind: binary
|
||||||
|
---
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
```
|
||||||
284
rust/kcl-lib/tests/non_english_identifiers/ast.snap
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
---
|
||||||
|
source: kcl-lib/src/simulation_tests.rs
|
||||||
|
description: Result of parsing non_english_identifiers.kcl
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"Ok": {
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"declaration": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"id": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "comprimentoTotal",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"raw": "100",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": {
|
||||||
|
"value": 100.0,
|
||||||
|
"suffix": "None"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"start": 0,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 0,
|
||||||
|
"kind": "const",
|
||||||
|
"start": 0,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"declaration": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"id": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "亞當",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"raw": "100",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": {
|
||||||
|
"value": 100.0,
|
||||||
|
"suffix": "None"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"start": 0,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 0,
|
||||||
|
"kind": "const",
|
||||||
|
"start": 0,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"declaration": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"id": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "comprimentoRosca",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"left": {
|
||||||
|
"abs_path": false,
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "亞當",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"path": [],
|
||||||
|
"start": 0,
|
||||||
|
"type": "Name",
|
||||||
|
"type": "Name"
|
||||||
|
},
|
||||||
|
"operator": "*",
|
||||||
|
"right": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"raw": "0.8",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": {
|
||||||
|
"value": 0.8,
|
||||||
|
"suffix": "None"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"start": 0,
|
||||||
|
"type": "BinaryExpression",
|
||||||
|
"type": "BinaryExpression"
|
||||||
|
},
|
||||||
|
"start": 0,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 0,
|
||||||
|
"kind": "const",
|
||||||
|
"start": 0,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"declaration": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"id": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "comprimentoCabeça",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"left": {
|
||||||
|
"abs_path": false,
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "comprimentoTotal",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"path": [],
|
||||||
|
"start": 0,
|
||||||
|
"type": "Name",
|
||||||
|
"type": "Name"
|
||||||
|
},
|
||||||
|
"operator": "-",
|
||||||
|
"right": {
|
||||||
|
"abs_path": false,
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "comprimentoRosca",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"path": [],
|
||||||
|
"start": 0,
|
||||||
|
"type": "Name",
|
||||||
|
"type": "Name"
|
||||||
|
},
|
||||||
|
"start": 0,
|
||||||
|
"type": "BinaryExpression",
|
||||||
|
"type": "BinaryExpression"
|
||||||
|
},
|
||||||
|
"start": 0,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 0,
|
||||||
|
"kind": "const",
|
||||||
|
"start": 0,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"expression": {
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"type": "LabeledArg",
|
||||||
|
"label": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "isEqualTo",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"arg": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"raw": "20",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": {
|
||||||
|
"value": 20.0,
|
||||||
|
"suffix": "None"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"abs_path": false,
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "assert",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"path": [],
|
||||||
|
"start": 0,
|
||||||
|
"type": "Name"
|
||||||
|
},
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "CallExpressionKw",
|
||||||
|
"type": "CallExpressionKw",
|
||||||
|
"unlabeled": {
|
||||||
|
"abs_path": false,
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "comprimentoCabeça",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"path": [],
|
||||||
|
"start": 0,
|
||||||
|
"type": "Name",
|
||||||
|
"type": "Name"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"start": 0,
|
||||||
|
"type": "ExpressionStatement",
|
||||||
|
"type": "ExpressionStatement"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"nonCodeMeta": {
|
||||||
|
"nonCodeNodes": {
|
||||||
|
"2": [
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "NonCodeNode",
|
||||||
|
"value": {
|
||||||
|
"type": "inlineComment",
|
||||||
|
"value": "80% do comprimento total é roscado",
|
||||||
|
"style": "line"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"startNodes": []
|
||||||
|
},
|
||||||
|
"start": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
5
rust/kcl-lib/tests/non_english_identifiers/input.kcl
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
comprimentoTotal = 100
|
||||||
|
亞當 = 100
|
||||||
|
comprimentoRosca = 亞當 * 0.8 // 80% do comprimento total é roscado
|
||||||
|
comprimentoCabeça = comprimentoTotal - comprimentoRosca
|
||||||
|
assert(comprimentoCabeça, isEqualTo = 20)
|
||||||
229
rust/kcl-lib/tests/non_english_identifiers/ops.snap
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
---
|
||||||
|
source: kcl-lib/src/simulation_tests.rs
|
||||||
|
description: Operations executed non_english_identifiers.kcl
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"rust/kcl-lib/tests/non_english_identifiers/input.kcl": [
|
||||||
|
{
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"name": "comprimentoTotal",
|
||||||
|
"value": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 100.0,
|
||||||
|
"ty": {
|
||||||
|
"type": "Default",
|
||||||
|
"len": {
|
||||||
|
"type": "Mm"
|
||||||
|
},
|
||||||
|
"angle": {
|
||||||
|
"type": "Degrees"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visibility": "default",
|
||||||
|
"nodePath": {
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "ProgramBodyItem",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "VariableDeclarationDeclaration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "VariableDeclarationInit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sourceRange": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"name": "亞當",
|
||||||
|
"value": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 100.0,
|
||||||
|
"ty": {
|
||||||
|
"type": "Default",
|
||||||
|
"len": {
|
||||||
|
"type": "Mm"
|
||||||
|
},
|
||||||
|
"angle": {
|
||||||
|
"type": "Degrees"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visibility": "default",
|
||||||
|
"nodePath": {
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "ProgramBodyItem",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "VariableDeclarationDeclaration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "VariableDeclarationInit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sourceRange": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"name": "comprimentoRosca",
|
||||||
|
"value": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 80.0,
|
||||||
|
"ty": {
|
||||||
|
"type": "Default",
|
||||||
|
"len": {
|
||||||
|
"type": "Mm"
|
||||||
|
},
|
||||||
|
"angle": {
|
||||||
|
"type": "Degrees"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visibility": "default",
|
||||||
|
"nodePath": {
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "ProgramBodyItem",
|
||||||
|
"index": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "VariableDeclarationDeclaration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "VariableDeclarationInit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sourceRange": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"name": "comprimentoCabeça",
|
||||||
|
"value": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 20.0,
|
||||||
|
"ty": {
|
||||||
|
"type": "Default",
|
||||||
|
"len": {
|
||||||
|
"type": "Mm"
|
||||||
|
},
|
||||||
|
"angle": {
|
||||||
|
"type": "Degrees"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visibility": "default",
|
||||||
|
"nodePath": {
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "ProgramBodyItem",
|
||||||
|
"index": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "VariableDeclarationDeclaration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "VariableDeclarationInit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sourceRange": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"std::appearance": [],
|
||||||
|
"std::array": [],
|
||||||
|
"std::math": [
|
||||||
|
{
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"name": "PI",
|
||||||
|
"value": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 3.141592653589793,
|
||||||
|
"ty": {
|
||||||
|
"type": "Unknown"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visibility": "export",
|
||||||
|
"nodePath": {
|
||||||
|
"steps": []
|
||||||
|
},
|
||||||
|
"sourceRange": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"name": "E",
|
||||||
|
"value": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 2.718281828459045,
|
||||||
|
"ty": {
|
||||||
|
"type": "Known",
|
||||||
|
"type": "Count"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visibility": "export",
|
||||||
|
"nodePath": {
|
||||||
|
"steps": []
|
||||||
|
},
|
||||||
|
"sourceRange": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"name": "TAU",
|
||||||
|
"value": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 6.283185307179586,
|
||||||
|
"ty": {
|
||||||
|
"type": "Known",
|
||||||
|
"type": "Count"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visibility": "export",
|
||||||
|
"nodePath": {
|
||||||
|
"steps": []
|
||||||
|
},
|
||||||
|
"sourceRange": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"std::prelude": [
|
||||||
|
{
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"name": "START",
|
||||||
|
"value": {
|
||||||
|
"type": "String",
|
||||||
|
"value": "start"
|
||||||
|
},
|
||||||
|
"visibility": "export",
|
||||||
|
"nodePath": {
|
||||||
|
"steps": []
|
||||||
|
},
|
||||||
|
"sourceRange": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"name": "END",
|
||||||
|
"value": {
|
||||||
|
"type": "String",
|
||||||
|
"value": "end"
|
||||||
|
},
|
||||||
|
"visibility": "export",
|
||||||
|
"nodePath": {
|
||||||
|
"steps": []
|
||||||
|
},
|
||||||
|
"sourceRange": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"std::sketch": [],
|
||||||
|
"std::solid": [],
|
||||||
|
"std::sweep": [],
|
||||||
|
"std::transform": [],
|
||||||
|
"std::turns": [],
|
||||||
|
"std::types": [],
|
||||||
|
"std::units": []
|
||||||
|
}
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
---
|
||||||
|
source: kcl-lib/src/simulation_tests.rs
|
||||||
|
description: Variables in memory after executing non_english_identifiers.kcl
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"comprimentoCabeça": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 20.0,
|
||||||
|
"ty": {
|
||||||
|
"type": "Default",
|
||||||
|
"len": {
|
||||||
|
"type": "Mm"
|
||||||
|
},
|
||||||
|
"angle": {
|
||||||
|
"type": "Degrees"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"comprimentoRosca": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 80.0,
|
||||||
|
"ty": {
|
||||||
|
"type": "Default",
|
||||||
|
"len": {
|
||||||
|
"type": "Mm"
|
||||||
|
},
|
||||||
|
"angle": {
|
||||||
|
"type": "Degrees"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"comprimentoTotal": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 100.0,
|
||||||
|
"ty": {
|
||||||
|
"type": "Default",
|
||||||
|
"len": {
|
||||||
|
"type": "Mm"
|
||||||
|
},
|
||||||
|
"angle": {
|
||||||
|
"type": "Degrees"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"亞當": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 100.0,
|
||||||
|
"ty": {
|
||||||
|
"type": "Default",
|
||||||
|
"len": {
|
||||||
|
"type": "Mm"
|
||||||
|
},
|
||||||
|
"angle": {
|
||||||
|
"type": "Degrees"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
rust/kcl-lib/tests/non_english_identifiers/rendered_model.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
9
rust/kcl-lib/tests/non_english_identifiers/unparsed.snap
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
source: kcl-lib/src/simulation_tests.rs
|
||||||
|
description: Result of unparsing non_english_identifiers.kcl
|
||||||
|
---
|
||||||
|
comprimentoTotal = 100
|
||||||
|
亞當 = 100
|
||||||
|
comprimentoRosca = 亞當 * 0.8 // 80% do comprimento total é roscado
|
||||||
|
comprimentoCabeça = comprimentoTotal - comprimentoRosca
|
||||||
|
assert(comprimentoCabeça, isEqualTo = 20)
|
||||||
13
src/App.tsx
@ -28,6 +28,7 @@ import {
|
|||||||
codeManager,
|
codeManager,
|
||||||
kclManager,
|
kclManager,
|
||||||
settingsActor,
|
settingsActor,
|
||||||
|
editorManager,
|
||||||
getSettings,
|
getSettings,
|
||||||
} from '@src/lib/singletons'
|
} from '@src/lib/singletons'
|
||||||
import { maybeWriteToDisk } from '@src/lib/telemetry'
|
import { maybeWriteToDisk } from '@src/lib/telemetry'
|
||||||
@ -107,6 +108,16 @@ export function App() {
|
|||||||
useHotkeys('backspace', (e) => {
|
useHotkeys('backspace', (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
})
|
})
|
||||||
|
// Since these already exist in the editor, we don't need to define them
|
||||||
|
// with the wrapper.
|
||||||
|
useHotkeys('mod+z', (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
editorManager.undo()
|
||||||
|
})
|
||||||
|
useHotkeys('mod+shift+z', (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
editorManager.redo()
|
||||||
|
})
|
||||||
useHotkeyWrapper(
|
useHotkeyWrapper(
|
||||||
[isDesktop() ? 'mod + ,' : 'shift + mod + ,'],
|
[isDesktop() ? 'mod + ,' : 'shift + mod + ,'],
|
||||||
() => navigate(filePath + PATHS.SETTINGS),
|
() => navigate(filePath + PATHS.SETTINGS),
|
||||||
@ -256,7 +267,7 @@ export function App() {
|
|||||||
<StatusBar
|
<StatusBar
|
||||||
globalItems={[
|
globalItems={[
|
||||||
networkHealthStatus,
|
networkHealthStatus,
|
||||||
networkMachineStatus,
|
...(isDesktop() ? [networkMachineStatus] : []),
|
||||||
...defaultGlobalStatusBarItems({ location, filePath }),
|
...defaultGlobalStatusBarItems({ location, filePath }),
|
||||||
]}
|
]}
|
||||||
localItems={[
|
localItems={[
|
||||||
|
|||||||
@ -88,14 +88,14 @@ export function Toolbar({
|
|||||||
modelingState: state,
|
modelingState: state,
|
||||||
modelingSend: send,
|
modelingSend: send,
|
||||||
sketchPathId,
|
sketchPathId,
|
||||||
editorHasFocus: editorManager.editorView?.hasFocus,
|
editorHasFocus: editorManager.getEditorView()?.hasFocus,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
state,
|
state,
|
||||||
send,
|
send,
|
||||||
commandBarActor.send,
|
commandBarActor.send,
|
||||||
sketchPathId,
|
sketchPathId,
|
||||||
editorManager.editorView?.hasFocus,
|
editorManager.getEditorView()?.hasFocus,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -4078,7 +4078,7 @@ function isGroupStartProfileForCurrentProfile(sketchEntryNodePath: PathToNode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the 2D tangent direction vector at the end of the segmentGroup if it's an arc.
|
// Returns the 2D tangent direction vector at the end of the segmentGroup
|
||||||
function findTangentDirection(segmentGroup: Group) {
|
function findTangentDirection(segmentGroup: Group) {
|
||||||
let tangentDirection: Coords2d | undefined
|
let tangentDirection: Coords2d | undefined
|
||||||
if (segmentGroup.userData.type === TANGENTIAL_ARC_TO_SEGMENT) {
|
if (segmentGroup.userData.type === TANGENTIAL_ARC_TO_SEGMENT) {
|
||||||
@ -4107,11 +4107,6 @@ function findTangentDirection(segmentGroup: Group) {
|
|||||||
const from = segmentGroup.userData.from as Coords2d
|
const from = segmentGroup.userData.from as Coords2d
|
||||||
tangentDirection = subVec(to, from)
|
tangentDirection = subVec(to, from)
|
||||||
tangentDirection = normalizeVec(tangentDirection)
|
tangentDirection = normalizeVec(tangentDirection)
|
||||||
} else {
|
|
||||||
console.warn(
|
|
||||||
'Unsupported segment type for tangent direction calculation: ',
|
|
||||||
segmentGroup.userData.type
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return tangentDirection
|
return tangentDirection
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,7 @@ export const CommandBar = () => {
|
|||||||
const commandBarState = useCommandBarState()
|
const commandBarState = useCommandBarState()
|
||||||
const { immediateState } = useNetworkContext()
|
const { immediateState } = useNetworkContext()
|
||||||
const {
|
const {
|
||||||
context: { selectedCommand, currentArgument, commands },
|
context: { selectedCommand, currentArgument, commands, argumentsToSubmit },
|
||||||
} = commandBarState
|
} = commandBarState
|
||||||
const isArgumentThatShouldBeHardToDismiss =
|
const isArgumentThatShouldBeHardToDismiss =
|
||||||
currentArgument?.inputType === 'selection' ||
|
currentArgument?.inputType === 'selection' ||
|
||||||
@ -68,16 +68,23 @@ export const CommandBar = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
function stepBack() {
|
function stepBack() {
|
||||||
|
const entries = Object.entries(selectedCommand?.args || {}).filter(
|
||||||
|
([argName, arg]) => {
|
||||||
|
const argValue =
|
||||||
|
(typeof argumentsToSubmit[argName] === 'function'
|
||||||
|
? argumentsToSubmit[argName](commandBarState.context)
|
||||||
|
: argumentsToSubmit[argName]) || ''
|
||||||
|
const isRequired =
|
||||||
|
typeof arg.required === 'function'
|
||||||
|
? arg.required(commandBarState.context)
|
||||||
|
: arg.required
|
||||||
|
|
||||||
|
return !arg.hidden && (argValue || isRequired)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if (!currentArgument) {
|
if (!currentArgument) {
|
||||||
if (commandBarState.matches('Review')) {
|
if (commandBarState.matches('Review')) {
|
||||||
const entries = Object.entries(selectedCommand?.args || {}).filter(
|
|
||||||
([_, argConfig]) =>
|
|
||||||
!argConfig.hidden &&
|
|
||||||
(typeof argConfig.required === 'function'
|
|
||||||
? argConfig.required(commandBarState.context)
|
|
||||||
: argConfig.required)
|
|
||||||
)
|
|
||||||
|
|
||||||
const currentArgName = entries[entries.length - 1][0]
|
const currentArgName = entries[entries.length - 1][0]
|
||||||
const currentArg = {
|
const currentArg = {
|
||||||
name: currentArgName,
|
name: currentArgName,
|
||||||
@ -94,9 +101,6 @@ export const CommandBar = () => {
|
|||||||
commandBarActor.send({ type: 'Deselect command' })
|
commandBarActor.send({ type: 'Deselect command' })
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const entries = Object.entries(selectedCommand?.args || {}).filter(
|
|
||||||
(a) => !a[1].hidden
|
|
||||||
)
|
|
||||||
const index = entries.findIndex(
|
const index = entries.findIndex(
|
||||||
([key, _]) => key === currentArgument.name
|
([key, _]) => key === currentArgument.name
|
||||||
)
|
)
|
||||||
@ -183,20 +187,6 @@ export const CommandBar = () => {
|
|||||||
<kbd className="hotkey ml-4 dark:!bg-chalkboard-80">esc</kbd>
|
<kbd className="hotkey ml-4 dark:!bg-chalkboard-80">esc</kbd>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</button>
|
</button>
|
||||||
{!commandBarState.matches('Selecting command') && (
|
|
||||||
<button onClick={stepBack} className="m-0 p-0 border-none">
|
|
||||||
<CustomIcon name="arrowLeft" className="w-5 h-5 rounded-sm" />
|
|
||||||
<Tooltip position="bottom">
|
|
||||||
Step back{' '}
|
|
||||||
<kbd className="hotkey ml-4 dark:!bg-chalkboard-80">
|
|
||||||
Shift
|
|
||||||
</kbd>
|
|
||||||
<kbd className="hotkey ml-4 dark:!bg-chalkboard-80">
|
|
||||||
Bksp
|
|
||||||
</kbd>
|
|
||||||
</Tooltip>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</WrapperComponent.Panel>
|
</WrapperComponent.Panel>
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import CommandArgOptionInput from '@src/components/CommandBar/CommandArgOptionInput'
|
import CommandArgOptionInput from '@src/components/CommandBar/CommandArgOptionInput'
|
||||||
import CommandBarBasicInput from '@src/components/CommandBar/CommandBarBasicInput'
|
import CommandBarBasicInput from '@src/components/CommandBar/CommandBarBasicInput'
|
||||||
import CommandBarHeader from '@src/components/CommandBar/CommandBarHeader'
|
import CommandBarHeaderFooter from '@src/components/CommandBar/CommandBarHeaderFooter'
|
||||||
import CommandBarKclInput from '@src/components/CommandBar/CommandBarKclInput'
|
import CommandBarKclInput from '@src/components/CommandBar/CommandBarKclInput'
|
||||||
import CommandBarPathInput from '@src/components/CommandBar/CommandBarPathInput'
|
import CommandBarPathInput from '@src/components/CommandBar/CommandBarPathInput'
|
||||||
import CommandBarSelectionInput from '@src/components/CommandBar/CommandBarSelectionInput'
|
import CommandBarSelectionInput from '@src/components/CommandBar/CommandBarSelectionInput'
|
||||||
import CommandBarSelectionMixedInput from '@src/components/CommandBar/CommandBarSelectionMixedInput'
|
import CommandBarSelectionMixedInput from '@src/components/CommandBar/CommandBarSelectionMixedInput'
|
||||||
import CommandBarTextareaInput from '@src/components/CommandBar/CommandBarTextareaInput'
|
import CommandBarTextareaInput from '@src/components/CommandBar/CommandBarTextareaInput'
|
||||||
|
import CommandBarDivider from '@src/components/CommandBar/CommandBarDivider'
|
||||||
import type { CommandArgument } from '@src/lib/commandTypes'
|
import type { CommandArgument } from '@src/lib/commandTypes'
|
||||||
import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
|
import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
|
||||||
|
|
||||||
@ -28,13 +29,14 @@ function CommandBarArgument({ stepBack }: { stepBack: () => void }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
currentArgument && (
|
currentArgument && (
|
||||||
<CommandBarHeader>
|
<CommandBarHeaderFooter stepBack={stepBack}>
|
||||||
<ArgumentInput
|
<ArgumentInput
|
||||||
arg={currentArgument}
|
arg={currentArgument}
|
||||||
stepBack={stepBack}
|
stepBack={stepBack}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
/>
|
/>
|
||||||
</CommandBarHeader>
|
<CommandBarDivider />
|
||||||
|
</CommandBarHeaderFooter>
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
5
src/components/CommandBar/CommandBarDivider.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export default function CommandBarDivider() {
|
||||||
|
return (
|
||||||
|
<div className="block w-full my-2 h-[1px] bg-chalkboard-20 dark:bg-chalkboard-80" />
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -5,6 +5,7 @@ import { useHotkeys } from 'react-hotkeys-hook'
|
|||||||
import { ActionButton } from '@src/components/ActionButton'
|
import { ActionButton } from '@src/components/ActionButton'
|
||||||
import { CustomIcon } from '@src/components/CustomIcon'
|
import { CustomIcon } from '@src/components/CustomIcon'
|
||||||
import Tooltip from '@src/components/Tooltip'
|
import Tooltip from '@src/components/Tooltip'
|
||||||
|
import CommandBarDivider from '@src/components/CommandBar/CommandBarDivider'
|
||||||
import type {
|
import type {
|
||||||
KclCommandValue,
|
KclCommandValue,
|
||||||
KclExpressionWithVariable,
|
KclExpressionWithVariable,
|
||||||
@ -14,7 +15,10 @@ import { getSelectionTypeDisplayText } from '@src/lib/selections'
|
|||||||
import { roundOff } from '@src/lib/utils'
|
import { roundOff } from '@src/lib/utils'
|
||||||
import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
|
import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
|
||||||
|
|
||||||
function CommandBarHeader({ children }: React.PropsWithChildren<object>) {
|
function CommandBarHeaderFooter({
|
||||||
|
children,
|
||||||
|
stepBack,
|
||||||
|
}: React.PropsWithChildren<object> & { stepBack: () => void }) {
|
||||||
const commandBarState = useCommandBarState()
|
const commandBarState = useCommandBarState()
|
||||||
const {
|
const {
|
||||||
context: { selectedCommand, currentArgument, argumentsToSubmit },
|
context: { selectedCommand, currentArgument, argumentsToSubmit },
|
||||||
@ -102,19 +106,23 @@ function CommandBarHeader({ children }: React.PropsWithChildren<object>) {
|
|||||||
<span className="pr-2" />
|
<span className="pr-2" />
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
{Object.entries(nonHiddenArgs || {})
|
{Object.entries(nonHiddenArgs || {}).flatMap(
|
||||||
.filter(
|
([argName, arg], i) => {
|
||||||
([_, argConfig]) =>
|
|
||||||
argConfig.skip === false ||
|
|
||||||
(typeof argConfig.required === 'function'
|
|
||||||
? argConfig.required(commandBarState.context)
|
|
||||||
: argConfig.required)
|
|
||||||
)
|
|
||||||
.map(([argName, arg], i) => {
|
|
||||||
const argValue =
|
const argValue =
|
||||||
(typeof argumentsToSubmit[argName] === 'function'
|
(typeof argumentsToSubmit[argName] === 'function'
|
||||||
? argumentsToSubmit[argName](commandBarState.context)
|
? argumentsToSubmit[argName](commandBarState.context)
|
||||||
: argumentsToSubmit[argName]) || ''
|
: argumentsToSubmit[argName]) || ''
|
||||||
|
const isCurrentArg = argName === currentArgument?.name
|
||||||
|
const isSkipFalse = arg.skip === false
|
||||||
|
const isRequired =
|
||||||
|
typeof arg.required === 'function'
|
||||||
|
? arg.required(commandBarState.context)
|
||||||
|
: arg.required
|
||||||
|
|
||||||
|
// We actually want to show non-hidden optional args that have a value set already
|
||||||
|
if (!(argValue || isCurrentArg || isSkipFalse || isRequired)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
@ -161,7 +169,9 @@ function CommandBarHeader({ children }: React.PropsWithChildren<object>) {
|
|||||||
),
|
),
|
||||||
4
|
4
|
||||||
)
|
)
|
||||||
) : arg.inputType === 'text' && !arg.valueSummary ? (
|
) : arg.inputType === 'text' &&
|
||||||
|
!arg.valueSummary &&
|
||||||
|
typeof argValue === 'string' ? (
|
||||||
`${argValue.slice(0, 12)}${argValue.length > 12 ? '...' : ''}`
|
`${argValue.slice(0, 12)}${argValue.length > 12 ? '...' : ''}`
|
||||||
) : typeof argValue === 'object' ? (
|
) : typeof argValue === 'object' ? (
|
||||||
arg.valueSummary ? (
|
arg.valueSummary ? (
|
||||||
@ -207,8 +217,14 @@ function CommandBarHeader({ children }: React.PropsWithChildren<object>) {
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
})}
|
}
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<CommandBarDivider />
|
||||||
|
{children}
|
||||||
|
<div className="px-4 pb-2 flex justify-between items-center gap-2">
|
||||||
|
<StepBackButton stepBack={stepBack} />
|
||||||
{isReviewing ? (
|
{isReviewing ? (
|
||||||
<ReviewingButton
|
<ReviewingButton
|
||||||
bgClassName={
|
bgClassName={
|
||||||
@ -237,8 +253,6 @@ function CommandBarHeader({ children }: React.PropsWithChildren<object>) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="block w-full my-2 h-[1px] bg-chalkboard-20 dark:bg-chalkboard-80" />
|
|
||||||
{children}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -258,16 +272,16 @@ function ReviewingButton({ bgClassName, iconClassName }: ButtonProps) {
|
|||||||
ref={buttonRef}
|
ref={buttonRef}
|
||||||
type="submit"
|
type="submit"
|
||||||
form="review-form"
|
form="review-form"
|
||||||
className="w-fit !p-0 rounded-sm hover:shadow focus:outline-current"
|
className={`w-fit !p-0 rounded-sm hover:brightness-110 hover:shadow focus:outline-current ${bgClassName}`}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
data-testid="command-bar-submit"
|
data-testid="command-bar-submit"
|
||||||
iconStart={{
|
iconEnd={{
|
||||||
icon: 'checkmark',
|
icon: 'checkmark',
|
||||||
bgClassName: `p-1 rounded-sm hover:brightness-110 ${bgClassName}`,
|
bgClassName: `p-1 rounded-sm ${bgClassName}`,
|
||||||
iconClassName: `${iconClassName}`,
|
iconClassName: `${iconClassName}`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="sr-only">Submit command</span>
|
<span className={`pl-2 ${iconClassName}`}>Submit</span>
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -278,18 +292,48 @@ function GatheringArgsButton({ bgClassName, iconClassName }: ButtonProps) {
|
|||||||
Element="button"
|
Element="button"
|
||||||
type="submit"
|
type="submit"
|
||||||
form="arg-form"
|
form="arg-form"
|
||||||
className="w-fit !p-0 rounded-sm hover:shadow focus:outline-current"
|
className={`w-fit !p-0 rounded-sm hover:brightness-110 hover:shadow focus:outline-current ${bgClassName}`}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
data-testid="command-bar-continue"
|
data-testid="command-bar-continue"
|
||||||
iconStart={{
|
iconEnd={{
|
||||||
icon: 'arrowRight',
|
icon: 'arrowRight',
|
||||||
bgClassName: `p-1 rounded-sm hover:brightness-110 ${bgClassName}`,
|
bgClassName: `p-1 rounded-sm ${bgClassName}`,
|
||||||
iconClassName: `${iconClassName}`,
|
iconClassName: `${iconClassName}`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="sr-only">Continue</span>
|
<span className={`pl-2 ${iconClassName}`}>Continue</span>
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CommandBarHeader
|
function StepBackButton({
|
||||||
|
bgClassName,
|
||||||
|
iconClassName,
|
||||||
|
stepBack,
|
||||||
|
}: ButtonProps & { stepBack: () => void }) {
|
||||||
|
return (
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
type="button"
|
||||||
|
form="arg-form"
|
||||||
|
className={`w-fit !p-0 rounded-sm hover:brightness-110 hover:shadow focus:outline-current bg-chalkboard-20/50 dark:bg-chalkboard-80/50 border-chalkboard-20 dark:border-chalkboard-80 ${bgClassName}`}
|
||||||
|
tabIndex={0}
|
||||||
|
data-testid="command-bar-step-back"
|
||||||
|
iconStart={{
|
||||||
|
icon: 'arrowLeft',
|
||||||
|
bgClassName: `p-1 rounded-sm bg-chalkboard-20/50 dark:bg-chalkboard-80/50 ${bgClassName}`,
|
||||||
|
iconClassName: `${iconClassName}`,
|
||||||
|
}}
|
||||||
|
onClick={stepBack}
|
||||||
|
>
|
||||||
|
<span className={`pr-2 ${iconClassName}`}>Step back</span>
|
||||||
|
<Tooltip position="bottom">
|
||||||
|
Step back
|
||||||
|
<kbd className="hotkey ml-4 dark:!bg-chalkboard-80">Shift</kbd>
|
||||||
|
<kbd className="hotkey ml-2 dark:!bg-chalkboard-80">Bksp</kbd>
|
||||||
|
</Tooltip>
|
||||||
|
</ActionButton>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CommandBarHeaderFooter
|
||||||
@ -332,65 +332,67 @@ function CommandBarKclInput({
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="flex items-baseline gap-4 mx-4">
|
{arg.createVariable !== 'disallow' && (
|
||||||
<input
|
<div className="flex items-baseline gap-4 mx-4">
|
||||||
type="checkbox"
|
<input
|
||||||
id="variable-checkbox"
|
type="checkbox"
|
||||||
data-testid="cmd-bar-variable-checkbox"
|
id="variable-checkbox"
|
||||||
checked={createNewVariable}
|
data-testid="cmd-bar-variable-checkbox"
|
||||||
onChange={(e) => {
|
checked={createNewVariable}
|
||||||
setCreateNewVariable(e.target.checked)
|
onChange={(e) => {
|
||||||
}}
|
setCreateNewVariable(e.target.checked)
|
||||||
className="bg-chalkboard-10 dark:bg-chalkboard-80"
|
}}
|
||||||
/>
|
className="bg-chalkboard-10 dark:bg-chalkboard-80"
|
||||||
<label
|
/>
|
||||||
htmlFor="variable-checkbox"
|
<label
|
||||||
className="text-blue border-none bg-transparent font-sm flex gap-1 items-center pl-0 pr-1"
|
htmlFor="variable-checkbox"
|
||||||
>
|
className="text-blue border-none bg-transparent font-sm flex gap-1 items-center pl-0 pr-1"
|
||||||
Create new variable
|
>
|
||||||
</label>
|
Create new variable
|
||||||
{createNewVariable && (
|
</label>
|
||||||
<>
|
{createNewVariable && (
|
||||||
<input
|
<>
|
||||||
type="text"
|
<input
|
||||||
id="variable-name"
|
type="text"
|
||||||
name="variable-name"
|
id="variable-name"
|
||||||
className="flex-1 border-solid border-0 border-b border-chalkboard-50 bg-transparent focus:outline-none"
|
name="variable-name"
|
||||||
placeholder="Variable name"
|
className="flex-1 border-solid border-0 border-b border-chalkboard-50 bg-transparent focus:outline-none"
|
||||||
value={newVariableName}
|
placeholder="Variable name"
|
||||||
autoCapitalize="off"
|
value={newVariableName}
|
||||||
autoCorrect="off"
|
autoCapitalize="off"
|
||||||
autoComplete="off"
|
autoCorrect="off"
|
||||||
spellCheck="false"
|
autoComplete="off"
|
||||||
autoFocus
|
spellCheck="false"
|
||||||
onChange={(e) => setNewVariableName(e.target.value)}
|
autoFocus
|
||||||
onKeyDown={(e) => {
|
onChange={(e) => setNewVariableName(e.target.value)}
|
||||||
if (
|
onKeyDown={(e) => {
|
||||||
e.currentTarget.value === '' &&
|
if (
|
||||||
e.key === 'Backspace' &&
|
e.currentTarget.value === '' &&
|
||||||
arg.createVariable !== 'force'
|
e.key === 'Backspace' &&
|
||||||
) {
|
arg.createVariable !== 'force'
|
||||||
setCreateNewVariable(false)
|
) {
|
||||||
|
setCreateNewVariable(false)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onKeyUp={(e) => {
|
||||||
|
if (e.key === 'Enter' && canSubmit) {
|
||||||
|
handleSubmit()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className={
|
||||||
|
isNewVariableNameUnique
|
||||||
|
? 'text-succeed-60 dark:text-succeed-40'
|
||||||
|
: 'text-destroy-60 dark:text-destroy-40'
|
||||||
}
|
}
|
||||||
}}
|
>
|
||||||
onKeyUp={(e) => {
|
{isNewVariableNameUnique ? 'Available' : 'Unavailable'}
|
||||||
if (e.key === 'Enter' && canSubmit) {
|
</span>
|
||||||
handleSubmit()
|
</>
|
||||||
}
|
)}
|
||||||
}}
|
</div>
|
||||||
/>
|
)}
|
||||||
<span
|
|
||||||
className={
|
|
||||||
isNewVariableNameUnique
|
|
||||||
? 'text-succeed-60 dark:text-succeed-40'
|
|
||||||
: 'text-destroy-60 dark:text-destroy-40'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{isNewVariableNameUnique ? 'Available' : 'Unavailable'}
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{isConstrainWithNamedValueCommand && (
|
{isConstrainWithNamedValueCommand && (
|
||||||
<div className="flex items-baseline gap-4 mx-4">
|
<div className="flex items-baseline gap-4 mx-4">
|
||||||
<input
|
<input
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
|
|
||||||
import CommandBarHeader from '@src/components/CommandBar/CommandBarHeader'
|
import CommandBarHeaderFooter from '@src/components/CommandBar/CommandBarHeaderFooter'
|
||||||
|
import CommandBarDivider from '@src/components/CommandBar/CommandBarDivider'
|
||||||
import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
|
import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
import { CustomIcon } from '@src/components/CustomIcon'
|
||||||
|
|
||||||
function CommandBarReview({ stepBack }: { stepBack: () => void }) {
|
function CommandBarReview({ stepBack }: { stepBack: () => void }) {
|
||||||
const commandBarState = useCommandBarState()
|
const commandBarState = useCommandBarState()
|
||||||
@ -57,19 +60,71 @@ function CommandBarReview({ stepBack }: { stepBack: () => void }) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const availableOptionalArgs = useMemo(() => {
|
||||||
|
if (!selectedCommand?.args) return undefined
|
||||||
|
const s = { ...selectedCommand.args }
|
||||||
|
for (const [name, arg] of Object.entries(s)) {
|
||||||
|
const value =
|
||||||
|
(typeof argumentsToSubmit[name] === 'function'
|
||||||
|
? argumentsToSubmit[name](commandBarState.context)
|
||||||
|
: argumentsToSubmit[name]) || ''
|
||||||
|
const isHidden =
|
||||||
|
typeof arg.hidden === 'function'
|
||||||
|
? arg.hidden(commandBarState.context)
|
||||||
|
: arg.hidden
|
||||||
|
const isRequired =
|
||||||
|
typeof arg.required === 'function'
|
||||||
|
? arg.required(commandBarState.context)
|
||||||
|
: arg.required
|
||||||
|
if (isHidden || isRequired || value) {
|
||||||
|
delete s[name]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}, [selectedCommand, argumentsToSubmit, commandBarState.context])
|
||||||
return (
|
return (
|
||||||
<CommandBarHeader>
|
<CommandBarHeaderFooter stepBack={stepBack}>
|
||||||
<p className="px-4 pb-2">
|
{selectedCommand?.reviewMessage && (
|
||||||
{selectedCommand?.reviewMessage ? (
|
<>
|
||||||
selectedCommand.reviewMessage instanceof Function ? (
|
<p className="px-4 py-2">
|
||||||
selectedCommand.reviewMessage(commandBarState.context)
|
{selectedCommand.reviewMessage instanceof Function
|
||||||
) : (
|
? selectedCommand.reviewMessage(commandBarState.context)
|
||||||
selectedCommand.reviewMessage
|
: selectedCommand.reviewMessage}
|
||||||
)
|
</p>
|
||||||
) : (
|
<CommandBarDivider />
|
||||||
<>Confirm {selectedCommand?.displayName || selectedCommand?.name}</>
|
</>
|
||||||
)}
|
)}
|
||||||
</p>
|
{Object.entries(availableOptionalArgs || {}).length > 0 && (
|
||||||
|
<>
|
||||||
|
<div className="px-4 flex flex-wrap gap-2 items-baseline">
|
||||||
|
<span className="text-sm mr-4">Optional</span>
|
||||||
|
{Object.entries(availableOptionalArgs || {}).map(
|
||||||
|
([argName, arg]) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
data-testid="cmd-bar-add-optional-arg"
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
commandBarActor.send({
|
||||||
|
type: 'Edit argument',
|
||||||
|
data: { arg: { ...arg, name: argName } },
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
key={argName}
|
||||||
|
className="w-fit px-2 py-1 m-0 rounded-sm flex gap-2 items-center border"
|
||||||
|
>
|
||||||
|
<span className="capitalize">
|
||||||
|
{arg.displayName || argName}
|
||||||
|
</span>
|
||||||
|
<CustomIcon name="plus" className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<CommandBarDivider />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<form
|
<form
|
||||||
id="review-form"
|
id="review-form"
|
||||||
className="absolute opacity-0 inset-0 pointer-events-none"
|
className="absolute opacity-0 inset-0 pointer-events-none"
|
||||||
@ -97,7 +152,7 @@ function CommandBarReview({ stepBack }: { stepBack: () => void }) {
|
|||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</form>
|
</form>
|
||||||
</CommandBarHeader>
|
</CommandBarHeaderFooter>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,8 @@ export type DownloadAppToastProps = {
|
|||||||
onDismiss: () => void
|
onDismiss: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const desktopAppPitchMessage = `The present web app is limited in features. We don't want you to miss out!`
|
||||||
|
|
||||||
export function DownloadAppToast({
|
export function DownloadAppToast({
|
||||||
onAccept,
|
onAccept,
|
||||||
onDismiss,
|
onDismiss,
|
||||||
@ -20,8 +22,7 @@ export function DownloadAppToast({
|
|||||||
<section>
|
<section>
|
||||||
<h2>Zoo Design Studio is primarily a desktop app</h2>
|
<h2>Zoo Design Studio is primarily a desktop app</h2>
|
||||||
<p className="text-sm text-chalkboard-70 dark:text-chalkboard-30">
|
<p className="text-sm text-chalkboard-70 dark:text-chalkboard-30">
|
||||||
The present web app is limited in features. We don't want you to
|
{desktopAppPitchMessage}
|
||||||
miss out!
|
|
||||||
</p>
|
</p>
|
||||||
{!navigator?.userAgent.includes('Chrome') && (
|
{!navigator?.userAgent.includes('Chrome') && (
|
||||||
<p className="mt-2 text-sm font-semibold text-chalkboard-70 dark:text-chalkboard-30">
|
<p className="mt-2 text-sm font-semibold text-chalkboard-70 dark:text-chalkboard-30">
|
||||||
|
|||||||
@ -610,7 +610,7 @@ export const EngineStream = (props: {
|
|||||||
dataTestId="loading-engine"
|
dataTestId="loading-engine"
|
||||||
className="fixed inset-0 h-screen"
|
className="fixed inset-0 h-screen"
|
||||||
>
|
>
|
||||||
Connecting and setting up scene
|
Connecting and setting up scene...
|
||||||
</Loading>
|
</Loading>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -65,7 +65,7 @@ const CodeEditor = forwardRef<CodeEditorRef, CodeEditorProps>((props, ref) => {
|
|||||||
} = props
|
} = props
|
||||||
const editor = useRef<HTMLDivElement>(null)
|
const editor = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
const { view, state, container } = useCodeMirror({
|
const { view, container } = useCodeMirror({
|
||||||
container: editor.current,
|
container: editor.current,
|
||||||
onCreateEditor,
|
onCreateEditor,
|
||||||
extensions,
|
extensions,
|
||||||
@ -77,8 +77,8 @@ const CodeEditor = forwardRef<CodeEditorRef, CodeEditorProps>((props, ref) => {
|
|||||||
|
|
||||||
useImperativeHandle(
|
useImperativeHandle(
|
||||||
ref,
|
ref,
|
||||||
() => ({ editor: editor.current, view: view, state: state }),
|
() => ({ editor: editor.current, view: view, state: view?.state }),
|
||||||
[editor, container, view, state]
|
[editor, container, view]
|
||||||
)
|
)
|
||||||
|
|
||||||
return <div ref={editor}></div>
|
return <div ref={editor}></div>
|
||||||
@ -138,7 +138,7 @@ export function useCodeMirror(props: UseCodeMirror) {
|
|||||||
parent: container,
|
parent: container,
|
||||||
})
|
})
|
||||||
setView(viewCurrent)
|
setView(viewCurrent)
|
||||||
onCreateEditor && onCreateEditor(viewCurrent)
|
onCreateEditor?.(viewCurrent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return () => {
|
return () => {
|
||||||
@ -156,6 +156,7 @@ export function useCodeMirror(props: UseCodeMirror) {
|
|||||||
if (view) {
|
if (view) {
|
||||||
view.destroy()
|
view.destroy()
|
||||||
setView(undefined)
|
setView(undefined)
|
||||||
|
onCreateEditor?.(null)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[view]
|
[view]
|
||||||
@ -175,7 +176,7 @@ export function useCodeMirror(props: UseCodeMirror) {
|
|||||||
}
|
}
|
||||||
}, [targetExtensions, view, isFirstRender])
|
}, [targetExtensions, view, isFirstRender])
|
||||||
|
|
||||||
return { view, setView, container, setContainer, state, setState }
|
return { view, container, setContainer, state }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CodeEditor
|
export default CodeEditor
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import type { ComponentProps } from 'react'
|
|||||||
import { useCallback, useEffect, useMemo, useRef } from 'react'
|
import { useCallback, useEffect, useMemo, useRef } from 'react'
|
||||||
import type { Actor, Prop } from 'xstate'
|
import type { Actor, Prop } from 'xstate'
|
||||||
|
|
||||||
import type { Operation } from '@rust/kcl-lib/bindings/Operation'
|
import type { Operation, OpKclValue } from '@rust/kcl-lib/bindings/Operation'
|
||||||
|
|
||||||
import { ContextMenu, ContextMenuItem } from '@src/components/ContextMenu'
|
import { ContextMenu, ContextMenuItem } from '@src/components/ContextMenu'
|
||||||
import type { CustomIconName } from '@src/components/CustomIcon'
|
import type { CustomIconName } from '@src/components/CustomIcon'
|
||||||
@ -45,7 +45,7 @@ export const FeatureTreePane = () => {
|
|||||||
guards: {
|
guards: {
|
||||||
codePaneIsOpen: () =>
|
codePaneIsOpen: () =>
|
||||||
modelingState.context.store.openPanes.includes('code') &&
|
modelingState.context.store.openPanes.includes('code') &&
|
||||||
editorManager.editorView !== null,
|
editorManager.getEditorView() !== null,
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
openCodePane: () => {
|
openCodePane: () => {
|
||||||
@ -241,6 +241,7 @@ const OperationItemWrapper = ({
|
|||||||
name,
|
name,
|
||||||
variableName,
|
variableName,
|
||||||
visibilityToggle,
|
visibilityToggle,
|
||||||
|
valueDetail,
|
||||||
menuItems,
|
menuItems,
|
||||||
errors,
|
errors,
|
||||||
customSuffix,
|
customSuffix,
|
||||||
@ -252,6 +253,7 @@ const OperationItemWrapper = ({
|
|||||||
name: string
|
name: string
|
||||||
variableName?: string
|
variableName?: string
|
||||||
visibilityToggle?: VisibilityToggleProps
|
visibilityToggle?: VisibilityToggleProps
|
||||||
|
valueDetail?: { calculated: OpKclValue; display: string }
|
||||||
customSuffix?: JSX.Element
|
customSuffix?: JSX.Element
|
||||||
menuItems?: ComponentProps<typeof ContextMenu>['items']
|
menuItems?: ComponentProps<typeof ContextMenu>['items']
|
||||||
errors?: Diagnostic[]
|
errors?: Diagnostic[]
|
||||||
@ -266,19 +268,24 @@ const OperationItemWrapper = ({
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
{...props}
|
{...props}
|
||||||
className={`reset flex-1 flex items-center gap-2 text-left text-base ${selectable ? 'border-transparent dark:border-transparent' : 'border-none cursor-default'} ${className}`}
|
className={`reset !py-0.5 !px-1 flex-1 flex items-center gap-2 text-left text-base ${selectable ? 'border-transparent dark:border-transparent' : 'border-none cursor-default'} ${className}`}
|
||||||
>
|
>
|
||||||
<CustomIcon name={icon} className="w-5 h-5 block" />
|
<CustomIcon name={icon} className="w-5 h-5 block" />
|
||||||
<div className="flex items-baseline align-baseline">
|
<div className="flex flex-1 items-baseline align-baseline">
|
||||||
<div className="mr-2">
|
<div className="flex-1 inline-flex items-baseline flex-wrap gap-x-2">
|
||||||
{name}
|
{name}
|
||||||
{variableName && (
|
{variableName && (
|
||||||
<span className="ml-2 opacity-50 text-[11px] font-semibold">
|
<span className="text-chalkboard-70 dark:text-chalkboard-40 text-xs">
|
||||||
{variableName}
|
{variableName}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
{customSuffix && customSuffix}
|
||||||
</div>
|
</div>
|
||||||
{customSuffix && customSuffix}
|
{valueDetail && (
|
||||||
|
<code className="px-1 text-right text-chalkboard-70 dark:text-chalkboard-40 text-xs">
|
||||||
|
{valueDetail.display}
|
||||||
|
</code>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
{errors && errors.length > 0 && (
|
{errors && errors.length > 0 && (
|
||||||
@ -302,6 +309,19 @@ const OperationItem = (props: {
|
|||||||
}) => {
|
}) => {
|
||||||
const kclContext = useKclContext()
|
const kclContext = useKclContext()
|
||||||
const name = getOperationLabel(props.item)
|
const name = getOperationLabel(props.item)
|
||||||
|
const valueDetail = useMemo(
|
||||||
|
() =>
|
||||||
|
props.item.type === 'VariableDeclaration'
|
||||||
|
? {
|
||||||
|
display: kclContext.code.slice(
|
||||||
|
props.item.sourceRange[0],
|
||||||
|
props.item.sourceRange[1]
|
||||||
|
),
|
||||||
|
calculated: props.item.value,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
[props.item, kclContext.code]
|
||||||
|
)
|
||||||
|
|
||||||
const variableName = useMemo(() => {
|
const variableName = useMemo(() => {
|
||||||
return getOperationVariableName(props.item, kclContext.ast)
|
return getOperationVariableName(props.item, kclContext.ast)
|
||||||
@ -334,7 +354,10 @@ const OperationItem = (props: {
|
|||||||
* TODO: https://github.com/KittyCAD/modeling-app/issues/4442
|
* TODO: https://github.com/KittyCAD/modeling-app/issues/4442
|
||||||
*/
|
*/
|
||||||
function enterEditFlow() {
|
function enterEditFlow() {
|
||||||
if (props.item.type === 'StdLibCall') {
|
if (
|
||||||
|
props.item.type === 'StdLibCall' ||
|
||||||
|
props.item.type === 'VariableDeclaration'
|
||||||
|
) {
|
||||||
props.send({
|
props.send({
|
||||||
type: 'enterEditFlow',
|
type: 'enterEditFlow',
|
||||||
data: {
|
data: {
|
||||||
@ -449,15 +472,25 @@ const OperationItem = (props: {
|
|||||||
</ContextMenuItem>,
|
</ContextMenuItem>,
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
...(props.item.type === 'StdLibCall'
|
...(props.item.type === 'StdLibCall' ||
|
||||||
|
props.item.type === 'VariableDeclaration'
|
||||||
? [
|
? [
|
||||||
<ContextMenuItem
|
<ContextMenuItem
|
||||||
disabled={!stdLibMap[props.item.name]?.prepareToEdit}
|
disabled={
|
||||||
|
!(
|
||||||
|
stdLibMap[props.item.name]?.prepareToEdit ||
|
||||||
|
props.item.type === 'VariableDeclaration'
|
||||||
|
)
|
||||||
|
}
|
||||||
onClick={enterEditFlow}
|
onClick={enterEditFlow}
|
||||||
hotkey="Double click"
|
hotkey="Double click"
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</ContextMenuItem>,
|
</ContextMenuItem>,
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
...(props.item.type === 'StdLibCall'
|
||||||
|
? [
|
||||||
<ContextMenuItem
|
<ContextMenuItem
|
||||||
disabled={!stdLibMap[props.item.name]?.supportsAppearance}
|
disabled={!stdLibMap[props.item.name]?.supportsAppearance}
|
||||||
onClick={enterAppearanceFlow}
|
onClick={enterAppearanceFlow}
|
||||||
@ -517,6 +550,7 @@ const OperationItem = (props: {
|
|||||||
icon={getOperationIcon(props.item)}
|
icon={getOperationIcon(props.item)}
|
||||||
name={name}
|
name={name}
|
||||||
variableName={variableName}
|
variableName={variableName}
|
||||||
|
valueDetail={valueDetail}
|
||||||
menuItems={menuItems}
|
menuItems={menuItems}
|
||||||
onClick={selectOperation}
|
onClick={selectOperation}
|
||||||
onDoubleClick={enterEditFlow}
|
onDoubleClick={enterEditFlow}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
defaultKeymap,
|
defaultKeymap,
|
||||||
history,
|
history,
|
||||||
|
historyField,
|
||||||
historyKeymap,
|
historyKeymap,
|
||||||
indentWithTab,
|
indentWithTab,
|
||||||
} from '@codemirror/commands'
|
} from '@codemirror/commands'
|
||||||
@ -37,13 +38,12 @@ import interact from '@replit/codemirror-interact'
|
|||||||
import { TEST } from '@src/env'
|
import { TEST } from '@src/env'
|
||||||
import { useSelector } from '@xstate/react'
|
import { useSelector } from '@xstate/react'
|
||||||
import { useEffect, useMemo, useRef } from 'react'
|
import { useEffect, useMemo, useRef } from 'react'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
|
||||||
|
|
||||||
import { useLspContext } from '@src/components/LspProvider'
|
import { useLspContext } from '@src/components/LspProvider'
|
||||||
import CodeEditor from '@src/components/ModelingSidebar/ModelingPanes/CodeEditor'
|
import CodeEditor from '@src/components/ModelingSidebar/ModelingPanes/CodeEditor'
|
||||||
import { lineHighlightField } from '@src/editor/highlightextension'
|
import { lineHighlightField } from '@src/editor/highlightextension'
|
||||||
import { modelingMachineEvent } from '@src/editor/manager'
|
import { modelingMachineEvent } from '@src/editor/manager'
|
||||||
import { codeManagerHistoryCompartment } from '@src/lang/codeManager'
|
import { historyCompartment } from '@src/editor/compartments'
|
||||||
import { codeManager, editorManager, kclManager } from '@src/lib/singletons'
|
import { codeManager, editorManager, kclManager } from '@src/lib/singletons'
|
||||||
import { Themes, getSystemTheme } from '@src/lib/theme'
|
import { Themes, getSystemTheme } from '@src/lib/theme'
|
||||||
import { onMouseDragMakeANewNumber, onMouseDragRegex } from '@src/lib/utils'
|
import { onMouseDragMakeANewNumber, onMouseDragRegex } from '@src/lib/utils'
|
||||||
@ -75,17 +75,6 @@ export const KclEditorPane = () => {
|
|||||||
: context.app.theme.current
|
: context.app.theme.current
|
||||||
const { copilotLSP, kclLSP } = useLspContext()
|
const { copilotLSP, kclLSP } = useLspContext()
|
||||||
|
|
||||||
// Since these already exist in the editor, we don't need to define them
|
|
||||||
// with the wrapper.
|
|
||||||
useHotkeys('mod+z', (e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
editorManager.undo()
|
|
||||||
})
|
|
||||||
useHotkeys('mod+shift+z', (e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
editorManager.redo()
|
|
||||||
})
|
|
||||||
|
|
||||||
// When this component unmounts, we need to tell the machine that the editor
|
// When this component unmounts, we need to tell the machine that the editor
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
@ -96,12 +85,13 @@ export const KclEditorPane = () => {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!editorIsMounted || !lastSelectionEvent || !editorManager.editorView) {
|
const editorView = editorManager.getEditorView()
|
||||||
|
if (!editorIsMounted || !lastSelectionEvent || !editorView) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
editorManager.editorView.dispatch({
|
editorView.dispatch({
|
||||||
selection: lastSelectionEvent.codeMirrorSelection,
|
selection: lastSelectionEvent.codeMirrorSelection,
|
||||||
annotations: [modelingMachineEvent, Transaction.addToHistory.of(false)],
|
annotations: [modelingMachineEvent, Transaction.addToHistory.of(false)],
|
||||||
scrollIntoView: lastSelectionEvent.scrollIntoView,
|
scrollIntoView: lastSelectionEvent.scrollIntoView,
|
||||||
@ -119,13 +109,21 @@ export const KclEditorPane = () => {
|
|||||||
// Instead, hot load hotkeys via code mirror native.
|
// Instead, hot load hotkeys via code mirror native.
|
||||||
const codeMirrorHotkeys = codeManager.getCodemirrorHotkeys()
|
const codeMirrorHotkeys = codeManager.getCodemirrorHotkeys()
|
||||||
|
|
||||||
|
// When opening the editor, use the existing history in editorManager.
|
||||||
|
// This is needed to ensure users can undo beyond when the editor has been openeed.
|
||||||
|
// (Another solution would be to reuse the same state instead of creating a new one in CodeEditor.)
|
||||||
|
const existingHistory = editorManager.editorState.field(historyField)
|
||||||
|
const initialHistory = existingHistory
|
||||||
|
? historyField.init(() => existingHistory)
|
||||||
|
: history()
|
||||||
|
|
||||||
const editorExtensions = useMemo(() => {
|
const editorExtensions = useMemo(() => {
|
||||||
const extensions = [
|
const extensions = [
|
||||||
drawSelection({
|
drawSelection({
|
||||||
cursorBlinkRate: cursorBlinking.current ? 1200 : 0,
|
cursorBlinkRate: cursorBlinking.current ? 1200 : 0,
|
||||||
}),
|
}),
|
||||||
lineHighlightField,
|
lineHighlightField,
|
||||||
codeManagerHistoryCompartment.of(history()),
|
historyCompartment.of(initialHistory),
|
||||||
closeBrackets(),
|
closeBrackets(),
|
||||||
codeFolding(),
|
codeFolding(),
|
||||||
keymap.of([
|
keymap.of([
|
||||||
@ -206,10 +204,9 @@ export const KclEditorPane = () => {
|
|||||||
extensions={editorExtensions}
|
extensions={editorExtensions}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
onCreateEditor={(_editorView) => {
|
onCreateEditor={(_editorView) => {
|
||||||
if (_editorView === null) return
|
|
||||||
|
|
||||||
editorManager.setEditorView(_editorView)
|
editorManager.setEditorView(_editorView)
|
||||||
kclEditorActor.send({ type: 'setKclEditorMounted', data: true })
|
|
||||||
|
if (!_editorView) return
|
||||||
|
|
||||||
// Update diagnostics as they are cleared when the editor is unmounted.
|
// Update diagnostics as they are cleared when the editor is unmounted.
|
||||||
// Without this, errors would not be shown when closing and reopening the editor.
|
// Without this, errors would not be shown when closing and reopening the editor.
|
||||||
|
|||||||
@ -1,65 +1,29 @@
|
|||||||
import { Popover } from '@headlessui/react'
|
|
||||||
import { useContext } from 'react'
|
import { useContext } from 'react'
|
||||||
import { CustomIcon } from '@src/components/CustomIcon'
|
|
||||||
import { MachineManagerContext } from '@src/components/MachineManagerProvider'
|
import { MachineManagerContext } from '@src/components/MachineManagerProvider'
|
||||||
import Tooltip from '@src/components/Tooltip'
|
|
||||||
import { isDesktop } from '@src/lib/isDesktop'
|
|
||||||
import type { components } from '@src/lib/machine-api'
|
import type { components } from '@src/lib/machine-api'
|
||||||
import type { StatusBarItemType } from '@src/components/StatusBar/statusBarTypes'
|
import type { StatusBarItemType } from '@src/components/StatusBar/statusBarTypes'
|
||||||
|
|
||||||
export const NetworkMachineIndicator = ({
|
export const useNetworkMachineStatus = (): StatusBarItemType => {
|
||||||
className,
|
|
||||||
}: {
|
|
||||||
className?: string
|
|
||||||
}) => {
|
|
||||||
const {
|
const {
|
||||||
noMachinesReason,
|
noMachinesReason,
|
||||||
machines,
|
machines,
|
||||||
machines: { length: machineCount },
|
machines: { length: machineCount },
|
||||||
} = useContext(MachineManagerContext)
|
} = useContext(MachineManagerContext)
|
||||||
const reason = noMachinesReason()
|
const reason = noMachinesReason()
|
||||||
|
|
||||||
return isDesktop() ? (
|
|
||||||
<Popover className="relative">
|
|
||||||
<Popover.Button
|
|
||||||
className={`flex items-center p-0 border-none bg-transparent dark:bg-transparent relative ${className || ''}`}
|
|
||||||
data-testid="network-machine-toggle"
|
|
||||||
>
|
|
||||||
<NetworkMachinesIcon machineCount={machineCount} />
|
|
||||||
<Tooltip position="top-left" wrapperClassName="ui-open:hidden">
|
|
||||||
Network machines ({machineCount}) {reason && `: ${reason}`}
|
|
||||||
</Tooltip>
|
|
||||||
</Popover.Button>
|
|
||||||
<Popover.Panel
|
|
||||||
className="absolute left-0 bottom-full mb-1 w-64 flex flex-col gap-1 align-stretch bg-chalkboard-10 dark:bg-chalkboard-90 rounded shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50 text-sm"
|
|
||||||
data-testid="network-popover"
|
|
||||||
>
|
|
||||||
<NetworkMachinesPopoverContent machines={machines} />
|
|
||||||
</Popover.Panel>
|
|
||||||
</Popover>
|
|
||||||
) : null
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useNetworkMachineStatus = (): StatusBarItemType => {
|
|
||||||
return {
|
return {
|
||||||
id: 'network-machines',
|
id: 'network-machines',
|
||||||
component: NetworkMachineIndicator,
|
'data-testid': `network-machine-toggle`,
|
||||||
|
label: `${machineCount}`,
|
||||||
|
hideLabel: machineCount === 0,
|
||||||
|
toolTip: {
|
||||||
|
children: `Network machines (${machineCount}) ${reason ? `: ${reason}` : ''}`,
|
||||||
|
},
|
||||||
|
element: 'popover',
|
||||||
|
icon: 'printer3d',
|
||||||
|
popoverContent: <NetworkMachinesPopoverContent machines={machines} />,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function NetworkMachinesIcon({ machineCount }: { machineCount: number }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<CustomIcon name="printer3d" className="w-5 h-5" />
|
|
||||||
{machineCount > 0 && (
|
|
||||||
<p aria-hidden className="flex items-center justify-center text-xs">
|
|
||||||
{machineCount}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function NetworkMachinesPopoverContent({
|
function NetworkMachinesPopoverContent({
|
||||||
machines,
|
machines,
|
||||||
}: { machines: components['schemas']['MachineInfoResponse'][] }) {
|
}: { machines: components['schemas']['MachineInfoResponse'][] }) {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { StatusBarItemType } from '@src/components/StatusBar/statusBarTypes'
|
import type { StatusBarItemType } from '@src/components/StatusBar/statusBarTypes'
|
||||||
import type { Location } from 'react-router-dom'
|
import type { Location } from 'react-router-dom'
|
||||||
import { PATHS } from '@src/lib/paths'
|
import { PATHS } from '@src/lib/paths'
|
||||||
import { APP_VERSION } from '@src/routes/utils'
|
import { APP_VERSION, getReleaseUrl } from '@src/routes/utils'
|
||||||
import {
|
import {
|
||||||
BillingRemaining,
|
BillingRemaining,
|
||||||
BillingRemainingMode,
|
BillingRemainingMode,
|
||||||
@ -11,6 +11,10 @@ import { BillingDialog } from '@src/components/BillingDialog'
|
|||||||
import { Popover } from '@headlessui/react'
|
import { Popover } from '@headlessui/react'
|
||||||
import Tooltip from '@src/components/Tooltip'
|
import Tooltip from '@src/components/Tooltip'
|
||||||
import { HelpMenu } from '@src/components/HelpMenu'
|
import { HelpMenu } from '@src/components/HelpMenu'
|
||||||
|
import { isDesktop } from '@src/lib/isDesktop'
|
||||||
|
import { VITE_KC_SITE_BASE_URL } from '@src/env'
|
||||||
|
import { APP_DOWNLOAD_PATH } from '@src/lib/constants'
|
||||||
|
import { desktopAppPitchMessage } from '@src/components/DownloadAppToast'
|
||||||
|
|
||||||
export const defaultGlobalStatusBarItems = ({
|
export const defaultGlobalStatusBarItems = ({
|
||||||
location,
|
location,
|
||||||
@ -19,15 +23,26 @@ export const defaultGlobalStatusBarItems = ({
|
|||||||
location: Location
|
location: Location
|
||||||
filePath?: string
|
filePath?: string
|
||||||
}): StatusBarItemType[] => [
|
}): StatusBarItemType[] => [
|
||||||
{
|
isDesktop()
|
||||||
id: 'version',
|
? {
|
||||||
element: 'externalLink',
|
id: 'version',
|
||||||
label: `v${APP_VERSION}`,
|
element: 'externalLink',
|
||||||
href: `https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`,
|
label: `v${APP_VERSION}`,
|
||||||
toolTip: {
|
href: getReleaseUrl(),
|
||||||
children: 'View the release notes on GitHub',
|
toolTip: {
|
||||||
},
|
children: 'View the release notes on GitHub',
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
id: 'download-desktop-app',
|
||||||
|
element: 'externalLink',
|
||||||
|
label: 'Download the app',
|
||||||
|
href: `${VITE_KC_SITE_BASE_URL}/${APP_DOWNLOAD_PATH}`,
|
||||||
|
icon: 'download',
|
||||||
|
toolTip: {
|
||||||
|
children: desktopAppPitchMessage,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'telemetry',
|
id: 'telemetry',
|
||||||
element: 'link',
|
element: 'link',
|
||||||
|
|||||||
3
src/editor/compartments.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { Compartment } from '@codemirror/state'
|
||||||
|
|
||||||
|
export const historyCompartment = new Compartment()
|
||||||
@ -1,10 +1,22 @@
|
|||||||
import { redo, undo } from '@codemirror/commands'
|
import {
|
||||||
|
defaultKeymap,
|
||||||
|
history,
|
||||||
|
historyKeymap,
|
||||||
|
redo,
|
||||||
|
undo,
|
||||||
|
} from '@codemirror/commands'
|
||||||
import { syntaxTree } from '@codemirror/language'
|
import { syntaxTree } from '@codemirror/language'
|
||||||
import type { Diagnostic } from '@codemirror/lint'
|
import type { Diagnostic } from '@codemirror/lint'
|
||||||
import { forEachDiagnostic, setDiagnosticsEffect } from '@codemirror/lint'
|
import { forEachDiagnostic, setDiagnosticsEffect } from '@codemirror/lint'
|
||||||
import { Annotation, EditorSelection, Transaction } from '@codemirror/state'
|
import {
|
||||||
|
Annotation,
|
||||||
|
EditorSelection,
|
||||||
|
EditorState,
|
||||||
|
Transaction,
|
||||||
|
type TransactionSpec,
|
||||||
|
} from '@codemirror/state'
|
||||||
import type { ViewUpdate } from '@codemirror/view'
|
import type { ViewUpdate } from '@codemirror/view'
|
||||||
import { EditorView } from '@codemirror/view'
|
import { EditorView, keymap } from '@codemirror/view'
|
||||||
import type { StateFrom } from 'xstate'
|
import type { StateFrom } from 'xstate'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -22,6 +34,8 @@ import type {
|
|||||||
ModelingMachineEvent,
|
ModelingMachineEvent,
|
||||||
modelingMachine,
|
modelingMachine,
|
||||||
} from '@src/machines/modelingMachine'
|
} from '@src/machines/modelingMachine'
|
||||||
|
import { historyCompartment } from '@src/editor/compartments'
|
||||||
|
import type CodeManager from '@src/lang/codeManager'
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
@ -65,11 +79,28 @@ export default class EditorManager {
|
|||||||
|
|
||||||
private _highlightRange: Array<[number, number]> = [[0, 0]]
|
private _highlightRange: Array<[number, number]> = [[0, 0]]
|
||||||
|
|
||||||
public _editorView: EditorView | null = null
|
private _editorState: EditorState
|
||||||
|
private _editorView: EditorView | null = null
|
||||||
public kclManager?: KclManager
|
public kclManager?: KclManager
|
||||||
|
public codeManager?: CodeManager
|
||||||
|
|
||||||
constructor(engineCommandManager: EngineCommandManager) {
|
constructor(engineCommandManager: EngineCommandManager) {
|
||||||
this.engineCommandManager = engineCommandManager
|
this.engineCommandManager = engineCommandManager
|
||||||
|
|
||||||
|
this._editorState = EditorState.create({
|
||||||
|
doc: '',
|
||||||
|
extensions: [
|
||||||
|
historyCompartment.of(history()),
|
||||||
|
keymap.of([...defaultKeymap, ...historyKeymap]),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get editorState(): EditorState {
|
||||||
|
return this._editorView?.state || this._editorState
|
||||||
|
}
|
||||||
|
get state() {
|
||||||
|
return this.editorState
|
||||||
}
|
}
|
||||||
|
|
||||||
setCopilotEnabled(enabled: boolean) {
|
setCopilotEnabled(enabled: boolean) {
|
||||||
@ -80,12 +111,25 @@ export default class EditorManager {
|
|||||||
return this._copilotEnabled
|
return this._copilotEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
setEditorView(editorView: EditorView) {
|
// Invoked when editorView is created and each time when it is updated (eg. user is sketching)..
|
||||||
|
setEditorView(editorView: EditorView | null) {
|
||||||
|
// Update editorState to the latest editorView state.
|
||||||
|
// This is needed because if kcl pane is closed, editorView will become null but we still want to use the last state.
|
||||||
|
this._editorState = editorView?.state || this._editorState
|
||||||
|
|
||||||
this._editorView = editorView
|
this._editorView = editorView
|
||||||
kclEditorActor.send({ type: 'setKclEditorMounted', data: true })
|
|
||||||
|
kclEditorActor.send({
|
||||||
|
type: 'setKclEditorMounted',
|
||||||
|
data: Boolean(editorView),
|
||||||
|
})
|
||||||
this.overrideTreeHighlighterUpdateForPerformanceTracking()
|
this.overrideTreeHighlighterUpdateForPerformanceTracking()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getEditorView(): EditorView | null {
|
||||||
|
return this._editorView
|
||||||
|
}
|
||||||
|
|
||||||
overrideTreeHighlighterUpdateForPerformanceTracking() {
|
overrideTreeHighlighterUpdateForPerformanceTracking() {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this._editorView?.plugins.forEach((e) => {
|
this._editorView?.plugins.forEach((e) => {
|
||||||
@ -132,10 +176,6 @@ export default class EditorManager {
|
|||||||
return this._isAllTextSelected
|
return this._isAllTextSelected
|
||||||
}
|
}
|
||||||
|
|
||||||
get editorView(): EditorView | null {
|
|
||||||
return this._editorView
|
|
||||||
}
|
|
||||||
|
|
||||||
get isShiftDown(): boolean {
|
get isShiftDown(): boolean {
|
||||||
return this._isShiftDown
|
return this._isShiftDown
|
||||||
}
|
}
|
||||||
@ -287,12 +327,39 @@ export default class EditorManager {
|
|||||||
undo() {
|
undo() {
|
||||||
if (this._editorView) {
|
if (this._editorView) {
|
||||||
undo(this._editorView)
|
undo(this._editorView)
|
||||||
|
} else if (this._editorState) {
|
||||||
|
const undoPerformed = undo(this) // invokes dispatch which updates this._editorState
|
||||||
|
if (undoPerformed) {
|
||||||
|
const newState = this._editorState
|
||||||
|
// Update the code, this is similar to kcl/index.ts / update, updateDoc,
|
||||||
|
// needed to update the code, so sketch segments can update themselves.
|
||||||
|
// In the editorView case this happens within the kcl plugin's update method being called during updates.
|
||||||
|
this.codeManager!.code = newState.doc.toString()
|
||||||
|
void this.kclManager!.executeCode()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
redo() {
|
redo() {
|
||||||
if (this._editorView) {
|
if (this._editorView) {
|
||||||
redo(this._editorView)
|
redo(this._editorView)
|
||||||
|
} else if (this._editorState) {
|
||||||
|
const redoPerformed = redo(this)
|
||||||
|
if (redoPerformed) {
|
||||||
|
const newState = this._editorState
|
||||||
|
this.codeManager!.code = newState.doc.toString()
|
||||||
|
void this.kclManager!.executeCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked by codeMirror during undo/redo.
|
||||||
|
// Call with incorrect "this" so it needs to be an arrow function.
|
||||||
|
dispatch = (spec: TransactionSpec) => {
|
||||||
|
if (this._editorView) {
|
||||||
|
this._editorView.dispatch(spec)
|
||||||
|
} else if (this._editorState) {
|
||||||
|
this._editorState = this._editorState.update(spec).state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -389,7 +389,7 @@ export class KclManager extends EventTarget {
|
|||||||
|
|
||||||
if (err(result)) {
|
if (err(result)) {
|
||||||
const kclError: KCLError = result as KCLError
|
const kclError: KCLError = result as KCLError
|
||||||
this.diagnostics = kclErrorsToDiagnostics([kclError])
|
this.diagnostics = kclErrorsToDiagnostics([kclError], code)
|
||||||
this._astParseFailed = true
|
this._astParseFailed = true
|
||||||
|
|
||||||
await this.checkIfSwitchedFilesShouldClear()
|
await this.checkIfSwitchedFilesShouldClear()
|
||||||
@ -403,8 +403,8 @@ export class KclManager extends EventTarget {
|
|||||||
this._kclErrorsCallBack([])
|
this._kclErrorsCallBack([])
|
||||||
this._logsCallBack([])
|
this._logsCallBack([])
|
||||||
|
|
||||||
this.addDiagnostics(compilationErrorsToDiagnostics(result.errors))
|
this.addDiagnostics(compilationErrorsToDiagnostics(result.errors, code))
|
||||||
this.addDiagnostics(compilationErrorsToDiagnostics(result.warnings))
|
this.addDiagnostics(compilationErrorsToDiagnostics(result.warnings, code))
|
||||||
if (result.errors.length > 0) {
|
if (result.errors.length > 0) {
|
||||||
this._astParseFailed = true
|
this._astParseFailed = true
|
||||||
|
|
||||||
@ -465,7 +465,12 @@ export class KclManager extends EventTarget {
|
|||||||
// Program was not interrupted, setup the scene
|
// Program was not interrupted, setup the scene
|
||||||
// Do not send send scene commands if the program was interrupted, go to clean up
|
// Do not send send scene commands if the program was interrupted, go to clean up
|
||||||
if (!isInterrupted) {
|
if (!isInterrupted) {
|
||||||
this.addDiagnostics(await lintAst({ ast: ast }))
|
this.addDiagnostics(
|
||||||
|
await lintAst({
|
||||||
|
ast,
|
||||||
|
sourceCode: this.singletons.codeManager.code,
|
||||||
|
})
|
||||||
|
)
|
||||||
await setSelectionFilterToDefault(this.engineCommandManager)
|
await setSelectionFilterToDefault(this.engineCommandManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -486,11 +491,16 @@ export class KclManager extends EventTarget {
|
|||||||
|
|
||||||
this.logs = logs
|
this.logs = logs
|
||||||
this.errors = errors
|
this.errors = errors
|
||||||
|
const code = this.singletons.codeManager.code
|
||||||
// Do not add the errors since the program was interrupted and the error is not a real KCL error
|
// Do not add the errors since the program was interrupted and the error is not a real KCL error
|
||||||
this.addDiagnostics(isInterrupted ? [] : kclErrorsToDiagnostics(errors))
|
this.addDiagnostics(
|
||||||
|
isInterrupted ? [] : kclErrorsToDiagnostics(errors, code)
|
||||||
|
)
|
||||||
// Add warnings and non-fatal errors
|
// Add warnings and non-fatal errors
|
||||||
this.addDiagnostics(
|
this.addDiagnostics(
|
||||||
isInterrupted ? [] : compilationErrorsToDiagnostics(execState.errors)
|
isInterrupted
|
||||||
|
? []
|
||||||
|
: compilationErrorsToDiagnostics(execState.errors, code)
|
||||||
)
|
)
|
||||||
this.execState = execState
|
this.execState = execState
|
||||||
if (!errors.length) {
|
if (!errors.length) {
|
||||||
|
|||||||
@ -2,10 +2,11 @@
|
|||||||
// NOT updating the code state when we don't need to.
|
// NOT updating the code state when we don't need to.
|
||||||
// This prevents re-renders of the codemirror editor, when typing.
|
// This prevents re-renders of the codemirror editor, when typing.
|
||||||
import { history } from '@codemirror/commands'
|
import { history } from '@codemirror/commands'
|
||||||
import { Annotation, Compartment, Transaction } from '@codemirror/state'
|
import { Annotation, Transaction } from '@codemirror/state'
|
||||||
import type { EditorView, KeyBinding } from '@codemirror/view'
|
import type { KeyBinding } from '@codemirror/view'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
|
|
||||||
|
import { historyCompartment } from '@src/editor/compartments'
|
||||||
import type { Program } from '@src/lang/wasm'
|
import type { Program } from '@src/lang/wasm'
|
||||||
import { parse, recast } from '@src/lang/wasm'
|
import { parse, recast } from '@src/lang/wasm'
|
||||||
import { bracket } from '@src/lib/exampleKcl'
|
import { bracket } from '@src/lib/exampleKcl'
|
||||||
@ -17,7 +18,6 @@ const PERSIST_CODE_KEY = 'persistCode'
|
|||||||
|
|
||||||
const codeManagerUpdateAnnotation = Annotation.define<boolean>()
|
const codeManagerUpdateAnnotation = Annotation.define<boolean>()
|
||||||
export const codeManagerUpdateEvent = codeManagerUpdateAnnotation.of(true)
|
export const codeManagerUpdateEvent = codeManagerUpdateAnnotation.of(true)
|
||||||
export const codeManagerHistoryCompartment = new Compartment()
|
|
||||||
|
|
||||||
export default class CodeManager {
|
export default class CodeManager {
|
||||||
private _code: string = bracket
|
private _code: string = bracket
|
||||||
@ -103,25 +103,24 @@ export default class CodeManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the code in the editor.
|
* Update the code in the editor.
|
||||||
|
* This is invoked when a segment is being dragged on the canvas, among other things.
|
||||||
*/
|
*/
|
||||||
updateCodeEditor(code: string, clearHistory?: boolean): void {
|
updateCodeEditor(code: string, clearHistory?: boolean): void {
|
||||||
this.code = code
|
this.code = code
|
||||||
if (editorManager.editorView) {
|
if (clearHistory) {
|
||||||
if (clearHistory) {
|
clearCodeMirrorHistory()
|
||||||
clearCodeMirrorHistory(editorManager.editorView)
|
|
||||||
}
|
|
||||||
editorManager.editorView.dispatch({
|
|
||||||
changes: {
|
|
||||||
from: 0,
|
|
||||||
to: editorManager.editorView.state.doc.length,
|
|
||||||
insert: code,
|
|
||||||
},
|
|
||||||
annotations: [
|
|
||||||
codeManagerUpdateEvent,
|
|
||||||
Transaction.addToHistory.of(!clearHistory),
|
|
||||||
],
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
editorManager.dispatch({
|
||||||
|
changes: {
|
||||||
|
from: 0,
|
||||||
|
to: editorManager.editorState?.doc.length || 0,
|
||||||
|
insert: code,
|
||||||
|
},
|
||||||
|
annotations: [
|
||||||
|
codeManagerUpdateEvent,
|
||||||
|
Transaction.addToHistory.of(!clearHistory),
|
||||||
|
],
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -213,16 +212,16 @@ function safeLSSetItem(key: string, value: string) {
|
|||||||
localStorage?.setItem(key, value)
|
localStorage?.setItem(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearCodeMirrorHistory(view: EditorView) {
|
function clearCodeMirrorHistory() {
|
||||||
// Clear history
|
// Clear history
|
||||||
view.dispatch({
|
editorManager.dispatch({
|
||||||
effects: [codeManagerHistoryCompartment.reconfigure([])],
|
effects: [historyCompartment.reconfigure([])],
|
||||||
annotations: [codeManagerUpdateEvent],
|
annotations: [codeManagerUpdateEvent],
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add history back
|
// Add history back
|
||||||
view.dispatch({
|
editorManager.dispatch({
|
||||||
effects: [codeManagerHistoryCompartment.reconfigure([history()])],
|
effects: [historyCompartment.reconfigure([history()])],
|
||||||
annotations: [codeManagerUpdateEvent],
|
annotations: [codeManagerUpdateEvent],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,35 @@
|
|||||||
import type { KCLError } from '@src/lang/errors'
|
import type { KCLError } from '@src/lang/errors'
|
||||||
import { kclErrorsToDiagnostics } from '@src/lang/errors'
|
import { kclErrorsToDiagnostics, toUtf16, toUtf8 } from '@src/lang/errors'
|
||||||
import { defaultArtifactGraph } from '@src/lang/std/artifactGraph'
|
import { defaultArtifactGraph } from '@src/lang/std/artifactGraph'
|
||||||
import { topLevelRange } from '@src/lang/util'
|
import { topLevelRange } from '@src/lang/util'
|
||||||
|
|
||||||
|
describe('test UTF conversions', () => {
|
||||||
|
it('Converts UTF-8 to UTF-16', () => {
|
||||||
|
// This KCL program has an error. The variable `亞當` cannot be +3 because
|
||||||
|
// it holds a string. So that variable, on line 2, should be highlighted by
|
||||||
|
// a source range.
|
||||||
|
const sourceCode = "亞當 = 'adam'\nx = 亞當 + 3"
|
||||||
|
// Start with a SourceRange from the KCL interpreter,
|
||||||
|
// which is a UTF-8 range, on where the variable is used on the second line.
|
||||||
|
const utf8SourceRange = [20, 26, 0]
|
||||||
|
|
||||||
|
// JS string of the program uses UTF-16, so check we can correctly find the
|
||||||
|
// source range offset in UTF-16.
|
||||||
|
const actualStart = toUtf16(utf8SourceRange[0], sourceCode)
|
||||||
|
const actualEnd = toUtf16(utf8SourceRange[1], sourceCode)
|
||||||
|
const textInSourceRange = sourceCode.slice(actualStart, actualEnd)
|
||||||
|
expect(actualStart).toBe(16)
|
||||||
|
expect(actualEnd).toBe(18)
|
||||||
|
expect(textInSourceRange).toBe('亞當')
|
||||||
|
|
||||||
|
// Test we can convert the UTF-16 source range back to UTF-8,
|
||||||
|
// getting the original source range back.
|
||||||
|
const utf16Range: [number, number, number] = [actualStart, actualEnd, 0]
|
||||||
|
const actualUtf8Range = toUtf8(utf16Range, sourceCode)
|
||||||
|
expect(actualUtf8Range).toStrictEqual(utf8SourceRange)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('test kclErrToDiagnostic', () => {
|
describe('test kclErrToDiagnostic', () => {
|
||||||
it('converts KCL errors to CodeMirror diagnostics', () => {
|
it('converts KCL errors to CodeMirror diagnostics', () => {
|
||||||
const errors: KCLError[] = [
|
const errors: KCLError[] = [
|
||||||
@ -33,7 +60,7 @@ describe('test kclErrToDiagnostic', () => {
|
|||||||
defaultPlanes: null,
|
defaultPlanes: null,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
const diagnostics = kclErrorsToDiagnostics(errors)
|
const diagnostics = kclErrorsToDiagnostics(errors, 'TEST PROGRAM')
|
||||||
expect(diagnostics).toEqual([
|
expect(diagnostics).toEqual([
|
||||||
{
|
{
|
||||||
from: 0,
|
from: 0,
|
||||||
|
|||||||
@ -290,6 +290,38 @@ export class KCLUndefinedValueError extends KCLError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Convert this UTF-16 source range offset to UTF-8 as SourceRange is always a UTF-8
|
||||||
|
*/
|
||||||
|
export function toUtf8(
|
||||||
|
utf16SourceRange: SourceRange,
|
||||||
|
sourceCode: string
|
||||||
|
): SourceRange {
|
||||||
|
const moduleId = utf16SourceRange[2]
|
||||||
|
const textEncoder = new TextEncoder()
|
||||||
|
const prefixUtf16 = sourceCode.slice(0, utf16SourceRange[0])
|
||||||
|
const prefixUtf8 = textEncoder.encode(prefixUtf16)
|
||||||
|
const prefixLen = prefixUtf8.length
|
||||||
|
const toHighlightUtf16 = sourceCode.slice(
|
||||||
|
utf16SourceRange[0],
|
||||||
|
utf16SourceRange[1]
|
||||||
|
)
|
||||||
|
const toHighlightUtf8 = textEncoder.encode(toHighlightUtf16)
|
||||||
|
const toHighlightLen = toHighlightUtf8.length
|
||||||
|
return [prefixLen, prefixLen + toHighlightLen, moduleId]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Convert this UTF-8 source range offset to UTF-16 for display in CodeMirror,
|
||||||
|
as it relies on JS-style string encoding which is UTF-16.
|
||||||
|
*/
|
||||||
|
export function toUtf16(utf8Offset: number, sourceCode: string): number {
|
||||||
|
const sourceUtf8 = new TextEncoder().encode(sourceCode)
|
||||||
|
const prefix = sourceUtf8.slice(0, utf8Offset)
|
||||||
|
const backTo16 = new TextDecoder().decode(prefix)
|
||||||
|
return backTo16.length
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps the lsp diagnostic to an array of KclErrors.
|
* Maps the lsp diagnostic to an array of KclErrors.
|
||||||
* Currently the diagnostics are all errors, but in the future they could include lints.
|
* Currently the diagnostics are all errors, but in the future they could include lints.
|
||||||
@ -299,20 +331,23 @@ export function lspDiagnosticsToKclErrors(
|
|||||||
diagnostics: LspDiagnostic[]
|
diagnostics: LspDiagnostic[]
|
||||||
): KCLError[] {
|
): KCLError[] {
|
||||||
return diagnostics
|
return diagnostics
|
||||||
.flatMap(
|
.flatMap(({ range, message }) => {
|
||||||
({ range, message }) =>
|
const sourceRange = toUtf8(
|
||||||
new KCLError(
|
[posToOffset(doc, range.start)!, posToOffset(doc, range.end)!, 0],
|
||||||
'unexpected',
|
doc.toString()
|
||||||
message,
|
)
|
||||||
[posToOffset(doc, range.start)!, posToOffset(doc, range.end)!, 0],
|
return new KCLError(
|
||||||
[],
|
'unexpected',
|
||||||
[],
|
message,
|
||||||
[],
|
sourceRange,
|
||||||
defaultArtifactGraph(),
|
[],
|
||||||
{},
|
[],
|
||||||
null
|
[],
|
||||||
)
|
defaultArtifactGraph(),
|
||||||
)
|
{},
|
||||||
|
null
|
||||||
|
)
|
||||||
|
})
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
const c = a.sourceRange[0]
|
const c = a.sourceRange[0]
|
||||||
const d = b.sourceRange[0]
|
const d = b.sourceRange[0]
|
||||||
@ -331,7 +366,8 @@ export function lspDiagnosticsToKclErrors(
|
|||||||
* Currently the diagnostics are all errors, but in the future they could include lints.
|
* Currently the diagnostics are all errors, but in the future they could include lints.
|
||||||
* */
|
* */
|
||||||
export function kclErrorsToDiagnostics(
|
export function kclErrorsToDiagnostics(
|
||||||
errors: KCLError[]
|
errors: KCLError[],
|
||||||
|
sourceCode: string
|
||||||
): CodeMirrorDiagnostic[] {
|
): CodeMirrorDiagnostic[] {
|
||||||
let nonFatal: CodeMirrorDiagnostic[] = []
|
let nonFatal: CodeMirrorDiagnostic[] = []
|
||||||
const errs = errors
|
const errs = errors
|
||||||
@ -350,8 +386,8 @@ export function kclErrorsToDiagnostics(
|
|||||||
item.sourceRange[1] !== err.sourceRange[1]
|
item.sourceRange[1] !== err.sourceRange[1]
|
||||||
) {
|
) {
|
||||||
diagnostics.push({
|
diagnostics.push({
|
||||||
from: item.sourceRange[0],
|
from: toUtf16(item.sourceRange[0], sourceCode),
|
||||||
to: item.sourceRange[1],
|
to: toUtf16(item.sourceRange[1], sourceCode),
|
||||||
message: 'Part of the error backtrace',
|
message: 'Part of the error backtrace',
|
||||||
severity: 'hint',
|
severity: 'hint',
|
||||||
})
|
})
|
||||||
@ -365,11 +401,13 @@ export function kclErrorsToDiagnostics(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (err.nonFatal.length > 0) {
|
if (err.nonFatal.length > 0) {
|
||||||
nonFatal = nonFatal.concat(compilationErrorsToDiagnostics(err.nonFatal))
|
nonFatal = nonFatal.concat(
|
||||||
|
compilationErrorsToDiagnostics(err.nonFatal, sourceCode)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
diagnostics.push({
|
diagnostics.push({
|
||||||
from: err.sourceRange[0],
|
from: toUtf16(err.sourceRange[0], sourceCode),
|
||||||
to: err.sourceRange[1],
|
to: toUtf16(err.sourceRange[1], sourceCode),
|
||||||
message,
|
message,
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
})
|
})
|
||||||
@ -379,7 +417,8 @@ export function kclErrorsToDiagnostics(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function compilationErrorsToDiagnostics(
|
export function compilationErrorsToDiagnostics(
|
||||||
errors: CompilationError[]
|
errors: CompilationError[],
|
||||||
|
sourceCode: string
|
||||||
): CodeMirrorDiagnostic[] {
|
): CodeMirrorDiagnostic[] {
|
||||||
return errors
|
return errors
|
||||||
?.filter((err) => isTopLevelModule(err.sourceRange))
|
?.filter((err) => isTopLevelModule(err.sourceRange))
|
||||||
@ -397,8 +436,8 @@ export function compilationErrorsToDiagnostics(
|
|||||||
apply: (view: EditorView, from: number, to: number) => {
|
apply: (view: EditorView, from: number, to: number) => {
|
||||||
view.dispatch({
|
view.dispatch({
|
||||||
changes: {
|
changes: {
|
||||||
from: suggestion.source_range[0],
|
from: toUtf16(suggestion.source_range[0], sourceCode),
|
||||||
to: suggestion.source_range[1],
|
to: toUtf16(suggestion.source_range[1], sourceCode),
|
||||||
insert: suggestion.insert,
|
insert: suggestion.insert,
|
||||||
},
|
},
|
||||||
annotations: [lspCodeActionEvent],
|
annotations: [lspCodeActionEvent],
|
||||||
@ -408,8 +447,8 @@ export function compilationErrorsToDiagnostics(
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
from: err.sourceRange[0],
|
from: toUtf16(err.sourceRange[0], sourceCode),
|
||||||
to: err.sourceRange[1],
|
to: toUtf16(err.sourceRange[1], sourceCode),
|
||||||
message: err.message,
|
message: err.message,
|
||||||
severity,
|
severity,
|
||||||
actions,
|
actions,
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import type { Diagnostic } from '@codemirror/lint'
|
|||||||
import { lspCodeActionEvent } from '@kittycad/codemirror-lsp-client'
|
import { lspCodeActionEvent } from '@kittycad/codemirror-lsp-client'
|
||||||
import type { Node } from '@rust/kcl-lib/bindings/Node'
|
import type { Node } from '@rust/kcl-lib/bindings/Node'
|
||||||
|
|
||||||
import { KCLError } from '@src/lang/errors'
|
import { KCLError, toUtf16 } from '@src/lang/errors'
|
||||||
import type { ExecState, Program } from '@src/lang/wasm'
|
import type { ExecState, Program } from '@src/lang/wasm'
|
||||||
import { emptyExecState, kclLint } from '@src/lang/wasm'
|
import { emptyExecState, kclLint } from '@src/lang/wasm'
|
||||||
import { EXECUTE_AST_INTERRUPT_ERROR_STRING } from '@src/lib/constants'
|
import { EXECUTE_AST_INTERRUPT_ERROR_STRING } from '@src/lib/constants'
|
||||||
@ -142,8 +142,10 @@ function handleExecuteError(e: any): ExecutionResult {
|
|||||||
|
|
||||||
export async function lintAst({
|
export async function lintAst({
|
||||||
ast,
|
ast,
|
||||||
|
sourceCode,
|
||||||
}: {
|
}: {
|
||||||
ast: Program
|
ast: Program
|
||||||
|
sourceCode: string
|
||||||
}): Promise<Array<Diagnostic>> {
|
}): Promise<Array<Diagnostic>> {
|
||||||
try {
|
try {
|
||||||
const discovered_findings = await kclLint(ast)
|
const discovered_findings = await kclLint(ast)
|
||||||
@ -157,8 +159,8 @@ export async function lintAst({
|
|||||||
apply: (view: EditorView, from: number, to: number) => {
|
apply: (view: EditorView, from: number, to: number) => {
|
||||||
view.dispatch({
|
view.dispatch({
|
||||||
changes: {
|
changes: {
|
||||||
from: suggestion.source_range[0],
|
from: toUtf16(suggestion.source_range[0], sourceCode),
|
||||||
to: suggestion.source_range[1],
|
to: toUtf16(suggestion.source_range[1], sourceCode),
|
||||||
insert: suggestion.insert,
|
insert: suggestion.insert,
|
||||||
},
|
},
|
||||||
annotations: [lspCodeActionEvent],
|
annotations: [lspCodeActionEvent],
|
||||||
@ -168,8 +170,8 @@ export async function lintAst({
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
from: lint.pos[0],
|
from: toUtf16(lint.pos[0], sourceCode),
|
||||||
to: lint.pos[1],
|
to: toUtf16(lint.pos[1], sourceCode),
|
||||||
message: lint.finding.title,
|
message: lint.finding.title,
|
||||||
severity: 'info',
|
severity: 'info',
|
||||||
actions,
|
actions,
|
||||||
|
|||||||
@ -149,6 +149,7 @@ function moreNodePathFromSourceRange(
|
|||||||
return moreNodePathFromSourceRange(init, sourceRange, path)
|
return moreNodePathFromSourceRange(init, sourceRange, path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return path
|
||||||
}
|
}
|
||||||
if (_node.type === 'UnaryExpression' && isInRange) {
|
if (_node.type === 'UnaryExpression' && isInRange) {
|
||||||
const { argument } = _node
|
const { argument } = _node
|
||||||
|
|||||||
@ -415,10 +415,10 @@ export const errFromErrWithOutputs = (e: any): KCLError => {
|
|||||||
|
|
||||||
export const kclLint = async (ast: Program): Promise<Array<Discovered>> => {
|
export const kclLint = async (ast: Program): Promise<Array<Discovered>> => {
|
||||||
try {
|
try {
|
||||||
const discovered_findings: Array<Discovered> = await kcl_lint(
|
const discoveredFindings: Array<Discovered> = await kcl_lint(
|
||||||
JSON.stringify(ast)
|
JSON.stringify(ast)
|
||||||
)
|
)
|
||||||
return discovered_findings
|
return discoveredFindings
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
return Promise.reject(e)
|
return Promise.reject(e)
|
||||||
}
|
}
|
||||||
|
|||||||