2024-07-02 17:16:27 +10:00
|
|
|
import { executeAst, lintAst } from 'lang/langHelpers'
|
2024-12-20 13:39:06 +11:00
|
|
|
import { handleSelectionBatch, Selections } from 'lib/selections'
|
2024-12-06 13:57:31 +13:00
|
|
|
import {
|
|
|
|
KCLError,
|
|
|
|
complilationErrorsToDiagnostics,
|
|
|
|
kclErrorsToDiagnostics,
|
|
|
|
} from './errors'
|
2024-04-03 19:38:16 +11:00
|
|
|
import { uuidv4 } from 'lib/utils'
|
2024-03-22 16:55:30 +11:00
|
|
|
import { EngineCommandManager } from './std/engineConnection'
|
2024-06-24 11:45:40 -04:00
|
|
|
import { err } from 'lib/trap'
|
2024-08-30 05:14:24 -05:00
|
|
|
import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from 'lib/constants'
|
2024-03-22 16:55:30 +11:00
|
|
|
|
2023-10-11 13:36:54 +11:00
|
|
|
import {
|
2024-02-11 12:59:00 +11:00
|
|
|
CallExpression,
|
KCL: Use keyword arguments for line, lineTo, extrude and close (#5249)
Part of #4600.
PR: https://github.com/KittyCAD/modeling-app/pull/4826
# Changes to KCL stdlib
- `line(point, sketch, tag)` and `lineTo(point, sketch, tag)` are combined into `line(@sketch, end?, endAbsolute?, tag?)`
- `close(sketch, tag?)` is now `close(@sketch, tag?)`
- `extrude(length, sketch)` is now `extrude(@sketch, length)`
Note that if a parameter starts with `@` like `@sketch`, it doesn't have any label when called, so you call it like this:
```
sketch = startSketchAt([0, 0])
line(sketch, end = [3, 3], tag = $hi)
```
Note also that if you're using a `|>` pipeline, you can omit the `@` argument and it will be assumed to be the LHS of the `|>`. So the above could be written as
```
sketch = startSketchAt([0, 0])
|> line(end = [3, 3], tag = $hi)
```
Also changes frontend tests to use KittyCAD/kcl-samples#139 instead of its main
The regex find-and-replace I use for migrating code (note these don't work with multi-line expressions) are:
```
line\(([^=]*), %\)
line(end = $1)
line\((.*), %, (.*)\)
line(end = $1, tag = $2)
lineTo\((.*), %\)
line(endAbsolute = $1)
lineTo\((.*), %, (.*)\)
line(endAbsolute = $1, tag = $2)
extrude\((.*), %\)
extrude(length = $1)
extrude\(([^=]*), ([a-zA-Z0-9]+)\)
extrude($2, length = $1)
close\(%, (.*)\)
close(tag = $1)
```
# Selected notes from commits before I squash them all
* Fix test 'yRelative to horizontal distance'
Fixes:
- Make a lineTo helper
- Fix pathToNode to go through the labeled arg .arg property
* Fix test by changing lookups into transformMap
Parts of the code assumed that `line` is always a relative call. But
actually now it might be absolute, if it's got an `endAbsolute` parameter.
So, change whether to look up `line` or `lineTo` and the relevant absolute
or relative line types based on that parameter.
* Stop asserting on exact source ranges
When I changed line to kwargs, all the source ranges we assert on became
slightly different. I find these assertions to be very very low value.
So I'm removing them.
* Fix more tests: getConstraintType calls weren't checking if the
'line' fn was absolute or relative.
* Fixed another queryAst test
There were 2 problems:
- Test was looking for the old style of `line` call to choose an offset
for pathToNode
- Test assumed that the `tag` param was always the third one, but in
a kwarg call, you have to look it up by label
* Fix test: traverse was not handling CallExpressionKw
* Fix another test, addTagKw
addTag helper was not aware of kw args.
* Convert close from positional to kwargs
If the close() call has 0 args, or a single unlabeled arg, the parser
interprets it as a CallExpression (positional) not a CallExpressionKw.
But then if a codemod wants to add a tag to it, it tries adding a kwarg
called 'tag', which fails because the CallExpression doesn't need
kwargs inserted into it.
The fix is: change the node from CallExpression to CallExpressionKw, and
update getNodeFromPath to take a 'replacement' arg, so we can replace
the old node with the new node in the AST.
* Fix the last test
Test was looking for `lineTo` as a substring of the input KCL program.
But there's no more lineTo function, so I changed it to look for
line() with an endAbsolute arg, which is the new equivalent.
Also changed the getConstraintInfo code to look up the lineTo if using
line with endAbsolute.
* Fix many bad regex find-replaces
I wrote a regex find-and-replace which converted `line` calls from
positional to keyword calls. But it was accidentally applied to more
places than it should be, for example, angledLine, xLine and yLine calls.
Fixes this.
* Fixes test 'Basic sketch › code pane closed at start'
Problem was, the getNodeFromPath call might not actually find a callExpressionKw,
it might find a callExpression. So the `giveSketchFnCallTag` thought
it was modifying a kwargs call, but it was actually modifying a positional
call.
This meant it tried to push a labeled argument in, rather than a normal
arg, and a lot of other problems. Fixed by doing runtime typechecking.
* Fix: Optional args given with wrong type were silently ignored
Optional args don't have to be given. But if the user gives them, they
should be the right type.
Bug: if the KCL interpreter found an optional arg, which was given, but
was the wrong type, it would ignore it and pretend the arg was never
given at all. This was confusing for users.
Fix: Now if you give an optional arg, but it's the wrong type, KCL will
emit a type error just like it would for a mandatory argument.
---------
Signed-off-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Kevin Nadro <kevin@zoo.dev>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2025-02-04 08:31:43 -06:00
|
|
|
CallExpressionKw,
|
2024-12-06 14:56:53 -08:00
|
|
|
clearSceneAndBustCache,
|
2024-10-09 19:38:40 -04:00
|
|
|
emptyExecState,
|
|
|
|
ExecState,
|
2025-02-12 15:55:34 -08:00
|
|
|
getKclVersion,
|
2023-10-11 13:36:54 +11:00
|
|
|
initPromise,
|
2025-02-12 10:22:56 +13:00
|
|
|
KclValue,
|
2023-10-11 13:36:54 +11:00
|
|
|
parse,
|
|
|
|
PathToNode,
|
|
|
|
Program,
|
|
|
|
recast,
|
2024-07-04 01:19:24 -04:00
|
|
|
SourceRange,
|
2025-01-17 14:34:36 -05:00
|
|
|
topLevelRange,
|
2025-02-12 10:22:56 +13:00
|
|
|
VariableMap,
|
2023-10-11 13:36:54 +11:00
|
|
|
} from 'lang/wasm'
|
2025-02-06 12:37:13 -05:00
|
|
|
import { getNodeFromPath, getSettingsAnnotation } from './queryAst'
|
2024-05-06 19:28:30 +10:00
|
|
|
import { codeManager, editorManager, sceneInfra } from 'lib/singletons'
|
2024-07-29 08:41:02 -07:00
|
|
|
import { Diagnostic } from '@codemirror/lint'
|
2024-11-07 17:23:03 -05:00
|
|
|
import { markOnce } from 'lib/performance'
|
2024-10-30 16:52:17 -04:00
|
|
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
2024-12-20 13:39:06 +11:00
|
|
|
import {
|
|
|
|
EntityType_type,
|
|
|
|
ModelingCmdReq_type,
|
|
|
|
} from '@kittycad/lib/dist/types/src/models'
|
2024-12-20 16:19:59 -05:00
|
|
|
import { Operation } from 'wasm-lib/kcl/bindings/Operation'
|
2025-02-06 12:37:13 -05:00
|
|
|
import { KclSettingsAnnotation } from 'lib/settings/settingsTypes'
|
2023-10-11 13:36:54 +11:00
|
|
|
|
2024-08-14 08:49:00 -07:00
|
|
|
interface ExecuteArgs {
|
2024-10-30 16:52:17 -04:00
|
|
|
ast?: Node<Program>
|
2024-08-14 08:49:00 -07:00
|
|
|
zoomToFit?: boolean
|
|
|
|
executionId?: number
|
|
|
|
zoomOnRangeAndType?: {
|
|
|
|
range: SourceRange
|
|
|
|
type: string
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-22 16:55:30 +11:00
|
|
|
export class KclManager {
|
2024-10-30 16:52:17 -04:00
|
|
|
private _ast: Node<Program> = {
|
2023-10-11 13:36:54 +11:00
|
|
|
body: [],
|
2024-11-26 16:39:57 +13:00
|
|
|
shebang: null,
|
2023-10-11 13:36:54 +11:00
|
|
|
start: 0,
|
|
|
|
end: 0,
|
2024-11-07 11:23:41 -05:00
|
|
|
moduleId: 0,
|
2023-10-11 13:36:54 +11:00
|
|
|
nonCodeMeta: {
|
|
|
|
nonCodeNodes: {},
|
2024-10-30 16:52:17 -04:00
|
|
|
startNodes: [],
|
2023-10-11 13:36:54 +11:00
|
|
|
},
|
2025-02-13 16:17:09 +13:00
|
|
|
innerAttrs: [],
|
|
|
|
outerAttrs: [],
|
2023-10-11 13:36:54 +11:00
|
|
|
}
|
2024-10-09 19:38:40 -04:00
|
|
|
private _execState: ExecState = emptyExecState()
|
2025-02-12 10:22:56 +13:00
|
|
|
private _variables: VariableMap = {}
|
|
|
|
lastSuccessfulVariables: VariableMap = {}
|
2024-12-20 16:19:59 -05:00
|
|
|
lastSuccessfulOperations: Operation[] = []
|
2023-10-11 13:36:54 +11:00
|
|
|
private _logs: string[] = []
|
2024-12-20 16:19:59 -05:00
|
|
|
private _errors: KCLError[] = []
|
2024-12-06 13:57:31 +13:00
|
|
|
private _diagnostics: Diagnostic[] = []
|
2023-10-11 13:36:54 +11:00
|
|
|
private _isExecuting = false
|
2024-08-14 08:49:00 -07:00
|
|
|
private _executeIsStale: ExecuteArgs | null = null
|
2023-10-17 12:07:48 +11:00
|
|
|
private _wasmInitFailed = true
|
2024-12-06 13:57:31 +13:00
|
|
|
private _hasErrors = false
|
2024-12-06 14:56:53 -08:00
|
|
|
private _switchedFiles = false
|
2025-02-06 12:37:13 -05:00
|
|
|
private _fileSettings: KclSettingsAnnotation = {}
|
2025-02-12 15:55:34 -08:00
|
|
|
private _kclVersion: string | undefined = undefined
|
2023-10-11 13:36:54 +11:00
|
|
|
|
|
|
|
engineCommandManager: EngineCommandManager
|
|
|
|
|
2023-10-17 12:07:48 +11:00
|
|
|
private _isExecutingCallback: (arg: boolean) => void = () => {}
|
2024-10-30 16:52:17 -04:00
|
|
|
private _astCallBack: (arg: Node<Program>) => void = () => {}
|
2025-02-12 10:22:56 +13:00
|
|
|
private _variablesCallBack: (arg: {
|
|
|
|
[key in string]?: KclValue | undefined
|
|
|
|
}) => void = () => {}
|
2023-10-11 13:36:54 +11:00
|
|
|
private _logsCallBack: (arg: string[]) => void = () => {}
|
2024-12-20 16:19:59 -05:00
|
|
|
private _kclErrorsCallBack: (errors: KCLError[]) => void = () => {}
|
|
|
|
private _diagnosticsCallback: (errors: Diagnostic[]) => void = () => {}
|
2023-10-17 12:07:48 +11:00
|
|
|
private _wasmInitFailedCallback: (arg: boolean) => void = () => {}
|
2023-10-18 08:03:02 +11:00
|
|
|
private _executeCallback: () => void = () => {}
|
2023-10-11 13:36:54 +11:00
|
|
|
|
|
|
|
get ast() {
|
|
|
|
return this._ast
|
|
|
|
}
|
|
|
|
set ast(ast) {
|
|
|
|
this._ast = ast
|
|
|
|
this._astCallBack(ast)
|
|
|
|
}
|
|
|
|
|
2024-12-06 14:56:53 -08:00
|
|
|
set switchedFiles(switchedFiles: boolean) {
|
|
|
|
this._switchedFiles = switchedFiles
|
|
|
|
}
|
|
|
|
|
2025-02-12 10:22:56 +13:00
|
|
|
get variables() {
|
|
|
|
return this._variables
|
2023-10-11 13:36:54 +11:00
|
|
|
}
|
2024-10-09 19:38:40 -04:00
|
|
|
// This is private because callers should be setting the entire execState.
|
2025-02-12 10:22:56 +13:00
|
|
|
private set variables(variables) {
|
|
|
|
this._variables = variables
|
|
|
|
this._variablesCallBack(variables)
|
2023-10-11 13:36:54 +11:00
|
|
|
}
|
|
|
|
|
2024-12-05 19:51:06 -08:00
|
|
|
private set execState(execState) {
|
2024-10-09 19:38:40 -04:00
|
|
|
this._execState = execState
|
2025-02-12 10:22:56 +13:00
|
|
|
this.variables = execState.variables
|
2024-10-09 19:38:40 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
get execState() {
|
|
|
|
return this._execState
|
|
|
|
}
|
|
|
|
|
2025-02-12 15:55:34 -08:00
|
|
|
// Get the kcl version from the wasm module
|
|
|
|
// and store it in the singleton
|
|
|
|
// so we don't waste time getting it multiple times
|
|
|
|
get kclVersion() {
|
|
|
|
if (this._kclVersion === undefined) {
|
|
|
|
this._kclVersion = getKclVersion()
|
|
|
|
}
|
|
|
|
return this._kclVersion
|
|
|
|
}
|
|
|
|
|
2024-12-20 16:19:59 -05:00
|
|
|
get errors() {
|
|
|
|
return this._errors
|
|
|
|
}
|
|
|
|
set errors(errors) {
|
|
|
|
this._errors = errors
|
|
|
|
this._kclErrorsCallBack(errors)
|
|
|
|
}
|
2023-10-11 13:36:54 +11:00
|
|
|
get logs() {
|
|
|
|
return this._logs
|
|
|
|
}
|
|
|
|
set logs(logs) {
|
|
|
|
this._logs = logs
|
|
|
|
this._logsCallBack(logs)
|
|
|
|
}
|
|
|
|
|
2024-12-06 13:57:31 +13:00
|
|
|
get diagnostics() {
|
|
|
|
return this._diagnostics
|
2024-07-29 08:41:02 -07:00
|
|
|
}
|
|
|
|
|
2024-12-06 13:57:31 +13:00
|
|
|
set diagnostics(ds) {
|
|
|
|
if (ds === this._diagnostics) return
|
|
|
|
this._diagnostics = ds
|
|
|
|
this.setDiagnosticsForCurrentErrors()
|
2024-07-29 08:41:02 -07:00
|
|
|
}
|
|
|
|
|
2024-12-06 13:57:31 +13:00
|
|
|
addDiagnostics(ds: Diagnostic[]) {
|
|
|
|
if (ds.length === 0) return
|
|
|
|
this.diagnostics = this.diagnostics.concat(ds)
|
2024-08-04 15:16:34 -07:00
|
|
|
}
|
|
|
|
|
2024-12-06 13:57:31 +13:00
|
|
|
hasErrors(): boolean {
|
|
|
|
return this._hasErrors
|
2023-10-11 13:36:54 +11:00
|
|
|
}
|
|
|
|
|
2024-12-06 13:57:31 +13:00
|
|
|
setDiagnosticsForCurrentErrors() {
|
|
|
|
editorManager?.setDiagnostics(this.diagnostics)
|
2024-12-20 16:19:59 -05:00
|
|
|
this._diagnosticsCallback(this.diagnostics)
|
2024-07-29 08:41:02 -07:00
|
|
|
}
|
|
|
|
|
2023-10-11 13:36:54 +11:00
|
|
|
get isExecuting() {
|
|
|
|
return this._isExecuting
|
|
|
|
}
|
2024-08-30 05:14:24 -05:00
|
|
|
|
2023-10-11 13:36:54 +11:00
|
|
|
set isExecuting(isExecuting) {
|
|
|
|
this._isExecuting = isExecuting
|
2024-08-14 08:49:00 -07:00
|
|
|
// If we have finished executing, but the execute is stale, we should
|
|
|
|
// execute again.
|
|
|
|
if (!isExecuting && this.executeIsStale) {
|
|
|
|
const args = this.executeIsStale
|
|
|
|
this.executeIsStale = null
|
2024-09-09 18:17:45 -04:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
2024-08-14 08:49:00 -07:00
|
|
|
this.executeAst(args)
|
|
|
|
}
|
2023-10-11 13:36:54 +11:00
|
|
|
this._isExecutingCallback(isExecuting)
|
|
|
|
}
|
|
|
|
|
2024-08-14 08:49:00 -07:00
|
|
|
get executeIsStale() {
|
|
|
|
return this._executeIsStale
|
|
|
|
}
|
|
|
|
|
|
|
|
set executeIsStale(executeIsStale) {
|
|
|
|
this._executeIsStale = executeIsStale
|
|
|
|
}
|
|
|
|
|
2023-10-17 12:07:48 +11:00
|
|
|
get wasmInitFailed() {
|
|
|
|
return this._wasmInitFailed
|
|
|
|
}
|
|
|
|
set wasmInitFailed(wasmInitFailed) {
|
|
|
|
this._wasmInitFailed = wasmInitFailed
|
|
|
|
this._wasmInitFailedCallback(wasmInitFailed)
|
|
|
|
}
|
|
|
|
|
2023-10-11 13:36:54 +11:00
|
|
|
constructor(engineCommandManager: EngineCommandManager) {
|
|
|
|
this.engineCommandManager = engineCommandManager
|
2023-11-06 11:49:13 +11:00
|
|
|
|
2024-09-09 18:17:45 -04:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
2024-12-06 14:56:53 -08:00
|
|
|
this.ensureWasmInit().then(async () => {
|
|
|
|
await this.safeParse(codeManager.code).then((ast) => {
|
|
|
|
if (ast) {
|
|
|
|
this.ast = ast
|
|
|
|
}
|
|
|
|
})
|
2024-02-11 12:59:00 +11:00
|
|
|
})
|
2023-10-11 13:36:54 +11:00
|
|
|
}
|
2024-04-17 20:18:07 -07:00
|
|
|
|
2023-10-11 13:36:54 +11:00
|
|
|
registerCallBacks({
|
2025-02-12 10:22:56 +13:00
|
|
|
setVariables,
|
2023-10-11 13:36:54 +11:00
|
|
|
setAst,
|
|
|
|
setLogs,
|
2024-12-20 16:19:59 -05:00
|
|
|
setErrors,
|
|
|
|
setDiagnostics,
|
2023-10-11 13:36:54 +11:00
|
|
|
setIsExecuting,
|
2023-10-17 12:07:48 +11:00
|
|
|
setWasmInitFailed,
|
2023-10-11 13:36:54 +11:00
|
|
|
}: {
|
2025-02-12 10:22:56 +13:00
|
|
|
setVariables: (arg: VariableMap) => void
|
2024-10-30 16:52:17 -04:00
|
|
|
setAst: (arg: Node<Program>) => void
|
2023-10-11 13:36:54 +11:00
|
|
|
setLogs: (arg: string[]) => void
|
2024-12-20 16:19:59 -05:00
|
|
|
setErrors: (errors: KCLError[]) => void
|
|
|
|
setDiagnostics: (errors: Diagnostic[]) => void
|
2023-10-11 13:36:54 +11:00
|
|
|
setIsExecuting: (arg: boolean) => void
|
2023-10-17 12:07:48 +11:00
|
|
|
setWasmInitFailed: (arg: boolean) => void
|
2023-10-11 13:36:54 +11:00
|
|
|
}) {
|
2025-02-12 10:22:56 +13:00
|
|
|
this._variablesCallBack = setVariables
|
2023-10-11 13:36:54 +11:00
|
|
|
this._astCallBack = setAst
|
|
|
|
this._logsCallBack = setLogs
|
2024-12-20 16:19:59 -05:00
|
|
|
this._kclErrorsCallBack = setErrors
|
|
|
|
this._diagnosticsCallback = setDiagnostics
|
2023-10-11 13:36:54 +11:00
|
|
|
this._isExecutingCallback = setIsExecuting
|
2023-10-17 12:07:48 +11:00
|
|
|
this._wasmInitFailedCallback = setWasmInitFailed
|
|
|
|
}
|
2023-10-18 08:03:02 +11:00
|
|
|
registerExecuteCallback(callback: () => void) {
|
|
|
|
this._executeCallback = callback
|
|
|
|
}
|
2023-10-17 12:07:48 +11:00
|
|
|
|
2024-06-19 16:15:22 -04:00
|
|
|
clearAst() {
|
|
|
|
this._ast = {
|
|
|
|
body: [],
|
2024-11-26 16:39:57 +13:00
|
|
|
shebang: null,
|
2024-06-19 16:15:22 -04:00
|
|
|
start: 0,
|
|
|
|
end: 0,
|
2024-11-07 11:23:41 -05:00
|
|
|
moduleId: 0,
|
2024-06-19 16:15:22 -04:00
|
|
|
nonCodeMeta: {
|
|
|
|
nonCodeNodes: {},
|
2024-10-30 16:52:17 -04:00
|
|
|
startNodes: [],
|
2024-06-19 16:15:22 -04:00
|
|
|
},
|
2025-02-13 16:17:09 +13:00
|
|
|
innerAttrs: [],
|
|
|
|
outerAttrs: [],
|
2024-06-19 16:15:22 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-06 14:56:53 -08:00
|
|
|
// (jess) I'm not in love with this, but it ensures we clear the scene and
|
|
|
|
// bust the cache on
|
|
|
|
// errors from parsing when opening new files.
|
|
|
|
// Why not just clear the cache on all parse errors, you ask? well its actually
|
|
|
|
// really nice to keep the cache on parse errors within the same file, and
|
|
|
|
// only bust on engine errors esp if they take a long time to execute and
|
|
|
|
// you hit the wrong key!
|
|
|
|
private async checkIfSwitchedFilesShouldClear() {
|
|
|
|
// If we were switching files and we hit an error on parse we need to bust
|
|
|
|
// the cache and clear the scene.
|
|
|
|
if (this._hasErrors && this._switchedFiles) {
|
|
|
|
await clearSceneAndBustCache(this.engineCommandManager)
|
|
|
|
} else if (this._switchedFiles) {
|
|
|
|
// Reset the switched files boolean.
|
|
|
|
this._switchedFiles = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async safeParse(code: string): Promise<Node<Program> | null> {
|
2024-12-06 13:57:31 +13:00
|
|
|
const result = parse(code)
|
|
|
|
this.diagnostics = []
|
|
|
|
this._hasErrors = false
|
|
|
|
|
|
|
|
if (err(result)) {
|
|
|
|
const kclerror: KCLError = result as KCLError
|
|
|
|
this.diagnostics = kclErrorsToDiagnostics([kclerror])
|
|
|
|
this._hasErrors = true
|
2024-12-06 14:56:53 -08:00
|
|
|
|
|
|
|
await this.checkIfSwitchedFilesShouldClear()
|
2024-12-06 13:57:31 +13:00
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
|
|
|
this.addDiagnostics(complilationErrorsToDiagnostics(result.errors))
|
|
|
|
this.addDiagnostics(complilationErrorsToDiagnostics(result.warnings))
|
|
|
|
if (result.errors.length > 0) {
|
|
|
|
this._hasErrors = true
|
|
|
|
|
2024-12-06 14:56:53 -08:00
|
|
|
await this.checkIfSwitchedFilesShouldClear()
|
2024-12-06 13:57:31 +13:00
|
|
|
return null
|
|
|
|
}
|
2024-06-24 11:45:40 -04:00
|
|
|
|
2024-12-06 13:57:31 +13:00
|
|
|
return result.program
|
2023-11-06 17:47:45 +11:00
|
|
|
}
|
|
|
|
|
2023-10-17 12:07:48 +11:00
|
|
|
async ensureWasmInit() {
|
|
|
|
try {
|
|
|
|
await initPromise
|
|
|
|
if (this.wasmInitFailed) {
|
|
|
|
this.wasmInitFailed = false
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
this.wasmInitFailed = true
|
|
|
|
}
|
2023-10-11 13:36:54 +11:00
|
|
|
}
|
|
|
|
|
2024-02-19 09:23:18 +11:00
|
|
|
private _cancelTokens: Map<number, boolean> = new Map()
|
|
|
|
|
2024-04-17 20:18:07 -07:00
|
|
|
// This NEVER updates the code, if you want to update the code DO NOT add to
|
|
|
|
// this function, too many other things that don't want it exist.
|
|
|
|
// just call to codeManager from wherever you want in other files.
|
2024-08-14 08:49:00 -07:00
|
|
|
async executeAst(args: ExecuteArgs = {}): Promise<void> {
|
|
|
|
if (this.isExecuting) {
|
|
|
|
this.executeIsStale = args
|
2024-08-30 05:14:24 -05:00
|
|
|
|
|
|
|
// The previous execteAst will be rejected and cleaned up. The execution will be marked as stale.
|
|
|
|
// A new executeAst will start.
|
|
|
|
this.engineCommandManager.rejectAllModelingCommands(
|
|
|
|
EXECUTE_AST_INTERRUPT_ERROR_MESSAGE
|
|
|
|
)
|
2024-08-14 08:49:00 -07:00
|
|
|
// Exit early if we are already executing.
|
|
|
|
return
|
2024-07-04 01:19:24 -04:00
|
|
|
}
|
2024-08-14 08:49:00 -07:00
|
|
|
|
|
|
|
const ast = args.ast || this.ast
|
2024-11-07 17:23:03 -05:00
|
|
|
markOnce('code/startExecuteAst')
|
2024-08-14 08:49:00 -07:00
|
|
|
|
|
|
|
const currentExecutionId = args.executionId || Date.now()
|
2024-02-19 09:23:18 +11:00
|
|
|
this._cancelTokens.set(currentExecutionId, false)
|
|
|
|
|
2023-10-11 13:36:54 +11:00
|
|
|
this.isExecuting = true
|
2024-02-26 21:02:33 +11:00
|
|
|
await this.ensureWasmInit()
|
2024-10-09 19:38:40 -04:00
|
|
|
const { logs, errors, execState, isInterrupted } = await executeAst({
|
2023-10-11 13:36:54 +11:00
|
|
|
ast,
|
2025-01-28 15:43:39 -08:00
|
|
|
path: codeManager.currentFilePath || undefined,
|
2023-10-11 13:36:54 +11:00
|
|
|
engineCommandManager: this.engineCommandManager,
|
2025-02-12 10:22:56 +13:00
|
|
|
isMock: false,
|
2023-10-11 13:36:54 +11:00
|
|
|
})
|
2024-06-26 00:04:52 -04:00
|
|
|
|
2024-08-30 05:14:24 -05:00
|
|
|
// Program was not interrupted, setup the scene
|
|
|
|
// Do not send send scene commands if the program was interrupted, go to clean up
|
|
|
|
if (!isInterrupted) {
|
2024-12-06 13:57:31 +13:00
|
|
|
this.addDiagnostics(await lintAst({ ast: ast }))
|
2024-12-20 13:39:06 +11:00
|
|
|
setSelectionFilterToDefault(this.engineCommandManager)
|
2024-08-30 05:14:24 -05:00
|
|
|
|
|
|
|
if (args.zoomToFit) {
|
|
|
|
let zoomObjectId: string | undefined = ''
|
|
|
|
if (args.zoomOnRangeAndType) {
|
|
|
|
zoomObjectId = this.engineCommandManager?.mapRangeToObjectId(
|
|
|
|
args.zoomOnRangeAndType.range,
|
|
|
|
args.zoomOnRangeAndType.type
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
await this.engineCommandManager.sendSceneCommand({
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
cmd: {
|
|
|
|
type: 'zoom_to_fit',
|
|
|
|
object_ids: zoomObjectId ? [zoomObjectId] : [], // leave empty to zoom to all objects
|
|
|
|
padding: 0.1, // padding around the objects
|
2024-10-04 13:47:44 -07:00
|
|
|
animated: false, // don't animate the zoom for now
|
2024-08-30 05:14:24 -05:00
|
|
|
},
|
|
|
|
})
|
2024-07-04 01:19:24 -04:00
|
|
|
}
|
2024-05-24 12:32:15 -07:00
|
|
|
}
|
|
|
|
|
2023-10-11 13:36:54 +11:00
|
|
|
this.isExecuting = false
|
2024-08-14 08:49:00 -07:00
|
|
|
|
2024-02-19 09:23:18 +11:00
|
|
|
// Check the cancellation token for this execution before applying side effects
|
|
|
|
if (this._cancelTokens.get(currentExecutionId)) {
|
|
|
|
this._cancelTokens.delete(currentExecutionId)
|
|
|
|
return
|
|
|
|
}
|
2024-10-09 10:33:20 -04:00
|
|
|
|
|
|
|
// Exit sketch mode if the AST is empty
|
|
|
|
if (this._isAstEmpty(ast)) {
|
|
|
|
await this.disableSketchMode()
|
|
|
|
}
|
|
|
|
|
2025-02-06 12:37:13 -05:00
|
|
|
let fileSettings = getSettingsAnnotation(ast)
|
|
|
|
if (err(fileSettings)) {
|
|
|
|
console.error(fileSettings)
|
|
|
|
fileSettings = {}
|
|
|
|
}
|
|
|
|
this.fileSettings = fileSettings
|
|
|
|
|
2023-11-08 12:27:43 +11:00
|
|
|
this.logs = logs
|
2024-12-20 16:19:59 -05:00
|
|
|
this.errors = errors
|
2024-08-30 05:14:24 -05:00
|
|
|
// Do not add the errors since the program was interrupted and the error is not a real KCL error
|
2024-12-06 13:57:31 +13:00
|
|
|
this.addDiagnostics(isInterrupted ? [] : kclErrorsToDiagnostics(errors))
|
2024-10-09 19:38:40 -04:00
|
|
|
this.execState = execState
|
2024-10-02 13:15:40 +10:00
|
|
|
if (!errors.length) {
|
2025-02-12 10:22:56 +13:00
|
|
|
this.lastSuccessfulVariables = execState.variables
|
2024-12-20 16:19:59 -05:00
|
|
|
this.lastSuccessfulOperations = execState.operations
|
2024-10-02 13:15:40 +10:00
|
|
|
}
|
2023-11-08 12:27:43 +11:00
|
|
|
this.ast = { ...ast }
|
2025-02-12 10:22:56 +13:00
|
|
|
// updateArtifactGraph relies on updated executeState/variables
|
2025-01-17 14:34:36 -05:00
|
|
|
this.engineCommandManager.updateArtifactGraph(execState.artifactGraph)
|
2023-10-18 08:03:02 +11:00
|
|
|
this._executeCallback()
|
2024-12-16 10:34:11 -05:00
|
|
|
if (!isInterrupted) {
|
2024-12-12 18:06:26 -08:00
|
|
|
sceneInfra.modelingSend({ type: 'code edit during sketch' })
|
2024-12-16 10:34:11 -05:00
|
|
|
}
|
2024-03-22 16:55:30 +11:00
|
|
|
this.engineCommandManager.addCommandLog({
|
2023-11-24 08:59:24 +11:00
|
|
|
type: 'execution-done',
|
|
|
|
data: null,
|
|
|
|
})
|
2024-08-30 05:14:24 -05:00
|
|
|
|
2024-02-19 09:23:18 +11:00
|
|
|
this._cancelTokens.delete(currentExecutionId)
|
2024-11-07 17:23:03 -05:00
|
|
|
markOnce('code/endExecuteAst')
|
2023-10-11 13:36:54 +11:00
|
|
|
}
|
2025-01-08 10:58:41 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* This cleanup function is external and internal to the KclSingleton class.
|
|
|
|
* Since the WASM runtime can panic and the error cannot be caught in executeAst
|
|
|
|
* we need a global exception handler in exceptions.ts
|
|
|
|
* This file will interface with this cleanup as if it caught the original error
|
|
|
|
* to properly restore the TS application state.
|
|
|
|
*/
|
|
|
|
executeAstCleanUp() {
|
|
|
|
this.isExecuting = false
|
|
|
|
this.executeIsStale = null
|
|
|
|
this.engineCommandManager.addCommandLog({
|
|
|
|
type: 'execution-done',
|
|
|
|
data: null,
|
|
|
|
})
|
|
|
|
markOnce('code/endExecuteAst')
|
|
|
|
}
|
|
|
|
|
2024-04-17 20:18:07 -07:00
|
|
|
// NOTE: this always updates the code state and editor.
|
|
|
|
// DO NOT CALL THIS from codemirror ever.
|
2025-02-06 12:56:19 -05:00
|
|
|
async executeAstMock(ast: Program = this._ast) {
|
2023-10-17 12:07:48 +11:00
|
|
|
await this.ensureWasmInit()
|
2024-06-11 19:23:35 -04:00
|
|
|
|
2023-10-11 13:36:54 +11:00
|
|
|
const newCode = recast(ast)
|
2024-06-24 11:45:40 -04:00
|
|
|
if (err(newCode)) {
|
|
|
|
console.error(newCode)
|
|
|
|
return
|
|
|
|
}
|
2024-12-06 14:56:53 -08:00
|
|
|
const newAst = await this.safeParse(newCode)
|
2024-06-26 00:04:52 -04:00
|
|
|
if (!newAst) {
|
|
|
|
this.clearAst()
|
|
|
|
return
|
|
|
|
}
|
2023-10-11 13:36:54 +11:00
|
|
|
this._ast = { ...newAst }
|
|
|
|
|
2024-10-09 19:38:40 -04:00
|
|
|
const { logs, errors, execState } = await executeAst({
|
2023-10-11 13:36:54 +11:00
|
|
|
ast: newAst,
|
|
|
|
engineCommandManager: this.engineCommandManager,
|
2025-02-12 10:22:56 +13:00
|
|
|
isMock: true,
|
2023-10-11 13:36:54 +11:00
|
|
|
})
|
2024-06-26 00:04:52 -04:00
|
|
|
|
2023-10-11 13:36:54 +11:00
|
|
|
this._logs = logs
|
2024-12-06 13:57:31 +13:00
|
|
|
this.addDiagnostics(kclErrorsToDiagnostics(errors))
|
2025-02-15 00:57:04 +11:00
|
|
|
|
2024-10-09 19:38:40 -04:00
|
|
|
this._execState = execState
|
2025-02-12 10:22:56 +13:00
|
|
|
this._variables = execState.variables
|
2024-10-02 13:15:40 +10:00
|
|
|
if (!errors.length) {
|
2025-02-12 10:22:56 +13:00
|
|
|
this.lastSuccessfulVariables = execState.variables
|
2024-12-20 16:19:59 -05:00
|
|
|
this.lastSuccessfulOperations = execState.operations
|
2024-10-02 13:15:40 +10:00
|
|
|
}
|
2023-10-11 13:36:54 +11:00
|
|
|
}
|
2024-02-19 09:23:18 +11:00
|
|
|
cancelAllExecutions() {
|
|
|
|
this._cancelTokens.forEach((_, key) => {
|
|
|
|
this._cancelTokens.set(key, true)
|
|
|
|
})
|
2023-10-11 13:36:54 +11:00
|
|
|
}
|
2024-07-23 20:37:04 -04:00
|
|
|
async executeCode(zoomToFit?: boolean): Promise<void> {
|
2024-12-06 14:56:53 -08:00
|
|
|
const ast = await this.safeParse(codeManager.code)
|
2025-01-06 21:53:20 -05:00
|
|
|
|
2024-06-19 16:15:22 -04:00
|
|
|
if (!ast) {
|
|
|
|
this.clearAst()
|
|
|
|
return
|
|
|
|
}
|
2025-01-06 21:53:20 -05:00
|
|
|
|
|
|
|
zoomToFit = this.tryToZoomToFitOnCodeUpdate(ast, zoomToFit)
|
|
|
|
|
2024-04-18 12:40:05 -07:00
|
|
|
this.ast = { ...ast }
|
2024-08-14 08:49:00 -07:00
|
|
|
return this.executeAst({ zoomToFit })
|
2023-10-11 13:36:54 +11:00
|
|
|
}
|
2025-01-06 21:53:20 -05:00
|
|
|
/**
|
|
|
|
* This will override the zoom to fit to zoom into the model if the previous AST was empty.
|
|
|
|
* Workflows this improves,
|
|
|
|
* When someone comments the entire file then uncomments the entire file it zooms to the model
|
|
|
|
* When someone CRTL+A and deletes the code then adds the code back it zooms to the model
|
|
|
|
* When someone CRTL+A and copies new code into the editor it zooms to the model
|
|
|
|
*/
|
|
|
|
tryToZoomToFitOnCodeUpdate(
|
|
|
|
ast: Node<Program>,
|
|
|
|
zoomToFit: boolean | undefined
|
|
|
|
) {
|
|
|
|
const isAstEmpty = this._isAstEmpty(this._ast)
|
|
|
|
const isRequestedAstEmpty = this._isAstEmpty(ast)
|
|
|
|
|
|
|
|
// If the AST went from empty to not empty or
|
|
|
|
// If the user has all of the content selected and they copy new code in
|
|
|
|
if (
|
|
|
|
(isAstEmpty && !isRequestedAstEmpty) ||
|
|
|
|
editorManager.isAllTextSelected
|
|
|
|
) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
return zoomToFit
|
|
|
|
}
|
2024-12-06 14:56:53 -08:00
|
|
|
async format() {
|
2024-04-17 20:18:07 -07:00
|
|
|
const originalCode = codeManager.code
|
2024-12-06 14:56:53 -08:00
|
|
|
const ast = await this.safeParse(originalCode)
|
2024-06-19 16:15:22 -04:00
|
|
|
if (!ast) {
|
|
|
|
this.clearAst()
|
|
|
|
return
|
|
|
|
}
|
2024-04-17 20:18:07 -07:00
|
|
|
const code = recast(ast)
|
2024-06-24 11:45:40 -04:00
|
|
|
if (err(code)) {
|
|
|
|
console.error(code)
|
|
|
|
return
|
|
|
|
}
|
2024-04-17 20:18:07 -07:00
|
|
|
if (originalCode === code) return
|
|
|
|
|
|
|
|
// Update the code state and the editor.
|
|
|
|
codeManager.updateCodeStateEditor(code)
|
2024-11-08 17:16:46 -05:00
|
|
|
|
2024-11-16 16:49:44 -05:00
|
|
|
// Write back to the file system.
|
|
|
|
void codeManager.writeToFile().then(() => this.executeCode())
|
2023-10-11 13:36:54 +11:00
|
|
|
}
|
2023-11-01 17:34:54 -05:00
|
|
|
// There's overlapping responsibility between updateAst and executeAst.
|
2023-10-11 13:36:54 +11:00
|
|
|
// updateAst was added as it was used a lot before xState migration so makes the port easier.
|
|
|
|
// but should probably have think about which of the function to keep
|
2024-04-17 20:18:07 -07:00
|
|
|
// This always updates the code state and editor and writes to the file system.
|
2023-10-11 13:36:54 +11:00
|
|
|
async updateAst(
|
2024-10-30 16:52:17 -04:00
|
|
|
ast: Node<Program>,
|
2023-10-11 13:36:54 +11:00
|
|
|
execute: boolean,
|
|
|
|
optionalParams?: {
|
2024-09-23 08:07:31 +02:00
|
|
|
focusPath?: Array<PathToNode>
|
2024-07-04 01:19:24 -04:00
|
|
|
zoomToFit?: boolean
|
|
|
|
zoomOnRangeAndType?: {
|
|
|
|
range: SourceRange
|
|
|
|
type: string
|
|
|
|
}
|
2023-10-11 13:36:54 +11:00
|
|
|
}
|
2024-06-24 11:45:40 -04:00
|
|
|
): Promise<{
|
2024-10-30 16:52:17 -04:00
|
|
|
newAst: Node<Program>
|
2024-06-24 11:45:40 -04:00
|
|
|
selections?: Selections
|
|
|
|
}> {
|
2023-10-11 13:36:54 +11:00
|
|
|
const newCode = recast(ast)
|
2024-06-24 11:45:40 -04:00
|
|
|
if (err(newCode)) return Promise.reject(newCode)
|
|
|
|
|
2024-12-06 14:56:53 -08:00
|
|
|
const astWithUpdatedSource = await this.safeParse(newCode)
|
2024-06-24 11:45:40 -04:00
|
|
|
if (!astWithUpdatedSource) return Promise.reject(new Error('bad ast'))
|
|
|
|
let returnVal: Selections | undefined = undefined
|
2023-10-11 13:36:54 +11:00
|
|
|
|
|
|
|
if (optionalParams?.focusPath) {
|
|
|
|
returnVal = {
|
2024-11-21 15:04:30 +11:00
|
|
|
graphSelections: [],
|
2024-09-23 08:07:31 +02:00
|
|
|
otherSelections: [],
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const path of optionalParams.focusPath) {
|
|
|
|
const getNodeFromPathResult = getNodeFromPath<any>(
|
|
|
|
astWithUpdatedSource,
|
|
|
|
path
|
|
|
|
)
|
|
|
|
if (err(getNodeFromPathResult))
|
|
|
|
return Promise.reject(getNodeFromPathResult)
|
|
|
|
const { node } = getNodeFromPathResult
|
|
|
|
|
|
|
|
const { start, end } = node
|
|
|
|
|
|
|
|
if (!start || !end)
|
|
|
|
return {
|
|
|
|
selections: undefined,
|
|
|
|
newAst: astWithUpdatedSource,
|
|
|
|
}
|
|
|
|
|
|
|
|
if (start && end) {
|
2024-11-21 15:04:30 +11:00
|
|
|
returnVal.graphSelections.push({
|
|
|
|
codeRef: {
|
2025-01-17 14:34:36 -05:00
|
|
|
range: topLevelRange(start, end),
|
2024-11-21 15:04:30 +11:00
|
|
|
pathToNode: path,
|
|
|
|
},
|
2024-09-23 08:07:31 +02:00
|
|
|
})
|
|
|
|
}
|
2023-10-11 13:36:54 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (execute) {
|
2024-08-14 08:49:00 -07:00
|
|
|
await this.executeAst({
|
|
|
|
ast: astWithUpdatedSource,
|
|
|
|
zoomToFit: optionalParams?.zoomToFit,
|
|
|
|
zoomOnRangeAndType: optionalParams?.zoomOnRangeAndType,
|
|
|
|
})
|
2023-10-11 13:36:54 +11:00
|
|
|
} else {
|
|
|
|
// When we don't re-execute, we still want to update the program
|
|
|
|
// memory with the new ast. So we will hit the mock executor
|
2024-04-17 20:18:07 -07:00
|
|
|
// instead..
|
|
|
|
// Execute ast mock will update the code state and editor.
|
|
|
|
await this.executeAstMock(astWithUpdatedSource)
|
2023-10-11 13:36:54 +11:00
|
|
|
}
|
2024-06-24 11:45:40 -04:00
|
|
|
|
|
|
|
return { selections: returnVal, newAst: astWithUpdatedSource }
|
2023-10-11 13:36:54 +11:00
|
|
|
}
|
2024-02-17 07:04:24 +11:00
|
|
|
|
|
|
|
get defaultPlanes() {
|
|
|
|
return this?.engineCommandManager?.defaultPlanes
|
|
|
|
}
|
|
|
|
|
2024-06-18 16:08:41 +10:00
|
|
|
showPlanes(all = false) {
|
|
|
|
if (!this.defaultPlanes) return Promise.all([])
|
|
|
|
const thePromises = [
|
|
|
|
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xy, false),
|
|
|
|
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.yz, false),
|
|
|
|
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xz, false),
|
|
|
|
]
|
|
|
|
if (all) {
|
|
|
|
thePromises.push(
|
|
|
|
this.engineCommandManager.setPlaneHidden(
|
|
|
|
this.defaultPlanes.negXy,
|
|
|
|
false
|
|
|
|
)
|
|
|
|
)
|
|
|
|
thePromises.push(
|
|
|
|
this.engineCommandManager.setPlaneHidden(
|
|
|
|
this.defaultPlanes.negYz,
|
|
|
|
false
|
|
|
|
)
|
|
|
|
)
|
|
|
|
thePromises.push(
|
|
|
|
this.engineCommandManager.setPlaneHidden(
|
|
|
|
this.defaultPlanes.negXz,
|
|
|
|
false
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return Promise.all(thePromises)
|
2024-02-17 07:04:24 +11:00
|
|
|
}
|
|
|
|
|
2024-06-18 16:08:41 +10:00
|
|
|
hidePlanes(all = false) {
|
|
|
|
if (!this.defaultPlanes) return Promise.all([])
|
|
|
|
const thePromises = [
|
|
|
|
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xy, true),
|
|
|
|
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.yz, true),
|
|
|
|
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xz, true),
|
|
|
|
]
|
|
|
|
if (all) {
|
|
|
|
thePromises.push(
|
|
|
|
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.negXy, true)
|
|
|
|
)
|
|
|
|
thePromises.push(
|
|
|
|
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.negYz, true)
|
|
|
|
)
|
|
|
|
thePromises.push(
|
|
|
|
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.negXz, true)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return Promise.all(thePromises)
|
2024-02-17 07:04:24 +11:00
|
|
|
}
|
2024-11-26 11:36:14 -05:00
|
|
|
/** TODO: this function is hiding unawaited asynchronous work */
|
2024-12-20 13:39:06 +11:00
|
|
|
defaultSelectionFilter(selectionsToRestore?: Selections) {
|
|
|
|
setSelectionFilterToDefault(this.engineCommandManager, selectionsToRestore)
|
2024-11-26 11:36:14 -05:00
|
|
|
}
|
|
|
|
/** TODO: this function is hiding unawaited asynchronous work */
|
|
|
|
setSelectionFilter(filter: EntityType_type[]) {
|
|
|
|
setSelectionFilter(filter, this.engineCommandManager)
|
2024-05-21 05:55:34 +10:00
|
|
|
}
|
2024-10-09 10:33:20 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* We can send a single command of 'enable_sketch_mode' or send this in a batched request.
|
|
|
|
* When there is no code in the KCL editor we should be sending 'sketch_mode_disable' since any previous half finished
|
|
|
|
* code could leave the state of the application in sketch mode on the engine side.
|
|
|
|
*/
|
|
|
|
async disableSketchMode() {
|
|
|
|
await this.engineCommandManager.sendSceneCommand({
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
cmd: { type: 'sketch_mode_disable' },
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determines if there is no KCL code which means it is executing a blank KCL file
|
2024-10-30 16:52:17 -04:00
|
|
|
_isAstEmpty(ast: Node<Program>) {
|
2024-10-09 10:33:20 -04:00
|
|
|
return ast.start === 0 && ast.end === 0 && ast.body.length === 0
|
|
|
|
}
|
2025-02-06 12:37:13 -05:00
|
|
|
|
|
|
|
get fileSettings() {
|
|
|
|
return this._fileSettings
|
|
|
|
}
|
|
|
|
|
|
|
|
set fileSettings(settings: KclSettingsAnnotation) {
|
|
|
|
this._fileSettings = settings
|
|
|
|
}
|
2023-10-11 13:36:54 +11:00
|
|
|
}
|
|
|
|
|
2024-11-26 11:36:14 -05:00
|
|
|
const defaultSelectionFilter: EntityType_type[] = [
|
|
|
|
'face',
|
|
|
|
'edge',
|
|
|
|
'solid2d',
|
|
|
|
'curve',
|
|
|
|
'object',
|
|
|
|
]
|
|
|
|
|
|
|
|
/** TODO: This function is not synchronous but is currently treated as such */
|
|
|
|
function setSelectionFilterToDefault(
|
2024-12-20 13:39:06 +11:00
|
|
|
engineCommandManager: EngineCommandManager,
|
|
|
|
selectionsToRestore?: Selections
|
2024-03-22 16:55:30 +11:00
|
|
|
) {
|
2024-09-09 18:17:45 -04:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
2024-12-20 13:39:06 +11:00
|
|
|
setSelectionFilter(
|
|
|
|
defaultSelectionFilter,
|
|
|
|
engineCommandManager,
|
|
|
|
selectionsToRestore
|
|
|
|
)
|
2024-11-26 11:36:14 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/** TODO: This function is not synchronous but is currently treated as such */
|
|
|
|
function setSelectionFilter(
|
|
|
|
filter: EntityType_type[],
|
2024-12-20 13:39:06 +11:00
|
|
|
engineCommandManager: EngineCommandManager,
|
|
|
|
selectionsToRestore?: Selections
|
2024-11-26 11:36:14 -05:00
|
|
|
) {
|
2024-12-20 13:39:06 +11:00
|
|
|
const { engineEvents } = selectionsToRestore
|
|
|
|
? handleSelectionBatch({
|
|
|
|
selections: selectionsToRestore,
|
|
|
|
})
|
|
|
|
: { engineEvents: undefined }
|
|
|
|
if (!selectionsToRestore || !engineEvents) {
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
|
|
engineCommandManager.sendSceneCommand({
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
cmd: {
|
|
|
|
type: 'set_selection_filter',
|
|
|
|
filter,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const modelingCmd: ModelingCmdReq_type[] = []
|
|
|
|
engineEvents.forEach((event) => {
|
|
|
|
if (event.type === 'modeling_cmd_req') {
|
|
|
|
modelingCmd.push({
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
cmd: event.cmd,
|
|
|
|
})
|
|
|
|
}
|
2024-11-26 11:36:14 -05:00
|
|
|
})
|
2024-12-20 13:39:06 +11:00
|
|
|
// batch is needed other wise the selection flickers.
|
|
|
|
engineCommandManager
|
|
|
|
.sendSceneCommand({
|
|
|
|
type: 'modeling_cmd_batch_req',
|
|
|
|
batch_id: uuidv4(),
|
|
|
|
requests: [
|
|
|
|
{
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
cmd: {
|
|
|
|
type: 'set_selection_filter',
|
|
|
|
filter,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
...modelingCmd,
|
|
|
|
],
|
|
|
|
responses: false,
|
|
|
|
})
|
|
|
|
.catch(reportError)
|
2024-03-22 10:23:04 +11:00
|
|
|
}
|