Compare commits

..

1 Commits

Author SHA1 Message Date
8c6e62cfc4 bump and release kcl-lib 2024-12-03 15:21:34 -05:00
277 changed files with 5109 additions and 5086 deletions

View File

@ -362,17 +362,6 @@ jobs:
- name: List artifacts
run: "ls -R out"
- name: Set more complete nightly release notes
if: ${{ env.IS_NIGHTLY == 'true' }}
run: |
# Note: prefered going this way instead of a full clone in the checkout step,
# see https://github.com/actions/checkout/issues/1471
git fetch --prune --unshallow --tags
export TAG="nightly-${VERSION}"
export PREVIOUS_TAG=$(git describe --tags --match="nightly-v[0-9]*" --abbrev=0)
export NOTES=$(./scripts/get-nightly-changelog.sh)
yarn files:set-notes
- name: Authenticate to Google Cloud
if: ${{ env.IS_NIGHTLY == 'true' }}
uses: 'google-github-actions/auth@v2.1.7'
@ -393,14 +382,3 @@ jobs:
glob: '*'
parent: false
destination: 'dl.kittycad.io/releases/modeling-app/nightly'
- name: Tag nightly commit
if: ${{ env.IS_NIGHTLY == 'true' }}
uses: actions/github-script@v7
with:
script: |
const { VERSION } = process.env
const { owner, repo } = context.repo
const { sha } = context
const ref = `refs/tags/nightly-${VERSION}`
github.rest.git.createRef({ owner, repo, sha, ref })

View File

@ -99,7 +99,7 @@ yarn tron:start
This will start the application and hot-reload on changes.
Devtools can be opened with the usual Cmd-Opt-I (Mac) or Ctrl-Shift-I (Linux and Windows).
Devtools can be opened with the usual Cmd/Ctrl-Shift-I.
To build, run `yarn tron:package`.

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -518,10 +518,7 @@ test.describe('Editor tests', () => {
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
})
// TODO currently multiple source ranges are not supported
test.skip('error with 2 source ranges gets 2 diagnostics', async ({
page,
}) => {
test('error with 2 source ranges gets 2 diagnostics', async ({ page }) => {
const u = await getUtils(page)
await page.addInitScript(async () => {
localStorage.setItem(

View File

@ -6,7 +6,6 @@ export class ToolbarFixture {
public page: Page
extrudeButton!: Locator
loftButton!: Locator
offsetPlaneButton!: Locator
startSketchBtn!: Locator
lineBtn!: Locator
@ -27,7 +26,6 @@ export class ToolbarFixture {
reConstruct = (page: Page) => {
this.page = page
this.extrudeButton = page.getByTestId('extrude')
this.loftButton = page.getByTestId('loft')
this.offsetPlaneButton = page.getByTestId('plane-offset')
this.startSketchBtn = page.getByTestId('sketch')
this.lineBtn = page.getByTestId('line')

View File

@ -677,94 +677,3 @@ test(`Offset plane point-and-click`, async ({
await scene.expectPixelColor([74, 74, 74], testPoint, 15)
})
})
const loftPointAndClickCases = [
{ shouldPreselect: true },
{ shouldPreselect: false },
]
loftPointAndClickCases.forEach(({ shouldPreselect }) => {
test(`Loft point-and-click (preselected sketches: ${shouldPreselect})`, async ({
app,
page,
scene,
editor,
toolbar,
cmdBar,
}) => {
const initialCode = `sketch001 = startSketchOn('XZ')
|> circle({ center = [0, 0], radius = 30 }, %)
plane001 = offsetPlane('XZ', 50)
sketch002 = startSketchOn(plane001)
|> circle({ center = [0, 0], radius = 20 }, %)
`
await app.initialise(initialCode)
// One dumb hardcoded screen pixel value
const testPoint = { x: 575, y: 200 }
const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
const [clickOnSketch2] = scene.makeMouseHelpers(
testPoint.x,
testPoint.y + 80
)
const loftDeclaration = 'loft001 = loft([sketch001, sketch002])'
await test.step(`Look for the white of the sketch001 shape`, async () => {
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
})
async function selectSketches() {
await clickOnSketch1()
await page.keyboard.down('Shift')
await clickOnSketch2()
await app.page.waitForTimeout(500)
await page.keyboard.up('Shift')
}
if (!shouldPreselect) {
await test.step(`Go through the command bar flow without preselected sketches`, async () => {
await toolbar.loftButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'selection',
currentArgValue: '',
headerArguments: { Selection: '' },
highlightedHeaderArg: 'selection',
commandName: 'Loft',
})
await selectSketches()
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: { Selection: '2 faces' },
commandName: 'Loft',
})
await cmdBar.progressCmdBar()
})
} else {
await test.step(`Preselect the two sketches`, async () => {
await selectSketches()
})
await test.step(`Go through the command bar flow with preselected sketches`, async () => {
await toolbar.loftButton.click()
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: { Selection: '2 faces' },
commandName: 'Loft',
})
await cmdBar.progressCmdBar()
})
}
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
await editor.expectEditor.toContain(loftDeclaration)
await editor.expectState({
diagnostics: [],
activeLines: [loftDeclaration],
highlightedCode: '',
})
await scene.expectPixelColor([89, 89, 89], testPoint, 15)
})
})
})

View File

@ -550,7 +550,7 @@ sketch001 = startSketchAt([-0, -0])
const u = await getUtils(page)
// Constants and locators
const planeColor: [number, number, number] = [161, 220, 155]
const planeColor: [number, number, number] = [170, 220, 170]
const bgColor: [number, number, number] = [27, 27, 27]
const middlePixelIsColor = async (color: [number, number, number]) => {
return u.getGreatestPixDiff({ x: 600, y: 250 }, color)

View File

@ -7,8 +7,6 @@ try {
.split('\n')
.filter((line) => line && line.length > 1)
.forEach((line) => {
// Allow line comments.
if (line.trimStart().startsWith('#')) return
const [key, value] = line.split('=')
// prefer env vars over secrets file
secrets[key] = process.env[key] || (value as any).replaceAll('"', '')

View File

@ -943,110 +943,6 @@ sketch002 = startSketchOn(extrude001, 'END')
`.replace(/\s/g, '')
)
})
/* TODO: once we fix bug turn on.
test('empty-scene default-planes act as expected when spaces in file', async ({
page,
browserName,
}) => {
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
const XYPlanePoint = { x: 774, y: 116 } as const
const unHoveredColor: [number, number, number] = [47, 47, 93]
expect(
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
).toBeLessThan(8)
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
await page.waitForTimeout(200)
// color should not change for having been hovered
expect(
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
).toBeLessThan(8)
await u.openAndClearDebugPanel()
// Fill with spaces
await u.codeLocator.fill(`
`)
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
expect(
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
).toBeLessThan(8)
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
await page.waitForTimeout(200)
// color should not change for having been hovered
expect(
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
).toBeLessThan(8)
})
test('empty-scene default-planes act as expected when only code comments in file', async ({
page,
browserName,
}) => {
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
const XYPlanePoint = { x: 774, y: 116 } as const
const unHoveredColor: [number, number, number] = [47, 47, 93]
expect(
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
).toBeLessThan(8)
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
await page.waitForTimeout(200)
// color should not change for having been hovered
expect(
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
).toBeLessThan(8)
await u.openAndClearDebugPanel()
// Fill with spaces
await u.codeLocator.fill(`// this is a code comments ya nerds
`)
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
expect(
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
).toBeLessThan(8)
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
await page.waitForTimeout(200)
// color should not change for having been hovered
expect(
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
).toBeLessThan(8)
})*/
test('empty-scene default-planes act as expected', async ({
page,
browserName,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -23,7 +23,7 @@ test.describe('Test toggling perspective', () => {
y: screenHeight * 0.4,
}
const backgroundColor: [number, number, number] = [29, 29, 29]
const xzPlaneColor: [number, number, number] = [82, 55, 96]
const xzPlaneColor: [number, number, number] = [50, 50, 99]
const locationToHaveColor = async (color: [number, number, number]) => {
return u.getGreatestPixDiff(checkedScreenLocation, color)
}

View File

@ -212,7 +212,7 @@
"vite-tsconfig-paths": "^4.3.2",
"vitest": "^1.6.0",
"vitest-webgl-canvas-mock": "^1.1.0",
"wasm-pack": "^0.13.1",
"wasm-pack": "^0.13.0",
"ws": "^8.17.0",
"yarn": "^1.22.22"
}

View File

@ -149,6 +149,11 @@
"title": "Tire",
"description": "A tire is a critical component of a vehicle that provides the necessary traction and grip between the car and the road. It supports the vehicle's weight and absorbs shocks from road irregularities."
},
{
"file": "wall-e.kcl",
"title": "WALL-E",
"description": "An inquisitive trash compacting robot imagined by Disney Pixar Animation Studios"
},
{
"file": "washer.kcl",
"title": "Washer",

View File

@ -1,5 +0,0 @@
#!/bin/bash
echo "## What's Changed"
git log ${PREVIOUS_TAG}..HEAD --oneline --pretty=format:%s | grep -v Bump | awk '{print "* "toupper(substr($0,0,1))substr($0,2)}'
echo ""
echo "**Full Changelog**: https://github.com/KittyCAD/modeling-app/compare/${PREVIOUS_TAG}...${TAG}"

View File

@ -3,7 +3,7 @@ import { useHotKeyListener } from './hooks/useHotKeyListener'
import { Stream } from './components/Stream'
import { AppHeader } from './components/AppHeader'
import { useHotkeys } from 'react-hotkeys-hook'
import { useLoaderData, useLocation, useNavigate } from 'react-router-dom'
import { useLoaderData, useNavigate } from 'react-router-dom'
import { type IndexLoaderData } from 'lib/types'
import { PATHS } from 'lib/paths'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
@ -22,14 +22,7 @@ import Gizmo from 'components/Gizmo'
import { CoreDumpManager } from 'lib/coredump'
import { UnitsMenu } from 'components/UnitsMenu'
import { CameraProjectionToggle } from 'components/CameraProjectionToggle'
import { homeDefaultStatusBarItems } from 'components/statusBar/homeDefaultStatusBarItems'
import { StatusBar } from 'components/StatusBar'
import { useModelStateStatus } from 'components/ModelStateIndicator'
import { useNetworkHealthStatus } from 'components/NetworkHealthIndicator'
import { useModelingContext } from 'hooks/useModelingContext'
import { xStateValueToString } from 'lib/xStateValueToString'
import { maybeWriteToDisk } from 'lib/telemetry'
import { useNetworkMachineStatus } from 'components/NetworkMachineIndicator'
maybeWriteToDisk()
.then(() => {})
.catch(() => {})
@ -38,10 +31,11 @@ export function App() {
const { project, file } = useLoaderData() as IndexLoaderData
useRefreshSettings(PATHS.FILE + 'SETTINGS')
const navigate = useNavigate()
const location = useLocation()
const filePath = useAbsoluteFilePath()
const { onProjectOpen } = useLspContext()
const { state: modelingState, streamRef } = useModelingContext()
// We need the ref for the outermost div so we can screenshot the app for
// the coredump.
const ref = useRef<HTMLDivElement>(null)
const projectName = project?.name || null
const projectPath = project?.path || null
@ -83,44 +77,21 @@ export function App() {
useEngineConnectionSubscriptions()
return (
<div className="h-screen flex flex-col overflow-hidden select-none">
<div className="relative flex flex-1 flex-col" ref={streamRef}>
<AppHeader
className={'transition-opacity transition-duration-75 ' + paneOpacity}
project={{ project, file }}
enableMenu={true}
/>
<ModalContainer />
<ModelingSidebar paneOpacity={paneOpacity} />
<Stream />
{/* <CamToggle /> */}
<LowerRightControls coreDumpManager={coreDumpManager}>
<UnitsMenu />
<Gizmo />
<CameraProjectionToggle />
</LowerRightControls>
</div>
<StatusBar
globalItems={[
useNetworkHealthStatus(),
useNetworkMachineStatus(),
...homeDefaultStatusBarItems({ coreDumpManager, location }),
]}
localItems={[
{
id: 'modeling-state',
element: 'text',
label:
modelingState.value instanceof Object
? xStateValueToString(modelingState.value) ?? ''
: modelingState.value,
toolTip: {
children: 'The current state of the modeler',
},
},
useModelStateStatus(),
]}
<div className="relative h-full flex flex-col" ref={ref}>
<AppHeader
className={'transition-opacity transition-duration-75 ' + paneOpacity}
project={{ project, file }}
enableMenu={true}
/>
<ModalContainer />
<ModelingSidebar paneOpacity={paneOpacity} />
<Stream />
{/* <CamToggle /> */}
<LowerRightControls coreDumpManager={coreDumpManager}>
<UnitsMenu />
<Gizmo />
<CameraProjectionToggle />
</LowerRightControls>
</div>
)
}

View File

@ -155,6 +155,7 @@ export class CameraControls {
this.camera.zoom = camProps.zoom || 1
}
this.camera.updateProjectionMatrix()
console.log('doing this thing', camProps)
this.update(true)
}
@ -272,26 +273,14 @@ export class CameraControls {
camSettings.center.y,
camSettings.center.z
)
const orientation = new Quaternion(
const quat = new Quaternion(
camSettings.orientation.x,
camSettings.orientation.y,
camSettings.orientation.z,
camSettings.orientation.w
).invert()
const newUp = new Vector3(
camSettings.up.x,
camSettings.up.y,
camSettings.up.z
)
this.camera.quaternion.set(
orientation.x,
orientation.y,
orientation.z,
orientation.w
)
this.camera.up.copy(newUp)
this.camera.updateProjectionMatrix()
this.camera.up.copy(new Vector3(0, 1, 0).applyQuaternion(quat))
if (this.camera instanceof PerspectiveCamera && camSettings.ortho) {
this.useOrthographicCamera()
}
@ -1175,7 +1164,7 @@ export class CameraControls {
this.camera.updateProjectionMatrix()
}
if (this.syncDirection === 'clientToEngine' || forceUpdate) {
if (this.syncDirection === 'clientToEngine' || forceUpdate)
this.throttledUpdateEngineCamera({
quaternion: this.camera.quaternion,
position: this.camera.position,
@ -1183,7 +1172,6 @@ export class CameraControls {
isPerspective: this.isPerspective,
target: this.target,
})
}
this.deferReactUpdate(this.reactCameraProperties)
Object.values(this._camChangeCallbacks).forEach((cb) => cb())
}

View File

@ -29,9 +29,6 @@ import {
Expr,
parse,
recast,
defaultSourceRange,
resultIsOk,
ProgramMemory,
} from 'lang/wasm'
import { CustomIcon, CustomIconName } from 'components/CustomIcon'
import { ConstrainInfo } from 'lang/std/stdTypes'
@ -415,15 +412,14 @@ export async function deleteSegment({
if (err(modifiedAst)) return Promise.reject(modifiedAst)
const newCode = recast(modifiedAst)
const pResult = parse(newCode)
if (err(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
modifiedAst = pResult.program
modifiedAst = parse(newCode)
if (err(modifiedAst)) return Promise.reject(modifiedAst)
const testExecute = await executeAst({
ast: modifiedAst,
idGenerator: kclManager.execState.idGenerator,
useFakeExecutor: true,
engineCommandManager: engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride: ProgramMemory.empty(),
})
if (testExecute.errors.length) {
toast.error('Segment tag used outside of current Sketch. Could not delete.')
@ -594,9 +590,7 @@ const ConstraintSymbol = ({
if (err(_node)) return
const node = _node.node
const range: SourceRange = node
? [node.start, node.end, true]
: defaultSourceRange()
const range: SourceRange = node ? [node.start, node.end] : [0, 0]
if (_type === 'intersectionTag') return null
@ -618,7 +612,7 @@ const ConstraintSymbol = ({
editorManager.setHighlightRange([range])
}}
onMouseLeave={() => {
editorManager.setHighlightRange([defaultSourceRange()])
editorManager.setHighlightRange([[0, 0]])
}}
// disabled={isConstrained || !convertToVarEnabled}
// disabled={implicitDesc} TODO why does this change styles that are hard to override?
@ -633,12 +627,10 @@ const ConstraintSymbol = ({
})
} else if (isConstrained) {
try {
const pResult = parse(recast(kclManager.ast))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(pResult)
const parsed = parse(recast(kclManager.ast))
if (trap(parsed)) return Promise.reject(parsed)
const _node1 = getNodeFromPath<CallExpression>(
pResult.program!,
parsed,
pathToNode,
'CallExpression',
true

View File

@ -48,9 +48,6 @@ import {
VariableDeclarator,
sketchFromKclValue,
sketchFromKclValueOptional,
defaultSourceRange,
sourceRangeFromRust,
resultIsOk,
} from 'lang/wasm'
import {
engineCommandManager,
@ -498,9 +495,10 @@ export class SceneEntities {
const { execState } = await executeAst({
ast: truncatedAst,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
})
const programMemory = execState.memory
const sketch = sketchFromPathToNode({
@ -532,7 +530,7 @@ export class SceneEntities {
const segPathToNode = getNodePathFromSourceRange(
maybeModdedAst,
sourceRangeFromRust(sketch.start.__geoMeta.sourceRange)
sketch.start.__geoMeta.sourceRange
)
if (sketch?.paths?.[0]?.type !== 'Circle') {
const _profileStart = createProfileStartHandle({
@ -554,7 +552,7 @@ export class SceneEntities {
sketch.paths.forEach((segment, index) => {
let segPathToNode = getNodePathFromSourceRange(
maybeModdedAst,
sourceRangeFromRust(segment.__geoMeta.sourceRange)
segment.__geoMeta.sourceRange
)
if (
draftExpressionsIndices &&
@ -563,12 +561,12 @@ export class SceneEntities {
const previousSegment = sketch.paths[index - 1] || sketch.start
const previousSegmentPathToNode = getNodePathFromSourceRange(
maybeModdedAst,
sourceRangeFromRust(previousSegment.__geoMeta.sourceRange)
previousSegment.__geoMeta.sourceRange
)
const bodyIndex = previousSegmentPathToNode[1][0]
segPathToNode = getNodePathFromSourceRange(
truncatedAst,
sourceRangeFromRust(segment.__geoMeta.sourceRange)
segment.__geoMeta.sourceRange
)
segPathToNode[1][0] = bodyIndex
}
@ -577,10 +575,7 @@ export class SceneEntities {
index <= draftExpressionsIndices.end &&
index >= draftExpressionsIndices.start
const isSelected = selectionRanges?.graphSelections.some((selection) =>
isOverlap(
selection?.codeRef?.range,
sourceRangeFromRust(segment.__geoMeta.sourceRange)
)
isOverlap(selection?.codeRef?.range, segment.__geoMeta.sourceRange)
)
let seg: Group
@ -662,11 +657,13 @@ export class SceneEntities {
}
updateAstAndRejigSketch = async (
sketchPathToNode: PathToNode,
modifiedAst: Node<Program>,
modifiedAst: Node<Program> | Error,
forward: [number, number, number],
up: [number, number, number],
origin: [number, number, number]
) => {
if (err(modifiedAst)) return modifiedAst
const nextAst = await kclManager.updateAst(modifiedAst, false)
await this.tearDownSketch({ removeAxis: false })
sceneInfra.resetMouseListeners()
@ -724,9 +721,8 @@ export class SceneEntities {
pathToNode: sketchPathToNode,
})
if (trap(mod)) return Promise.reject(mod)
const pResult = parse(recast(mod.modifiedAst))
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
const modifiedAst = pResult.program
const modifiedAst = parse(recast(mod.modifiedAst))
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
const draftExpressionsIndices = { start: index, end: index }
@ -918,9 +914,9 @@ export class SceneEntities {
...getRectangleCallExpressions(rectangleOrigin, tags),
])
const pResult = parse(recast(_ast))
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
_ast = pResult.program
let _recastAst = parse(recast(_ast))
if (trap(_recastAst)) return Promise.reject(_recastAst)
_ast = _recastAst
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
sketchPathToNode,
@ -954,9 +950,10 @@ export class SceneEntities {
const { execState } = await executeAst({
ast: truncatedAst,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
})
const programMemory = execState.memory
this.sceneProgramMemory = programMemory
@ -1001,10 +998,9 @@ export class SceneEntities {
updateRectangleSketch(sketchInit, x, y, tags[0])
const newCode = recast(_ast)
const pResult = parse(newCode)
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(pResult)
_ast = pResult.program
let _recastAst = parse(newCode)
if (trap(_recastAst)) return
_ast = _recastAst
// Update the primary AST and unequip the rectangle tool
await kclManager.executeAstMock(_ast)
@ -1017,9 +1013,10 @@ export class SceneEntities {
const { execState } = await executeAst({
ast: _ast,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
})
const programMemory = execState.memory
@ -1074,9 +1071,9 @@ export class SceneEntities {
...getRectangleCallExpressions(rectangleOrigin, tags),
])
const pResult = parse(recast(_ast))
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
_ast = pResult.program
let _recastAst = parse(recast(_ast))
if (trap(_recastAst)) return Promise.reject(_recastAst)
_ast = _recastAst
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
sketchPathToNode,
@ -1117,9 +1114,10 @@ export class SceneEntities {
const { execState } = await executeAst({
ast: truncatedAst,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
})
const programMemory = execState.memory
this.sceneProgramMemory = programMemory
@ -1167,10 +1165,9 @@ export class SceneEntities {
rectangleOrigin[1]
)
const pResult = parse(recast(_ast))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(pResult)
_ast = pResult.program
let _recastAst = parse(recast(_ast))
if (trap(_recastAst)) return
_ast = _recastAst
// Update the primary AST and unequip the rectangle tool
await kclManager.executeAstMock(_ast)
@ -1183,9 +1180,10 @@ export class SceneEntities {
const { execState } = await executeAst({
ast: _ast,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
})
const programMemory = execState.memory
@ -1243,9 +1241,9 @@ export class SceneEntities {
]),
])
const pResult = parse(recast(_ast))
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
_ast = pResult.program
let _recastAst = parse(recast(_ast))
if (trap(_recastAst)) return Promise.reject(_recastAst)
_ast = _recastAst
// do a quick mock execution to get the program memory up-to-date
await kclManager.executeAstMock(_ast)
@ -1301,9 +1299,10 @@ export class SceneEntities {
const { execState } = await executeAst({
ast: modded,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
})
const programMemory = execState.memory
this.sceneProgramMemory = programMemory
@ -1366,10 +1365,9 @@ export class SceneEntities {
const newCode = recast(modded)
if (err(newCode)) return
const pResult = parse(newCode)
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(pResult)
_ast = pResult.program
let _recastAst = parse(newCode)
if (trap(_recastAst)) return Promise.reject(_recastAst)
_ast = _recastAst
// Update the primary AST and unequip the rectangle tool
await kclManager.executeAstMock(_ast)
@ -1662,7 +1660,7 @@ export class SceneEntities {
kclManager.programMemory,
{
type: 'sourceRange',
sourceRange: [node.start, node.end, true],
sourceRange: [node.start, node.end],
},
getChangeSketchInput()
)
@ -1685,9 +1683,10 @@ export class SceneEntities {
codeManager.updateCodeEditor(code)
const { execState } = await executeAst({
ast: truncatedAst,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
})
const programMemory = execState.memory
this.sceneProgramMemory = programMemory
@ -1751,7 +1750,7 @@ export class SceneEntities {
): (() => SegmentOverlayPayload | null) => {
const segPathToNode = getNodePathFromSourceRange(
modifiedAst,
sourceRangeFromRust(segment.__geoMeta.sourceRange)
segment.__geoMeta.sourceRange
)
const sgPaths = sketch.paths
const originalPathToNodeStr = JSON.stringify(segPathToNode)
@ -1902,10 +1901,8 @@ export class SceneEntities {
SEGMENT_BODIES_PLUS_PROFILE_START
)
if (parent?.userData?.pathToNode) {
const pResult = parse(recast(kclManager.ast))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(pResult)
const updatedAst = pResult.program
const updatedAst = parse(recast(kclManager.ast))
if (trap(updatedAst)) return
const _node = getNodeFromPath<Node<CallExpression>>(
updatedAst,
parent.userData.pathToNode,
@ -1913,7 +1910,7 @@ export class SceneEntities {
)
if (trap(_node, { suppress: true })) return
const node = _node.node
editorManager.setHighlightRange([[node.start, node.end, true]])
editorManager.setHighlightRange([[node.start, node.end]])
const yellow = 0xffff00
colorSegment(selected, yellow)
const extraSegmentGroup = parent.getObjectByName(EXTRA_SEGMENT_HANDLE)
@ -1958,10 +1955,10 @@ export class SceneEntities {
})
return
}
editorManager.setHighlightRange([defaultSourceRange()])
editorManager.setHighlightRange([[0, 0]])
},
onMouseLeave: ({ selected, ...rest }: OnMouseEnterLeaveArgs) => {
editorManager.setHighlightRange([defaultSourceRange()])
editorManager.setHighlightRange([[0, 0]])
const parent = getParentGroup(
selected,
SEGMENT_BODIES_PLUS_PROFILE_START
@ -2090,10 +2087,8 @@ function prepareTruncatedMemoryAndAst(
).body.push(newSegment)
// update source ranges to section we just added.
// hacks like this wouldn't be needed if the AST put pathToNode info in memory/sketch segments
const pResult = parse(recast(_ast)) // get source ranges correct since unfortunately we still rely on them
if (trap(pResult) || !resultIsOk(pResult))
return Error('Unexpected compilation error')
const updatedSrcRangeAst = pResult.program
const updatedSrcRangeAst = parse(recast(_ast)) // get source ranges correct since unfortunately we still rely on them
if (err(updatedSrcRangeAst)) return updatedSrcRangeAst
const lastPipeItem = (
(updatedSrcRangeAst.body[bodyIndex] as VariableDeclaration)

View File

@ -5,7 +5,6 @@ import { useEffect, useRef, useState } from 'react'
import { trap } from 'lib/trap'
import { codeToIdSelections } from 'lib/selections'
import { codeRefFromRange } from 'lang/std/artifactGraph'
import { defaultSourceRange } from 'lang/wasm'
export function AstExplorer() {
const { context } = useModelingContext()
@ -47,7 +46,7 @@ export function AstExplorer() {
<div
className="h-full relative"
onMouseLeave={(e) => {
editorManager.setHighlightRange([defaultSourceRange()])
editorManager.setHighlightRange([[0, 0]])
}}
>
<pre className="text-xs">
@ -116,19 +115,15 @@ function DisplayObj({
hasCursor ? 'bg-violet-100/80 dark:bg-violet-100/25' : ''
}`}
onMouseEnter={(e) => {
editorManager.setHighlightRange([[obj?.start || 0, obj.end, true]])
editorManager.setHighlightRange([[obj?.start || 0, obj.end]])
e.stopPropagation()
}}
onMouseMove={(e) => {
e.stopPropagation()
editorManager.setHighlightRange([[obj?.start || 0, obj.end, true]])
editorManager.setHighlightRange([[obj?.start || 0, obj.end]])
}}
onClick={(e) => {
const range: [number, number, boolean] = [
obj?.start || 0,
obj.end || 0,
true,
]
const range: [number, number] = [obj?.start || 0, obj.end || 0]
const idInfo = codeToIdSelections([
{ codeRef: codeRefFromRange(range, kclManager.ast) },
])[0]

View File

@ -1,11 +1,5 @@
import { useEffect, useState, useRef } from 'react'
import {
parse,
BinaryPart,
Expr,
ProgramMemory,
resultIsOk,
} from '../lang/wasm'
import { parse, BinaryPart, Expr, ProgramMemory } from '../lang/wasm'
import {
createIdentifier,
createLiteral,
@ -147,9 +141,8 @@ export function useCalc({
useEffect(() => {
try {
const code = `const __result__ = ${value}`
const pResult = parse(code)
if (trap(pResult) || !resultIsOk(pResult)) return
const ast = pResult.program
const ast = parse(code)
if (trap(ast)) return
const _programMem: ProgramMemory = ProgramMemory.empty()
for (const { key, value } of availableVarInfo.variables) {
const error = _programMem.set(key, {
@ -163,8 +156,9 @@ export function useCalc({
executeAst({
ast,
engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
useFakeExecutor: true,
programMemoryOverride: kclManager.programMemory.clone(),
idGenerator: kclManager.execState.idGenerator,
}).then(({ execState }) => {
const resultDeclaration = ast.body.find(
(a) =>

View File

@ -636,16 +636,6 @@ const CustomIconMap = {
/>
</svg>
),
loading: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M12.5001 6.25839C11.76 5.76392 10.89 5.5 10 5.5V4.5C11.0878 4.5 12.1512 4.82257 13.0556 5.42692C13.9601 6.03126 14.6651 6.89025 15.0813 7.89524C15.4976 8.90023 15.6065 10.0061 15.3943 11.073C15.1821 12.1399 14.6583 13.1199 13.8891 13.8891C13.1199 14.6583 12.1399 15.1821 11.073 15.3943C10.0061 15.6065 8.90023 15.4976 7.89524 15.0813C6.89025 14.6651 6.03126 13.9601 5.42692 13.0556C4.82257 12.1512 4.5 11.0878 4.5 10H5.5C5.5 10.89 5.76392 11.76 6.25839 12.5001C6.75285 13.2401 7.45566 13.8169 8.27792 14.1575C9.10019 14.4981 10.005 14.5872 10.8779 14.4135C11.7508 14.2399 12.5526 13.8113 13.182 13.182C13.8113 12.5526 14.2399 11.7508 14.4135 10.8779C14.5872 10.005 14.4981 9.10019 14.1575 8.27792C13.8169 7.45566 13.2401 6.75285 12.5001 6.25839Z"
fill="currentColor"
/>
</svg>
),
lockClosed: (
<svg
viewBox="0 0 20 20"

View File

@ -1,14 +1,141 @@
import { APP_VERSION } from 'routes/Settings'
import { CustomIcon } from 'components/CustomIcon'
import Tooltip from 'components/Tooltip'
import { PATHS } from 'lib/paths'
import { NetworkHealthIndicator } from 'components/NetworkHealthIndicator'
import { HelpMenu } from './HelpMenu'
import { Link, useLocation } from 'react-router-dom'
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
import { coreDump } from 'lang/wasm'
import toast from 'react-hot-toast'
import { CoreDumpManager } from 'lib/coredump'
import openWindow, { openExternalBrowserIfDesktop } from 'lib/openWindow'
import { NetworkMachineIndicator } from './NetworkMachineIndicator'
import { ModelStateIndicator } from './ModelStateIndicator'
import { reportRejection } from 'lib/trap'
export function LowerRightControls({
children,
coreDumpManager,
}: {
children?: React.ReactNode
coreDumpManager?: CoreDumpManager
}) {
const location = useLocation()
const filePath = useAbsoluteFilePath()
const linkOverrideClassName =
'!text-chalkboard-70 hover:!text-chalkboard-80 dark:!text-chalkboard-40 dark:hover:!text-chalkboard-30'
function reportbug(event: {
preventDefault: () => void
stopPropagation: () => void
}) {
event?.preventDefault()
event?.stopPropagation()
if (!coreDumpManager) {
// open default reporting option
openWindow(
'https://github.com/KittyCAD/modeling-app/issues/new/choose'
).catch(reportRejection)
} else {
toast
.promise(
coreDump(coreDumpManager, true),
{
loading: 'Preparing bug report...',
success: 'Bug report opened in new window',
error: 'Unable to export a core dump. Using default reporting.',
},
{
success: {
// Note: this extended duration is especially important for Playwright e2e testing
// default duration is 2000 - https://react-hot-toast.com/docs/toast#default-durations
duration: 6000,
},
}
)
.catch((err: Error) => {
if (err) {
openWindow(
'https://github.com/KittyCAD/modeling-app/issues/new/choose'
).catch(reportRejection)
}
})
}
}
return (
<section className="absolute bottom-2 right-2 flex flex-col items-end gap-3 pointer-events-none">
<section className="fixed bottom-2 right-2 flex flex-col items-end gap-3 pointer-events-none">
{children}
<menu className="flex items-center justify-end gap-3 pointer-events-auto">
{!location.pathname.startsWith(PATHS.HOME) && <ModelStateIndicator />}
<a
onClick={openExternalBrowserIfDesktop(
`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`
)}
href={`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`}
target="_blank"
rel="noopener noreferrer"
className={'!no-underline font-mono text-xs ' + linkOverrideClassName}
>
v{APP_VERSION}
</a>
<a
onClick={reportbug}
href="https://github.com/KittyCAD/modeling-app/issues/new/choose"
target="_blank"
rel="noopener noreferrer"
>
<CustomIcon
name="bug"
className={`w-5 h-5 ${linkOverrideClassName}`}
/>
<Tooltip position="top" contentClassName="text-xs">
Report a bug
</Tooltip>
</a>
<Link
to={
location.pathname.includes(PATHS.FILE)
? filePath + PATHS.TELEMETRY + '?tab=project'
: PATHS.HOME + PATHS.TELEMETRY
}
data-testid="telemetry-link"
>
<CustomIcon
name="stopwatch"
className={`w-5 h-5 ${linkOverrideClassName}`}
/>
<span className="sr-only">Telemetry</span>
<Tooltip position="top" contentClassName="text-xs">
Telemetry
</Tooltip>
</Link>
<Link
to={
location.pathname.includes(PATHS.FILE)
? filePath + PATHS.SETTINGS + '?tab=project'
: PATHS.HOME + PATHS.SETTINGS
}
data-testid="settings-link"
>
<CustomIcon
name="settings"
className={`w-5 h-5 ${linkOverrideClassName}`}
/>
<span className="sr-only">Settings</span>
<Tooltip position="top" contentClassName="text-xs">
Settings
</Tooltip>
</Link>
<NetworkMachineIndicator className={linkOverrideClassName} />
{!location.pathname.startsWith(PATHS.HOME) && (
<NetworkHealthIndicator />
)}
<HelpMenu />
</menu>
</section>
)
}

View File

@ -1,39 +1,6 @@
import { useEngineCommands } from './EngineCommands'
import { Spinner } from './Spinner'
import { CustomIcon } from './CustomIcon'
import { StatusBarItemType } from './statusBar/statusBarTypes'
export const useModelStateStatus = (): StatusBarItemType => {
const [commands] = useEngineCommands()
const lastCommandType = commands[commands.length - 1]?.type
let icon: StatusBarItemType['icon'] = 'loading'
const baseDataTestId = 'model-state-indicator'
let dataTestId = baseDataTestId
if (lastCommandType === 'receive-reliable') {
icon = 'checkmark'
dataTestId = `${baseDataTestId}-receive-reliable`
} else if (lastCommandType === 'execution-done') {
icon = 'checkmark'
dataTestId = `${baseDataTestId}-execution-done`
} else if (lastCommandType === 'export-done') {
icon = 'checkmark'
dataTestId = `${baseDataTestId}-export-done`
}
return {
id: 'model-state-indicator',
label: '',
icon,
toolTip: {
children: 'Model state indicator',
},
element: 'button',
onClick: () => {},
'data-testid': dataTestId,
}
}
export const ModelStateIndicator = () => {
const [commands] = useEngineCommands()

View File

@ -50,7 +50,6 @@ import {
isSketchPipe,
Selections,
updateSelections,
canLoftSelection,
} from 'lib/selections'
import { applyConstraintIntersect } from './Toolbar/Intersect'
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
@ -67,7 +66,7 @@ import {
sketchOnOffsetPlane,
startSketchOnDefault,
} from 'lang/modifyAst'
import { Program, parse, recast, resultIsOk } from 'lang/wasm'
import { Program, parse, recast } from 'lang/wasm'
import {
doesSceneHaveSweepableSketch,
getNodePathFromSourceRange,
@ -83,7 +82,7 @@ import { getVarNameModal } from 'hooks/useToolbarGuards'
import { err, reportRejection, trap } from 'lib/trap'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { modelingMachineEvent } from 'editor/manager'
import { hasValidEdgeTreatmentSelection } from 'lang/modifyAst/addEdgeTreatment'
import { hasValidEdgeTreatmentSelection } from 'lang/modifyAst/addFillet'
import {
ExportIntent,
EngineConnectionStateType,
@ -99,7 +98,6 @@ type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T>
context: ContextFrom<T>
send: Prop<Actor<T>, 'send'>
streamRef: React.RefObject<HTMLDivElement>
}
export const ModelingMachineContext = createContext(
@ -571,21 +569,6 @@ export const ModelingMachineProvider = ({
if (err(canSweep)) return false
return canSweep
},
'has valid loft selection': ({ context: { selectionRanges } }) => {
const hasNoSelection =
selectionRanges.graphSelections.length === 0 ||
isRangeBetweenCharacters(selectionRanges) ||
isSelectionLastLine(selectionRanges, codeManager.code)
if (hasNoSelection) {
const count = 2
return doesSceneHaveSweepableSketch(kclManager.ast, count)
}
const canLoft = canLoftSelection(selectionRanges)
if (err(canLoft)) return false
return canLoft
},
'has valid selection for deletion': ({
context: { selectionRanges },
}) => {
@ -613,11 +596,15 @@ export const ModelingMachineProvider = ({
)
},
'Has exportable geometry': () => {
if (!kclManager.hasErrors() && kclManager.ast.body.length > 0)
if (
kclManager.kclErrors.length === 0 &&
kclManager.ast.body.length > 0
)
return true
else {
let errorMessage = 'Unable to Export '
if (kclManager.hasErrors()) errorMessage += 'due to KCL Errors'
if (kclManager.kclErrors.length > 0)
errorMessage += 'due to KCL Errors'
else if (kclManager.ast.body.length === 0)
errorMessage += 'due to Empty Scene'
console.error(errorMessage)
@ -735,11 +722,7 @@ export const ModelingMachineProvider = ({
constraint: 'setHorzDistance',
selectionRanges,
})
const pResult = parse(recast(modifiedAst))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error'))
const _modifiedAst = pResult.program
const _modifiedAst = parse(recast(modifiedAst))
if (!sketchDetails)
return Promise.reject(new Error('No sketch details'))
const updatedPathToNode = updatePathToNodeFromMap(
@ -780,10 +763,7 @@ export const ModelingMachineProvider = ({
constraint: 'setVertDistance',
selectionRanges,
})
const pResult = parse(recast(modifiedAst))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error'))
const _modifiedAst = pResult.program
const _modifiedAst = parse(recast(modifiedAst))
if (!sketchDetails)
return Promise.reject(new Error('No sketch details'))
const updatedPathToNode = updatePathToNodeFromMap(
@ -831,10 +811,7 @@ export const ModelingMachineProvider = ({
selectionRanges,
angleOrLength: 'setAngle',
}))
const pResult = parse(recast(modifiedAst))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error'))
const _modifiedAst = pResult.program
const _modifiedAst = parse(recast(modifiedAst))
if (err(_modifiedAst)) return Promise.reject(_modifiedAst)
if (!sketchDetails)
@ -876,10 +853,7 @@ export const ModelingMachineProvider = ({
await applyConstraintAngleLength({
selectionRanges,
})
const pResult = parse(recast(modifiedAst))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error'))
const _modifiedAst = pResult.program
const _modifiedAst = parse(recast(modifiedAst))
if (!sketchDetails)
return Promise.reject(new Error('No sketch details'))
const updatedPathToNode = updatePathToNodeFromMap(
@ -919,10 +893,7 @@ export const ModelingMachineProvider = ({
await applyConstraintIntersect({
selectionRanges,
})
const pResult = parse(recast(modifiedAst))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error'))
const _modifiedAst = pResult.program
const _modifiedAst = parse(recast(modifiedAst))
if (!sketchDetails)
return Promise.reject(new Error('No sketch details'))
const updatedPathToNode = updatePathToNodeFromMap(
@ -963,10 +934,7 @@ export const ModelingMachineProvider = ({
constraint: 'xAbs',
selectionRanges,
})
const pResult = parse(recast(modifiedAst))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error'))
const _modifiedAst = pResult.program
const _modifiedAst = parse(recast(modifiedAst))
if (!sketchDetails)
return Promise.reject(new Error('No sketch details'))
const updatedPathToNode = updatePathToNodeFromMap(
@ -1007,10 +975,7 @@ export const ModelingMachineProvider = ({
constraint: 'yAbs',
selectionRanges,
})
const pResult = parse(recast(modifiedAst))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error'))
const _modifiedAst = pResult.program
const _modifiedAst = parse(recast(modifiedAst))
if (!sketchDetails)
return Promise.reject(new Error('No sketch details'))
const updatedPathToNode = updatePathToNodeFromMap(
@ -1051,10 +1016,9 @@ export const ModelingMachineProvider = ({
const { variableName } = await getVarNameModal({
valueName: data?.variableName || 'var',
})
let pResult = parse(recast(kclManager.ast))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error'))
let parsed = pResult.program
let parsed = parse(recast(kclManager.ast))
if (trap(parsed)) return Promise.reject(parsed)
parsed = parsed as Node<Program>
const { modifiedAst: _modifiedAst, pathToReplacedNode } =
moveValueIntoNewVariablePath(
@ -1063,11 +1027,7 @@ export const ModelingMachineProvider = ({
data?.pathToNode || [],
variableName
)
pResult = parse(recast(_modifiedAst))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error'))
parsed = pResult.program
parsed = parse(recast(_modifiedAst))
if (trap(parsed)) return Promise.reject(parsed)
parsed = parsed as Node<Program>
if (!pathToReplacedNode)
@ -1206,10 +1166,13 @@ export const ModelingMachineProvider = ({
state: modelingState,
context: modelingState.context,
send: modelingSend,
streamRef,
}}
>
{children}
{/* TODO #818: maybe pass reff down to children/app.ts or render app.tsx directly?
since realistically it won't ever have generic children that isn't app.tsx */}
<div className="h-screen overflow-hidden select-none" ref={streamRef}>
{children}
</div>
</ModelingMachineContext.Provider>
)
}

View File

@ -1,6 +1,6 @@
import { processMemory } from './MemoryPane'
import { enginelessExecutor } from '../../../lib/testHelpers'
import { assertParse, initPromise, ProgramMemory } from '../../../lang/wasm'
import { initPromise, parse, ProgramMemory } from '../../../lang/wasm'
beforeAll(async () => {
await initPromise
@ -28,16 +28,12 @@ describe('processMemory', () => {
|> lineTo([0.98, 5.16], %)
|> lineTo([2.15, 4.32], %)
// |> rx(90, %)`
const ast = assertParse(code)
const ast = parse(code)
const execState = await enginelessExecutor(ast, ProgramMemory.empty())
const output = processMemory(execState.memory)
expect(output.myVar).toEqual(5)
expect(output.otherVar).toEqual(3)
expect(output).toEqual({
HALF_TURN: 180,
QUARTER_TURN: 90,
THREE_QUARTER_TURN: 270,
ZERO: 0,
myVar: 5,
myFn: '__function(a)__',
otherVar: 3,

View File

@ -90,7 +90,7 @@ export const sidebarPanes: SidebarPane[] = [
keybinding: 'Shift + C',
showBadge: {
value: ({ kclContext }) => {
return kclContext.diagnostics.length
return kclContext.errors.length
},
onClick: (e) => {
e.preventDefault()

View File

@ -53,7 +53,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
settings: settings.context,
platform: getPlatformString(),
}),
[kclContext.diagnostics, settings.context]
[kclContext.errors, settings.context]
)
const sidebarActions: SidebarAction[] = [

View File

@ -6,7 +6,6 @@ import { useNetworkContext } from '../hooks/useNetworkContext'
import { NetworkHealthState } from '../hooks/useNetworkStatus'
import { toSync } from 'lib/utils'
import { reportRejection } from 'lib/trap'
import { StatusBarItemType } from './statusBar/statusBarTypes'
export const NETWORK_HEALTH_TEXT: Record<NetworkHealthState, string> = {
[NetworkHealthState.Ok]: 'Connected',
@ -65,28 +64,14 @@ const overallConnectionStateColor: Record<NetworkHealthState, IconColorConfig> =
},
}
const overallConnectionStateIcon = {
const overallConnectionStateIcon: Record<
NetworkHealthState,
ActionIconProps['icon']
> = {
[NetworkHealthState.Ok]: 'network',
[NetworkHealthState.Weak]: 'network',
[NetworkHealthState.Issue]: 'networkCrossedOut',
[NetworkHealthState.Disconnected]: 'networkCrossedOut',
} as const
export const useNetworkHealthStatus = (): StatusBarItemType => {
const { overallState } = useNetworkContext()
return {
id: 'network-health',
label: `Network health (${NETWORK_HEALTH_TEXT[overallState]})`,
hideLabel: true,
element: 'popover',
className: overallConnectionStateColor[overallState].icon,
toolTip: {
children: `Network health (${NETWORK_HEALTH_TEXT[overallState]})`,
},
icon: overallConnectionStateIcon[overallState],
popoverContent: <NetworkHealthPopoverContent />,
}
}
export const NetworkHealthIndicator = () => {
@ -124,95 +109,81 @@ export const NetworkHealthIndicator = () => {
Network health ({NETWORK_HEALTH_TEXT[overallState]})
</Tooltip>
</Popover.Button>
<Popover.Panel>
<NetworkHealthPopoverContent />
<Popover.Panel
className="absolute right-0 left-auto 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"
>
<div
className={`flex items-center justify-between p-2 rounded-t-sm ${overallConnectionStateColor[overallState].bg} ${overallConnectionStateColor[overallState].icon}`}
>
<h2 className="text-sm font-sans font-normal">Network health</h2>
<p
data-testid="network"
className="font-bold text-xs uppercase px-2 py-1 rounded-sm"
>
{NETWORK_HEALTH_TEXT[overallState]}
</p>
</div>
<ul className="divide-y divide-chalkboard-20 dark:divide-chalkboard-80">
{Object.keys(steps).map((name) => (
<li
key={name}
className={'flex flex-col px-2 py-4 gap-1 last:mb-0 '}
>
<div className="flex items-center text-left gap-1">
<p className="flex-1">{name}</p>
{internetConnected ? (
<ActionIcon
size="lg"
icon={
hasIssueToIcon[
String(issues[name as ConnectingTypeGroup])
]
}
iconClassName={
hasIssueToIconColors[
String(issues[name as ConnectingTypeGroup])
].icon
}
bgClassName={
'rounded-sm ' +
hasIssueToIconColors[
String(issues[name as ConnectingTypeGroup])
].bg
}
/>
) : (
<ActionIcon
icon={hasIssueToIcon.true}
bgClassName={hasIssueToIconColors.true.bg}
iconClassName={hasIssueToIconColors.true.icon}
/>
)}
</div>
{issues[name as ConnectingTypeGroup] && (
<button
onClick={toSync(async () => {
await navigator.clipboard.writeText(
JSON.stringify(error, null, 2) || ''
)
setHasCopied(true)
setTimeout(() => setHasCopied(false), 5000)
}, reportRejection)}
className="flex w-fit gap-2 items-center bg-transparent text-sm p-1 py-0 my-0 -mx-1 text-destroy-80 dark:text-destroy-10 hover:bg-transparent border-transparent dark:border-transparent hover:border-destroy-80 dark:hover:border-destroy-80 dark:hover:bg-destroy-80"
>
{hasCopied ? 'Copied' : 'Copy Error'}
<ActionIcon
size="lg"
icon={hasCopied ? 'clipboardCheckmark' : 'clipboardPlus'}
iconClassName="text-inherit dark:text-inherit"
bgClassName="!bg-transparent"
/>
</button>
)}
</li>
))}
</ul>
</Popover.Panel>
</Popover>
)
}
const NetworkHealthPopoverContent = () => {
const {
hasIssues,
overallState,
internetConnected,
steps,
issues,
error,
setHasCopied,
hasCopied,
} = useNetworkContext()
return (
<div
className="absolute left-2 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"
>
<div
className={`flex items-center justify-between p-2 rounded-t-sm ${overallConnectionStateColor[overallState].bg} ${overallConnectionStateColor[overallState].icon}`}
>
<h2 className="text-sm font-sans font-normal">Network health</h2>
<p
data-testid="network"
className="font-bold text-xs uppercase px-2 py-1 rounded-sm"
>
{NETWORK_HEALTH_TEXT[overallState]}
</p>
</div>
<ul className="divide-y divide-chalkboard-20 dark:divide-chalkboard-80">
{Object.keys(steps).map((name) => (
<li key={name} className={'flex flex-col px-2 py-4 gap-1 last:mb-0 '}>
<div className="flex items-center text-left gap-1">
<p className="flex-1">{name}</p>
{internetConnected ? (
<ActionIcon
size="lg"
icon={
hasIssueToIcon[String(issues[name as ConnectingTypeGroup])]
}
iconClassName={
hasIssueToIconColors[
String(issues[name as ConnectingTypeGroup])
].icon
}
bgClassName={
'rounded-sm ' +
hasIssueToIconColors[
String(issues[name as ConnectingTypeGroup])
].bg
}
/>
) : (
<ActionIcon
icon={hasIssueToIcon.true}
bgClassName={hasIssueToIconColors.true.bg}
iconClassName={hasIssueToIconColors.true.icon}
/>
)}
</div>
{issues[name as ConnectingTypeGroup] && (
<button
onClick={toSync(async () => {
await navigator.clipboard.writeText(
JSON.stringify(error, null, 2) || ''
)
setHasCopied(true)
setTimeout(() => setHasCopied(false), 5000)
}, reportRejection)}
className="flex w-fit gap-2 items-center bg-transparent text-sm p-1 py-0 my-0 -mx-1 text-destroy-80 dark:text-destroy-10 hover:bg-transparent border-transparent dark:border-transparent hover:border-destroy-80 dark:hover:border-destroy-80 dark:hover:bg-destroy-80"
>
{hasCopied ? 'Copied' : 'Copy Error'}
<ActionIcon
size="lg"
icon={hasCopied ? 'clipboardCheckmark' : 'clipboardPlus'}
iconClassName="text-inherit dark:text-inherit"
bgClassName="!bg-transparent"
/>
</button>
)}
</li>
))}
</ul>
</div>
)
}

View File

@ -5,7 +5,6 @@ import { isDesktop } from 'lib/isDesktop'
import { components } from 'lib/machine-api'
import { MachineManagerContext } from 'components/MachineManagerProvider'
import { CustomIcon } from './CustomIcon'
import { StatusBarItemType } from './statusBar/statusBarTypes'
export const NetworkMachineIndicator = ({
className,
@ -28,7 +27,12 @@ export const NetworkMachineIndicator = ({
}
data-testid="network-machine-toggle"
>
<NetworkMachinesIcon machineCount={machineCount} />
<CustomIcon name="printer3d" className="w-5 h-5" />
{machineCount > 0 && (
<p aria-hidden className="flex items-center justify-center text-xs">
{machineCount}
</p>
)}
<Tooltip position="top-right" wrapperClassName="ui-open:hidden">
Network machines ({machineCount}) {reason && `: ${reason}`}
</Tooltip>
@ -37,92 +41,50 @@ export const NetworkMachineIndicator = ({
className="absolute right-0 left-auto 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} />
<div className="flex items-center justify-between p-2 rounded-t-sm bg-chalkboard-20 dark:bg-chalkboard-80">
<h2 className="text-sm font-sans font-normal">Network machines</h2>
<p
data-testid="network"
className="font-bold text-xs uppercase px-2 py-1 rounded-sm"
>
{machineCount}
</p>
</div>
{machineCount > 0 && (
<ul className="divide-y divide-chalkboard-20 dark:divide-chalkboard-80">
{machines.map(
(machine: components['schemas']['MachineInfoResponse']) => {
return (
<li key={machine.id} className={'px-2 py-4 gap-1 last:mb-0 '}>
<p className="">{machine.id.toUpperCase()}</p>
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
{machine.make_model.model}
</p>
{machine.extra &&
machine.extra.type === 'bambu' &&
machine.extra.nozzle_diameter && (
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
Nozzle Diameter: {machine.extra.nozzle_diameter}
</p>
)}
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
{`Status: ${machine.state.state
.charAt(0)
.toUpperCase()}${machine.state.state.slice(1)}`}
{machine.state.state === 'failed' && machine.state.message
? ` (${machine.state.message})`
: ''}
{machine.state.state === 'running' && machine.progress
? ` (${Math.round(machine.progress)}%)`
: ''}
</p>
</li>
)
}
)}
</ul>
)}
</Popover.Panel>
</Popover>
) : null
}
export const useNetworkMachineStatus = (): StatusBarItemType => {
const {
noMachinesReason,
machines,
machines: { length: machineCount },
} = useContext(MachineManagerContext)
const reason = noMachinesReason()
return {
id: 'network-machines',
label: `Network machines (${machineCount}) ${reason && `: ${reason}`}`,
hideLabel: true,
element: 'popover',
toolTip: {
children: `Network machines (${machineCount}) ${reason && `: ${reason}`}`,
},
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({ machines }: { machines: components['schemas']['MachineInfoResponse'][] }) {
return (
<>
<div className="flex items-center justify-between p-2 rounded-t-sm bg-chalkboard-20 dark:bg-chalkboard-80">
<h2 className="text-sm font-sans font-normal">Network machines</h2>
<p
data-testid="network"
className="font-bold text-xs uppercase px-2 py-1 rounded-sm"
>
{machines.length}
</p>
</div>
{machines.length > 0 && (
<ul className="divide-y divide-chalkboard-20 dark:divide-chalkboard-80">
{machines.map(
(machine: components['schemas']['MachineInfoResponse']) => {
return (
<li key={machine.id} className={'px-2 py-4 gap-1 last:mb-0 '}>
<p className="">{machine.id.toUpperCase()}</p>
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
{machine.make_model.model}
</p>
{machine.extra &&
machine.extra.type === 'bambu' &&
machine.extra.nozzle_diameter && (
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
Nozzle Diameter: {machine.extra.nozzle_diameter}
</p>
)}
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
{`Status: ${machine.state.state
.charAt(0)
.toUpperCase()}${machine.state.state.slice(1)}`}
{machine.state.state === 'failed' && machine.state.message
? ` (${machine.state.message})`
: ''}
{machine.state.state === 'running' && machine.progress
? ` (${Math.round(machine.progress)}%)`
: ''}
</p>
</li>
)
}
)}
</ul>
)}
</>
)
}

View File

@ -68,8 +68,8 @@ function AppLogoLink({
data-testid="app-logo"
onClick={() => {
onProjectClose(file || null, project?.path || null, false)
// Clear the scene.
engineCommandManager.clearScene()
// Clear the scene and end the session.
engineCommandManager.endSession()
}}
to={PATHS.HOME}
className={wrapperClassName + ' hover:before:brightness-110'}
@ -190,8 +190,8 @@ function ProjectMenuPopover({
className: !isDesktop() ? 'hidden' : '',
onClick: () => {
onProjectClose(file || null, project?.path || null, true)
// Clear the scene.
engineCommandManager.clearScene()
// Clear the scene and end the session.
engineCommandManager.endSession()
},
},
].filter(

View File

@ -13,7 +13,7 @@ import { isDesktop } from 'lib/isDesktop'
import { ActionButton } from 'components/ActionButton'
import { SettingsFieldInput } from './SettingsFieldInput'
import toast from 'react-hot-toast'
import { APP_VERSION } from 'lib/appVersion'
import { APP_VERSION, PACKAGE_NAME } from 'routes/Settings'
import { PATHS } from 'lib/paths'
import {
createAndOpenNewTutorialProject,
@ -25,7 +25,6 @@ import { useLspContext } from 'components/LspProvider'
import { toSync } from 'lib/utils'
import { reportRejection } from 'lib/trap'
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
import { PACKAGE_NAME } from 'routes/Settings'
interface AllSettingsFieldsProps {
searchParamTab: SettingsLevel

View File

@ -1,148 +0,0 @@
import { useEffect } from 'react'
import { ActionButton } from './ActionButton'
import { StatusBarItemType } from './statusBar/statusBarTypes'
import Tooltip, { TooltipProps } from './Tooltip'
import { ActionIcon } from './ActionIcon'
import { Popover } from '@headlessui/react'
export function StatusBar({
globalItems,
localItems,
}: {
globalItems: StatusBarItemType[]
localItems: StatusBarItemType[]
}) {
return (
<footer
id="statusbar"
className="relative z-10 flex justify-between items-center bg-chalkboard-20 dark:bg-chalkboard-90 text-chalkboard-80 dark:text-chalkboard-30 border-t border-t-chalkboard-30 dark:border-t-chalkboard-80"
>
<menu id="statusbar-globals" className="flex items-stretch">
{globalItems.map((item) => (
<StatusBarItem key={item.id} {...item} position="left" />
))}
</menu>
<menu id="statusbar-locals" className="flex items-stretch">
{localItems.map((item) => (
<StatusBarItem key={item.id} {...item} position="right" />
))}
</menu>
</footer>
)
}
function StatusBarItem(
props: StatusBarItemType & { position: 'left' | 'middle' | 'right' }
) {
const defaultClassNames = `px-2 py-1 text-xs text-chalkboard-80 dark:text-chalkboard-30 rounded-none border-none hover:bg-chalkboard-30 dark:hover:bg-chalkboard-80 focus:bg-chalkboard-30 dark:focus:bg-chalkboard-80 hover:text-chalkboard-100 dark:hover:text-chalkboard-10 focustext-chalkboard-100 dark:focus:text-chalkboard-10 focus:outline-none focus-visible:ring-2 focus:ring-primary focus:ring-opacity-50`
const tooltipPosition: TooltipProps['position'] =
props.position === 'middle' ? 'top' : `top-${props.position}`
switch (props.element) {
case 'button':
return (
<ActionButton
Element="button"
iconStart={
props.icon && {
icon: props.icon,
iconClassName: props.icon === 'loading' ? 'animate-spin' : '',
bgClassName: 'bg-transparent dark:bg-transparent',
}
}
className={defaultClassNames + ' ' + props.className}
data-testid={props['data-testid']}
>
{props.label && (
<span className={props.hideLabel ? 'sr-only' : ''}>
{props.label}
</span>
)}
{props.toolTip && (
<Tooltip {...props.toolTip} position={tooltipPosition} />
)}
</ActionButton>
)
case 'popover':
return (
<Popover className="relative">
<Popover.Button
as={ActionButton}
Element="button"
iconStart={
props.icon && {
icon: props.icon,
iconClassName: props.icon === 'loading' ? 'animate-spin' : '',
bgClassName: 'bg-transparent dark:bg-transparent',
}
}
className={defaultClassNames + ' ' + props.className}
data-testid={props['data-testid']}
>
{props.label && (
<span className={props.hideLabel ? 'sr-only' : ''}>
{props.label}
</span>
)}
{props.toolTip && (
<Tooltip
{...props.toolTip}
wrapperClassName={`${
props.toolTip?.wrapperClassName || ''
} ui-open:hidden`}
position={tooltipPosition}
/>
)}
</Popover.Button>
<Popover.Panel>{props.popoverContent}</Popover.Panel>
</Popover>
)
case 'text':
return (
<div
role="tooltip"
className={defaultClassNames + ' ' + props.className}
>
{props.icon && (
<ActionIcon
icon={props.icon}
iconClassName={props.icon === 'loading' ? 'animate-spin' : ''}
bgClassName="bg-transparent dark:bg-transparent"
/>
)}
{props.label && (
<span className={props.hideLabel ? 'sr-only' : ''}>
{props.label}
</span>
)}
{props.toolTip && (
<Tooltip {...props.toolTip} position={tooltipPosition} />
)}
</div>
)
default:
return (
<ActionButton
Element={props.element}
to={props.href}
iconStart={
props.icon && {
icon: props.icon,
bgClassName: 'bg-transparent dark:bg-transparent',
}
}
className={defaultClassNames + ' ' + props.className}
data-testid={props['data-testid']}
>
{props.label && (
<span className={props.hideLabel ? 'sr-only' : ''}>
{props.label}
</span>
)}
{props.toolTip && (
<Tooltip {...props.toolTip} position={tooltipPosition} />
)}
</ActionButton>
)
}
}

View File

@ -40,10 +40,7 @@ export function removeConstrainingValuesInfo({
otherSelections: [],
graphSelections: nodes.map(
(node): Selection => ({
codeRef: codeRefFromRange(
[node.start, node.end, true],
kclManager.ast
),
codeRef: codeRefFromRange([node.start, node.end], kclManager.ast),
})
),
}

View File

@ -8,7 +8,7 @@ type LeftOrRight = 'left' | 'right'
type Corner = `${TopOrBottom}-${LeftOrRight}`
type TooltipPosition = TopOrBottom | LeftOrRight | Corner
export interface TooltipProps extends React.PropsWithChildren {
interface TooltipProps extends React.PropsWithChildren {
position?: TooltipPosition
wrapperClassName?: string
contentClassName?: string

View File

@ -1,96 +0,0 @@
import openWindow from 'lib/openWindow'
import { StatusBarItemType } from './statusBarTypes'
import { reportRejection } from 'lib/trap'
import { CoreDumpManager } from 'lib/coredump'
import toast from 'react-hot-toast'
import { coreDump } from 'lang/wasm'
import { APP_VERSION } from 'lib/appVersion'
import { Location } from 'react-router-dom'
import { PATHS } from 'lib/paths'
export const homeDefaultStatusBarItems = ({
coreDumpManager,
location,
}: {
coreDumpManager?: CoreDumpManager
location: Location
}): StatusBarItemType[] => [
{
id: 'version',
element: 'externalLink',
label: `v${APP_VERSION}`,
href: `https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`,
toolTip: {
children: 'View the release notes on GitHub',
},
},
{
id: 'report-bug',
element: 'button',
icon: 'bug',
label: 'Report a bug',
onClick: (event) => reportBug(event, { coreDumpManager }),
toolTip: {
children: 'Send your current app state to the developers for debugging',
},
},
{
id: 'settings',
element: 'link',
icon: 'settings',
href:
'.' +
PATHS.SETTINGS +
(location.pathname.includes(PATHS.FILE) ? '?tab=project' : ''),
'data-testid': 'settings-link',
label: 'Settings',
toolTip: {
children: 'Settings',
},
},
]
function reportBug(
event: {
preventDefault: () => void
stopPropagation: () => void
},
dependencies: {
coreDumpManager: CoreDumpManager | undefined
}
) {
event?.preventDefault()
event?.stopPropagation()
const { coreDumpManager } = dependencies
if (!coreDumpManager) {
// open default reporting option
openWindow(
'https://github.com/KittyCAD/modeling-app/issues/new/choose'
).catch(reportRejection)
} else {
toast
.promise(
coreDump(coreDumpManager, true),
{
loading: 'Preparing bug report...',
success: 'Bug report opened in new window',
error: 'Unable to export a core dump. Using default reporting.',
},
{
success: {
// Note: this extended duration is especially important for Playwright e2e testing
// default duration is 2000 - https://react-hot-toast.com/docs/toast#default-durations
duration: 6000,
},
}
)
.catch((err: Error) => {
if (err) {
openWindow(
'https://github.com/KittyCAD/modeling-app/issues/new/choose'
).catch(reportRejection)
}
})
}
}

View File

@ -1,28 +0,0 @@
import { CustomIconName } from 'components/CustomIcon'
import { TooltipProps } from 'components/Tooltip'
export type StatusBarItemType = {
id: string
label: string
icon?: CustomIconName
hideLabel?: boolean
toolTip?: Omit<TooltipProps, 'position'>
className?: string
['data-testid']?: string
} & (
| {
element: 'button'
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void
}
| {
element: 'popover'
popoverContent: React.ReactNode
}
| {
element: 'link' | 'externalLink'
href: string
}
| {
element: 'text'
}
)

View File

@ -139,9 +139,7 @@ export default class EditorManager {
}
setHighlightRange(range: Array<Selection['codeRef']['range']>): void {
this._highlightRange = range.map((s): [number, number] => {
return [s[0], s[1]]
})
this._highlightRange = range
const selectionsWithSafeEnds = range.map((s): [number, number] => {
const safeEnd = Math.min(s[1], this._editorView?.state.doc.length || s[1])

View File

@ -18,7 +18,7 @@ import {
import { err, reportRejection } from 'lib/trap'
import { DefaultPlaneStr, getFaceDetails } from 'clientSideScene/sceneEntities'
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
import { CallExpression, defaultSourceRange } from 'lang/wasm'
import { CallExpression } from 'lang/wasm'
import { EdgeCutInfo, ExtrudeFacePlane } from 'machines/modelingMachine'
export function useEngineConnectionSubscriptions() {
@ -46,7 +46,7 @@ export function useEngineConnectionSubscriptions() {
(editorManager.highlightRange[0][0] !== 0 &&
editorManager.highlightRange[0][1] !== 0)
) {
editorManager.setHighlightRange([defaultSourceRange()])
editorManager.setHighlightRange([[0, 0]])
}
},
})
@ -201,7 +201,7 @@ export function useEngineConnectionSubscriptions() {
const { z_axis, y_axis, origin } = faceInfo
const sketchPathToNode = getNodePathFromSourceRange(
kclManager.ast,
err(codeRef) ? defaultSourceRange() : codeRef.range
err(codeRef) ? [0, 0] : codeRef.range
)
const getEdgeCutMeta = (): null | EdgeCutInfo => {

View File

@ -1,15 +1,15 @@
import { KCLError } from './errors'
import { createContext, useContext, useEffect, useState } from 'react'
import { type IndexLoaderData } from 'lib/types'
import { useLoaderData } from 'react-router-dom'
import { codeManager, kclManager } from 'lib/singletons'
import { Diagnostic } from '@codemirror/lint'
const KclContext = createContext({
code: codeManager?.code || '',
programMemory: kclManager?.programMemory,
ast: kclManager?.ast,
isExecuting: kclManager?.isExecuting,
diagnostics: kclManager?.diagnostics,
errors: kclManager?.kclErrors,
logs: kclManager?.logs,
wasmInitFailed: kclManager?.wasmInitFailed,
})
@ -32,7 +32,7 @@ export function KclContextProvider({
const [programMemory, setProgramMemory] = useState(kclManager.programMemory)
const [ast, setAst] = useState(kclManager.ast)
const [isExecuting, setIsExecuting] = useState(false)
const [diagnostics, setErrors] = useState<Diagnostic[]>([])
const [errors, setErrors] = useState<KCLError[]>([])
const [logs, setLogs] = useState<string[]>([])
const [wasmInitFailed, setWasmInitFailed] = useState(false)
@ -57,7 +57,7 @@ export function KclContextProvider({
programMemory,
ast,
isExecuting,
diagnostics,
errors,
logs,
wasmInitFailed,
}}

View File

@ -1,10 +1,6 @@
import { executeAst, lintAst } from 'lang/langHelpers'
import { Selections } from 'lib/selections'
import {
KCLError,
complilationErrorsToDiagnostics,
kclErrorsToDiagnostics,
} from './errors'
import { KCLError, kclErrorsToDiagnostics } from './errors'
import { uuidv4 } from 'lib/utils'
import { EngineCommandManager } from './std/engineConnection'
import { err } from 'lib/trap'
@ -55,11 +51,11 @@ export class KclManager {
private _programMemory: ProgramMemory = ProgramMemory.empty()
lastSuccessfulProgramMemory: ProgramMemory = ProgramMemory.empty()
private _logs: string[] = []
private _diagnostics: Diagnostic[] = []
private _lints: Diagnostic[] = []
private _kclErrors: KCLError[] = []
private _isExecuting = false
private _executeIsStale: ExecuteArgs | null = null
private _wasmInitFailed = true
private _hasErrors = false
engineCommandManager: EngineCommandManager
@ -67,7 +63,7 @@ export class KclManager {
private _astCallBack: (arg: Node<Program>) => void = () => {}
private _programMemoryCallBack: (arg: ProgramMemory) => void = () => {}
private _logsCallBack: (arg: string[]) => void = () => {}
private _kclErrorsCallBack: (errors: Diagnostic[]) => void = () => {}
private _kclErrorsCallBack: (arg: KCLError[]) => void = () => {}
private _wasmInitFailedCallback: (arg: boolean) => void = () => {}
private _executeCallback: () => void = () => {}
@ -88,7 +84,7 @@ export class KclManager {
this._programMemoryCallBack(programMemory)
}
private set execState(execState) {
set execState(execState) {
this._execState = execState
this.programMemory = execState.memory
}
@ -105,28 +101,38 @@ export class KclManager {
this._logsCallBack(logs)
}
get diagnostics() {
return this._diagnostics
get lints() {
return this._lints
}
set diagnostics(ds) {
if (ds === this._diagnostics) return
this._diagnostics = ds
set lints(lints) {
if (lints === this._lints) return
this._lints = lints
// Run the lints through the diagnostics.
this.kclErrors = this._kclErrors
}
get kclErrors() {
return this._kclErrors
}
set kclErrors(kclErrors) {
if (kclErrors === this._kclErrors && this.lints.length === 0) return
this._kclErrors = kclErrors
this.setDiagnosticsForCurrentErrors()
}
addDiagnostics(ds: Diagnostic[]) {
if (ds.length === 0) return
this.diagnostics = this.diagnostics.concat(ds)
}
hasErrors(): boolean {
return this._hasErrors
this._kclErrorsCallBack(kclErrors)
}
setDiagnosticsForCurrentErrors() {
editorManager?.setDiagnostics(this.diagnostics)
this._kclErrorsCallBack(this.diagnostics)
let diagnostics = kclErrorsToDiagnostics(this.kclErrors)
if (this.lints.length > 0) {
diagnostics = diagnostics.concat(this.lints)
}
editorManager?.setDiagnostics(diagnostics)
}
addKclErrors(kclErrors: KCLError[]) {
if (kclErrors.length === 0) return
this.kclErrors = this.kclErrors.concat(kclErrors)
}
get isExecuting() {
@ -182,7 +188,7 @@ export class KclManager {
setProgramMemory: (arg: ProgramMemory) => void
setAst: (arg: Node<Program>) => void
setLogs: (arg: string[]) => void
setKclErrors: (errors: Diagnostic[]) => void
setKclErrors: (arg: KCLError[]) => void
setIsExecuting: (arg: boolean) => void
setWasmInitFailed: (arg: boolean) => void
}) {
@ -212,26 +218,17 @@ export class KclManager {
}
safeParse(code: string): Node<Program> | null {
const result = parse(code)
this.diagnostics = []
this._hasErrors = false
const ast = parse(code)
this.lints = []
this.kclErrors = []
if (!err(ast)) return ast
const kclerror: KCLError = ast as KCLError
if (err(result)) {
const kclerror: KCLError = result as KCLError
this.diagnostics = kclErrorsToDiagnostics([kclerror])
this._hasErrors = true
return null
}
this.addDiagnostics(complilationErrorsToDiagnostics(result.errors))
this.addDiagnostics(complilationErrorsToDiagnostics(result.warnings))
if (result.errors.length > 0) {
this._hasErrors = true
return null
}
return result.program
this.addKclErrors([kclerror])
// TODO: re-eval if session should end?
if (kclerror.msg === 'file is empty')
this.engineCommandManager?.endSession()
return null
}
async ensureWasmInit() {
@ -270,16 +267,19 @@ export class KclManager {
this._cancelTokens.set(currentExecutionId, false)
this.isExecuting = true
// Make sure we clear before starting again. End session will do this.
this.engineCommandManager?.endSession()
await this.ensureWasmInit()
const { logs, errors, execState, isInterrupted } = await executeAst({
ast,
idGenerator: this.execState.idGenerator,
engineCommandManager: this.engineCommandManager,
})
// Program was not interrupted, setup the scene
// Do not send send scene commands if the program was interrupted, go to clean up
if (!isInterrupted) {
this.addDiagnostics(await lintAst({ ast: ast }))
this.lints = await lintAst({ ast: ast })
sceneInfra.modelingSend({ type: 'code edit during sketch' })
setSelectionFilterToDefault(execState.memory, this.engineCommandManager)
@ -321,7 +321,9 @@ export class KclManager {
this.logs = logs
// 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.addKclErrors(isInterrupted ? [] : errors)
// Reset the next ID index so that we reuse the previous IDs next time.
execState.idGenerator.nextId = 0
this.execState = execState
if (!errors.length) {
this.lastSuccessfulProgramMemory = execState.memory
@ -362,13 +364,13 @@ export class KclManager {
const { logs, errors, execState } = await executeAst({
ast: newAst,
idGenerator: this.execState.idGenerator,
engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride: ProgramMemory.empty(),
useFakeExecutor: true,
})
this._logs = logs
this.addDiagnostics(kclErrorsToDiagnostics(errors))
this._kclErrors = errors
this._execState = execState
this._programMemory = execState.memory
if (!errors.length) {
@ -396,7 +398,7 @@ export class KclManager {
...artifact,
codeRef: {
...artifact.codeRef,
range: [node.start, node.end, true],
range: [node.start, node.end],
},
})
}
@ -488,7 +490,7 @@ export class KclManager {
if (start && end) {
returnVal.graphSelections.push({
codeRef: {
range: [start, end, true],
range: [start, end],
pathToNode: path,
},
})

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
import { assertParse, initPromise } from './wasm'
import { parse, initPromise } from './wasm'
import { enginelessExecutor } from '../lib/testHelpers'
beforeAll(async () => {
@ -14,7 +14,7 @@ const mySketch001 = startSketchOn('XY')
|> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %)
// |> rx(45, %)`
const execState = await enginelessExecutor(assertParse(code))
const execState = await enginelessExecutor(parse(code))
// @ts-ignore
const sketch001 = execState.memory.get('mySketch001')
expect(sketch001).toEqual({
@ -67,7 +67,7 @@ const mySketch001 = startSketchOn('XY')
|> lineTo([0.46, -5.82], %)
// |> rx(45, %)
|> extrude(2, %)`
const execState = await enginelessExecutor(assertParse(code))
const execState = await enginelessExecutor(parse(code))
// @ts-ignore
const sketch001 = execState.memory.get('mySketch001')
expect(sketch001).toEqual({
@ -147,7 +147,7 @@ const sk2 = startSketchOn('XY')
|> extrude(2, %)
`
const execState = await enginelessExecutor(assertParse(code))
const execState = await enginelessExecutor(parse(code))
const programMemory = execState.memory
// @ts-ignore
const geos = [programMemory.get('theExtrude'), programMemory.get('sk2')]

View File

@ -8,14 +8,20 @@ describe('test kclErrToDiagnostic', () => {
message: '',
kind: 'semantic',
msg: 'Semantic error',
sourceRange: [0, 1, true],
sourceRanges: [
[0, 1, 0],
[2, 3, 0],
],
},
{
name: '',
message: '',
kind: 'type',
msg: 'Type error',
sourceRange: [4, 5, true],
sourceRanges: [
[4, 5, 0],
[6, 7, 0],
],
},
]
const diagnostics = kclErrorsToDiagnostics(errors)
@ -26,12 +32,24 @@ describe('test kclErrToDiagnostic', () => {
message: 'Semantic error',
severity: 'error',
},
{
from: 2,
to: 3,
message: 'Semantic error',
severity: 'error',
},
{
from: 4,
to: 5,
message: 'Type error',
severity: 'error',
},
{
from: 6,
to: 7,
message: 'Type error',
severity: 'error',
},
])
})
})

View File

@ -1,90 +1,88 @@
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
import { CompilationError } from 'wasm-lib/kcl/bindings/CompilationError'
import { Diagnostic as CodeMirrorDiagnostic } from '@codemirror/lint'
import { posToOffset } from '@kittycad/codemirror-lsp-client'
import { Diagnostic as LspDiagnostic } from 'vscode-languageserver-protocol'
import { Text } from '@codemirror/state'
import { EditorView } from 'codemirror'
import { SourceRange } from 'lang/wasm'
const TOP_LEVEL_MODULE_ID = 0
type ExtractKind<T> = T extends { kind: infer K } ? K : never
export class KCLError extends Error {
kind: ExtractKind<RustKclError> | 'name'
sourceRange: SourceRange
sourceRanges: [number, number, number][]
msg: string
constructor(
kind: ExtractKind<RustKclError> | 'name',
msg: string,
sourceRange: SourceRange
sourceRanges: [number, number, number][]
) {
super()
this.kind = kind
this.msg = msg
this.sourceRange = sourceRange
this.sourceRanges = sourceRanges
Object.setPrototypeOf(this, KCLError.prototype)
}
}
export class KCLLexicalError extends KCLError {
constructor(msg: string, sourceRange: SourceRange) {
super('lexical', msg, sourceRange)
constructor(msg: string, sourceRanges: [number, number, number][]) {
super('lexical', msg, sourceRanges)
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
}
}
export class KCLInternalError extends KCLError {
constructor(msg: string, sourceRange: SourceRange) {
super('internal', msg, sourceRange)
constructor(msg: string, sourceRanges: [number, number, number][]) {
super('internal', msg, sourceRanges)
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
}
}
export class KCLSyntaxError extends KCLError {
constructor(msg: string, sourceRange: SourceRange) {
super('syntax', msg, sourceRange)
constructor(msg: string, sourceRanges: [number, number, number][]) {
super('syntax', msg, sourceRanges)
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
}
}
export class KCLSemanticError extends KCLError {
constructor(msg: string, sourceRange: SourceRange) {
super('semantic', msg, sourceRange)
constructor(msg: string, sourceRanges: [number, number, number][]) {
super('semantic', msg, sourceRanges)
Object.setPrototypeOf(this, KCLSemanticError.prototype)
}
}
export class KCLTypeError extends KCLError {
constructor(msg: string, sourceRange: SourceRange) {
super('type', msg, sourceRange)
constructor(msg: string, sourceRanges: [number, number, number][]) {
super('type', msg, sourceRanges)
Object.setPrototypeOf(this, KCLTypeError.prototype)
}
}
export class KCLUnimplementedError extends KCLError {
constructor(msg: string, sourceRange: SourceRange) {
super('unimplemented', msg, sourceRange)
constructor(msg: string, sourceRanges: [number, number, number][]) {
super('unimplemented', msg, sourceRanges)
Object.setPrototypeOf(this, KCLUnimplementedError.prototype)
}
}
export class KCLUnexpectedError extends KCLError {
constructor(msg: string, sourceRange: SourceRange) {
super('unexpected', msg, sourceRange)
constructor(msg: string, sourceRanges: [number, number, number][]) {
super('unexpected', msg, sourceRanges)
Object.setPrototypeOf(this, KCLUnexpectedError.prototype)
}
}
export class KCLValueAlreadyDefined extends KCLError {
constructor(key: string, sourceRange: SourceRange) {
super('name', `Key ${key} was already defined elsewhere`, sourceRange)
constructor(key: string, sourceRanges: [number, number, number][]) {
super('name', `Key ${key} was already defined elsewhere`, sourceRanges)
Object.setPrototypeOf(this, KCLValueAlreadyDefined.prototype)
}
}
export class KCLUndefinedValueError extends KCLError {
constructor(key: string, sourceRange: SourceRange) {
super('name', `Key ${key} has not been defined`, sourceRange)
constructor(key: string, sourceRanges: [number, number, number][]) {
super('name', `Key ${key} has not been defined`, sourceRanges)
Object.setPrototypeOf(this, KCLUndefinedValueError.prototype)
}
}
@ -101,14 +99,27 @@ export function lspDiagnosticsToKclErrors(
.flatMap(
({ range, message }) =>
new KCLError('unexpected', message, [
posToOffset(doc, range.start)!,
posToOffset(doc, range.end)!,
true,
[
posToOffset(doc, range.start)!,
posToOffset(doc, range.end)!,
TOP_LEVEL_MODULE_ID,
],
])
)
.filter(({ sourceRanges }) => {
const [from, to, moduleId] = sourceRanges[0]
return (
from !== null &&
to !== null &&
from !== undefined &&
to !== undefined &&
// Filter out errors that are not from the top-level module.
moduleId === TOP_LEVEL_MODULE_ID
)
})
.sort((a, b) => {
const c = a.sourceRange[0]
const d = b.sourceRange[0]
const c = a.sourceRanges[0][0]
const d = b.sourceRanges[0][0]
switch (true) {
case c < d:
return -1
@ -126,48 +137,17 @@ export function lspDiagnosticsToKclErrors(
export function kclErrorsToDiagnostics(
errors: KCLError[]
): CodeMirrorDiagnostic[] {
return errors
?.filter((err) => err.sourceRange[2])
.map((err) => {
return {
from: err.sourceRange[0],
to: err.sourceRange[1],
message: err.msg,
severity: 'error',
}
})
}
export function complilationErrorsToDiagnostics(
errors: CompilationError[]
): CodeMirrorDiagnostic[] {
return errors
?.filter((err) => err.sourceRange[2] === 0)
.map((err) => {
let severity: any = 'error'
if (err.severity === 'Warning') {
severity = 'warning'
}
let actions
const suggestion = err.suggestion
if (suggestion) {
actions = [
{
name: suggestion.title,
apply: (view: EditorView, from: number, to: number) => {
view.dispatch({
changes: { from, to, insert: suggestion.insert },
})
},
},
]
}
return {
from: err.sourceRange[0],
to: err.sourceRange[1],
message: err.message,
severity,
actions,
}
})
return errors?.flatMap((err) => {
const sourceRanges: CodeMirrorDiagnostic[] = err.sourceRanges
// Filter out errors that are not from the top-level module.
.filter(([_start, _end, moduleId]) => moduleId === TOP_LEVEL_MODULE_ID)
.map(([from, to]) => {
return { from, to, message: err.msg, severity: 'error' }
})
// Make sure we didn't filter out all the source ranges.
if (sourceRanges.length === 0) {
sourceRanges.push({ from: 0, to: 0, message: err.msg, severity: 'error' })
}
return sourceRanges
})
}

View File

@ -1,7 +1,7 @@
import fs from 'node:fs'
import {
assertParse,
parse,
ProgramMemory,
Sketch,
initPromise,
@ -472,7 +472,7 @@ describe('Testing Errors', () => {
const theExtrude = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([-2.4, 5], %)
|> line(myVarZ, %)
|> line([-0.76], myVarZ, %)
|> line([5,5], %)
|> close(%)
|> extrude(4, %)`
@ -480,7 +480,7 @@ const theExtrude = startSketchOn('XY')
new KCLError(
'undefined_value',
'memory item key `myVarZ` is not defined',
[129, 135, true]
[[129, 135, 0]]
)
)
})
@ -492,7 +492,7 @@ async function exe(
code: string,
programMemory: ProgramMemory = ProgramMemory.empty()
) {
const ast = assertParse(code)
const ast = parse(code)
const execState = await enginelessExecutor(ast, programMemory)
return execState.memory

View File

@ -1,5 +1,5 @@
import { getNodePathFromSourceRange, getNodeFromPath } from './queryAst'
import { Identifier, assertParse, initPromise, Parameter } from './wasm'
import { Identifier, parse, initPromise, Parameter } from './wasm'
import { err } from 'lib/trap'
beforeAll(async () => {
@ -17,19 +17,19 @@ const sk3 = startSketchAt([0, 0])
`
const subStr = 'lineTo([3, 4], %, $yo)'
const lineToSubstringIndex = code.indexOf(subStr)
const sourceRange: [number, number, boolean] = [
const sourceRange: [number, number] = [
lineToSubstringIndex,
lineToSubstringIndex + subStr.length,
true,
]
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
const _node = getNodeFromPath<any>(ast, nodePath)
if (err(_node)) throw _node
const { node } = _node
expect([node.start, node.end, true]).toEqual(sourceRange)
expect([node.start, node.end]).toEqual(sourceRange)
expect(node.type).toBe('CallExpression')
})
it('gets path right for function definition params', () => {
@ -45,13 +45,13 @@ const sk3 = startSketchAt([0, 0])
const b1 = cube([0,0], 10)`
const subStr = 'pos, scale'
const subStrIndex = code.indexOf(subStr)
const sourceRange: [number, number, boolean] = [
const sourceRange: [number, number] = [
subStrIndex,
subStrIndex + 'pos'.length,
true,
]
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
const _node = getNodeFromPath<Parameter>(ast, nodePath)
if (err(_node)) throw _node
@ -82,13 +82,13 @@ const b1 = cube([0,0], 10)`
const b1 = cube([0,0], 10)`
const subStr = 'scale, 0'
const subStrIndex = code.indexOf(subStr)
const sourceRange: [number, number, boolean] = [
const sourceRange: [number, number] = [
subStrIndex,
subStrIndex + 'scale'.length,
true,
]
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
const _node = getNodeFromPath<Identifier>(ast, nodePath)
if (err(_node)) throw _node

View File

@ -1,5 +1,6 @@
import { assertParse, initPromise, programMemoryInit } from './wasm'
import { parse, initPromise, programMemoryInit } from './wasm'
import { enginelessExecutor } from '../lib/testHelpers'
import { assert } from 'vitest'
// These unit tests makes web requests to a public github repository.
interface KclSampleFile {
@ -57,7 +58,8 @@ describe('Test KCL Samples from public Github repository', () => {
files.forEach((file: KclSampleFile) => {
it(`should parse ${file.filename} without errors`, async () => {
const code = await getKclSampleCodeFromGithub(file.filename)
assertParse(code)
const parsed = parse(code)
assert(!(parsed instanceof Error))
}, 1000)
})
})
@ -69,8 +71,9 @@ describe('Test KCL Samples from public Github repository', () => {
for (let i = 0; i < files.length; i++) {
const file: KclSampleFile = files[i]
const code = await getKclSampleCodeFromGithub(file.filename)
const ast = assertParse(code)
await enginelessExecutor(ast, programMemoryInit())
const parsed = parse(code)
assert(!(parsed instanceof Error))
await enginelessExecutor(parsed, programMemoryInit())
}
},
files.length * 1000

View File

@ -2,6 +2,7 @@ import {
Program,
_executor,
ProgramMemory,
programMemoryInit,
kclLint,
emptyExecState,
ExecState,
@ -10,6 +11,7 @@ import { enginelessExecutor } from 'lib/testHelpers'
import { EngineCommandManager } from 'lang/std/engineConnection'
import { KCLError } from 'lang/errors'
import { Diagnostic } from '@codemirror/lint'
import { IdGenerator } from 'wasm-lib/kcl/bindings/IdGenerator'
import { Node } from 'wasm-lib/kcl/bindings/Node'
export type ToolTip =
@ -47,13 +49,15 @@ export const toolTips: Array<ToolTip> = [
export async function executeAst({
ast,
engineCommandManager,
// If you set programMemoryOverride we assume you mean mock mode. Since that
// is the only way to go about it.
useFakeExecutor = false,
programMemoryOverride,
idGenerator,
}: {
ast: Node<Program>
engineCommandManager: EngineCommandManager
useFakeExecutor?: boolean
programMemoryOverride?: ProgramMemory
idGenerator?: IdGenerator
isInterrupted?: boolean
}): Promise<{
logs: string[]
@ -62,14 +66,22 @@ export async function executeAst({
isInterrupted: boolean
}> {
try {
const execState = await (programMemoryOverride
? enginelessExecutor(ast, programMemoryOverride)
: _executor(ast, engineCommandManager))
await engineCommandManager.waitForAllCommands(
programMemoryOverride !== undefined
)
if (!useFakeExecutor) {
engineCommandManager.endSession()
// eslint-disable-next-line @typescript-eslint/no-floating-promises
engineCommandManager.startNewSession()
}
const execState = await (useFakeExecutor
? enginelessExecutor(ast, programMemoryOverride || programMemoryInit())
: _executor(
ast,
programMemoryInit(),
idGenerator,
engineCommandManager,
false
))
await engineCommandManager.waitForAllCommands(useFakeExecutor)
return {
logs: [],
errors: [],

View File

@ -1,4 +1,4 @@
import { assertParse, recast, initPromise, Identifier } from './wasm'
import { parse, recast, initPromise, Identifier } from './wasm'
import {
createLiteral,
createIdentifier,
@ -146,13 +146,10 @@ function giveSketchFnCallTagTestHelper(
// giveSketchFnCallTag inputs and outputs an ast, which is very verbose for testing
// this wrapper changes the input and output to code
// making it more of an integration test, but easier to read the test intention is the goal
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const start = code.indexOf(searchStr)
const range: [number, number, boolean] = [
start,
start + searchStr.length,
true,
]
const range: [number, number] = [start, start + searchStr.length]
const sketchRes = giveSketchFnCallTag(ast, range)
if (err(sketchRes)) throw sketchRes
const { modifiedAst, tag, isTagExisting } = sketchRes
@ -224,13 +221,14 @@ part001 = startSketchOn('XY')
|> angledLine([jkl(yo) + 2, 3.09], %)
yo2 = hmm([identifierGuy + 5])`
it('should move a binary expression into a new variable', async () => {
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const execState = await enginelessExecutor(ast)
const startIndex = code.indexOf('100 + 100') + 1
const { modifiedAst } = moveValueIntoNewVariable(
ast,
execState.memory,
[startIndex, startIndex, true],
[startIndex, startIndex],
'newVar'
)
const newCode = recast(modifiedAst)
@ -238,13 +236,14 @@ yo2 = hmm([identifierGuy + 5])`
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
})
it('should move a value into a new variable', async () => {
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const execState = await enginelessExecutor(ast)
const startIndex = code.indexOf('2.8') + 1
const { modifiedAst } = moveValueIntoNewVariable(
ast,
execState.memory,
[startIndex, startIndex, true],
[startIndex, startIndex],
'newVar'
)
const newCode = recast(modifiedAst)
@ -252,13 +251,14 @@ yo2 = hmm([identifierGuy + 5])`
expect(newCode).toContain(`line([newVar, 0], %)`)
})
it('should move a callExpression into a new variable', async () => {
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const execState = await enginelessExecutor(ast)
const startIndex = code.indexOf('def(')
const { modifiedAst } = moveValueIntoNewVariable(
ast,
execState.memory,
[startIndex, startIndex, true],
[startIndex, startIndex],
'newVar'
)
const newCode = recast(modifiedAst)
@ -266,13 +266,14 @@ yo2 = hmm([identifierGuy + 5])`
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
})
it('should move a binary expression with call expression into a new variable', async () => {
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const execState = await enginelessExecutor(ast)
const startIndex = code.indexOf('jkl(') + 1
const { modifiedAst } = moveValueIntoNewVariable(
ast,
execState.memory,
[startIndex, startIndex, true],
[startIndex, startIndex],
'newVar'
)
const newCode = recast(modifiedAst)
@ -280,13 +281,14 @@ yo2 = hmm([identifierGuy + 5])`
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
})
it('should move a identifier into a new variable', async () => {
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const execState = await enginelessExecutor(ast)
const startIndex = code.indexOf('identifierGuy +') + 1
const { modifiedAst } = moveValueIntoNewVariable(
ast,
execState.memory,
[startIndex, startIndex, true],
[startIndex, startIndex],
'newVar'
)
const newCode = recast(modifiedAst)
@ -303,20 +305,19 @@ describe('testing sketchOnExtrudedFace', () => {
|> line([8.62, -9.57], %)
|> close(%)
|> extrude(5 + 7, %)`
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const segmentSnippet = `line([9.7, 9.19], %)`
const segmentRange: [number, number, boolean] = [
const segmentRange: [number, number] = [
code.indexOf(segmentSnippet),
code.indexOf(segmentSnippet) + segmentSnippet.length,
true,
]
const segmentPathToNode = getNodePathFromSourceRange(ast, segmentRange)
const extrudeSnippet = `extrude(5 + 7, %)`
const extrudeRange: [number, number, boolean] = [
const extrudeRange: [number, number] = [
code.indexOf(extrudeSnippet),
code.indexOf(extrudeSnippet) + extrudeSnippet.length,
true,
]
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
@ -344,19 +345,18 @@ sketch001 = startSketchOn(part001, seg01)`)
|> line([8.62, -9.57], %)
|> close(%)
|> extrude(5 + 7, %)`
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const segmentSnippet = `close(%)`
const segmentRange: [number, number, boolean] = [
const segmentRange: [number, number] = [
code.indexOf(segmentSnippet),
code.indexOf(segmentSnippet) + segmentSnippet.length,
true,
]
const segmentPathToNode = getNodePathFromSourceRange(ast, segmentRange)
const extrudeSnippet = `extrude(5 + 7, %)`
const extrudeRange: [number, number, boolean] = [
const extrudeRange: [number, number] = [
code.indexOf(extrudeSnippet),
code.indexOf(extrudeSnippet) + extrudeSnippet.length,
true,
]
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
@ -384,19 +384,18 @@ sketch001 = startSketchOn(part001, seg01)`)
|> line([8.62, -9.57], %)
|> close(%)
|> extrude(5 + 7, %)`
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const sketchSnippet = `startProfileAt([3.58, 2.06], %)`
const sketchRange: [number, number, boolean] = [
const sketchRange: [number, number] = [
code.indexOf(sketchSnippet),
code.indexOf(sketchSnippet) + sketchSnippet.length,
true,
]
const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange)
const extrudeSnippet = `extrude(5 + 7, %)`
const extrudeRange: [number, number, boolean] = [
const extrudeRange: [number, number] = [
code.indexOf(extrudeSnippet),
code.indexOf(extrudeSnippet) + extrudeSnippet.length,
true,
]
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
@ -433,19 +432,18 @@ sketch001 = startSketchOn(part001, 'END')`)
|> line([-17.67, 0.85], %)
|> close(%)
part001 = extrude(5 + 7, sketch001)`
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const segmentSnippet = `line([4.99, -0.46], %)`
const segmentRange: [number, number, boolean] = [
const segmentRange: [number, number] = [
code.indexOf(segmentSnippet),
code.indexOf(segmentSnippet) + segmentSnippet.length,
true,
]
const segmentPathToNode = getNodePathFromSourceRange(ast, segmentRange)
const extrudeSnippet = `extrude(5 + 7, sketch001)`
const extrudeRange: [number, number, boolean] = [
const extrudeRange: [number, number] = [
code.indexOf(extrudeSnippet),
code.indexOf(extrudeSnippet) + extrudeSnippet.length,
true,
]
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
@ -468,13 +466,13 @@ describe('Testing deleteSegmentFromPipeExpression', () => {
|> line([306.21, 198.82], %)
|> line([306.21, 198.85], %, $a)
|> line([306.21, 198.87], %)`
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const execState = await enginelessExecutor(ast)
const lineOfInterest = 'line([306.21, 198.85], %, $a)'
const range: [number, number, boolean] = [
const range: [number, number] = [
code.indexOf(lineOfInterest),
code.indexOf(lineOfInterest) + lineOfInterest.length,
true,
]
const pathToNode = getNodePathFromSourceRange(ast, range)
const modifiedAst = deleteSegmentFromPipeExpression(
@ -546,13 +544,13 @@ ${!replace1 ? ` |> ${line}\n` : ''} |> angledLine([-65, ${
],
])(`%s`, async (_, line, [replace1, replace2]) => {
const code = makeCode(line)
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const execState = await enginelessExecutor(ast)
const lineOfInterest = line
const range: [number, number, boolean] = [
const range: [number, number] = [
code.indexOf(lineOfInterest),
code.indexOf(lineOfInterest) + lineOfInterest.length,
true,
]
const pathToNode = getNodePathFromSourceRange(ast, range)
const dependentSegments = findUsesOfTagInPipe(ast, pathToNode)
@ -634,14 +632,14 @@ describe('Testing removeSingleConstraintInfo', () => {
],
['tangentialArcTo([3.14 + 0, 13.14], %)', 'arrayIndex', 1],
] as const)('stdlib fn: %s', async (expectedFinish, key, value) => {
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const execState = await enginelessExecutor(ast)
const lineOfInterest = expectedFinish.split('(')[0] + '('
const range: [number, number, boolean] = [
const range: [number, number] = [
code.indexOf(lineOfInterest) + 1,
code.indexOf(lineOfInterest) + lineOfInterest.length,
true,
]
const pathToNode = getNodePathFromSourceRange(ast, range)
let argPosition: SimplifiedArgDetails
@ -688,14 +686,14 @@ describe('Testing removeSingleConstraintInfo', () => {
['angledLineToX([12.14 + 0, 12], %)', 'arrayIndex', 1],
['angledLineToY([30, 10.14 + 0], %)', 'arrayIndex', 0],
])('stdlib fn: %s', async (expectedFinish, key, value) => {
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const execState = await enginelessExecutor(ast)
const lineOfInterest = expectedFinish.split('(')[0] + '('
const range: [number, number, boolean] = [
const range: [number, number] = [
code.indexOf(lineOfInterest) + 1,
code.indexOf(lineOfInterest) + lineOfInterest.length,
true,
]
let argPosition: SimplifiedArgDetails
if (key === 'arrayIndex' && typeof value === 'number') {
@ -885,14 +883,14 @@ sketch002 = startSketchOn({
'%s',
async (name, { codeBefore, codeAfter, lineOfInterest, type }) => {
// const lineOfInterest = 'line([-2.94, 2.7], %)'
const ast = assertParse(codeBefore)
const ast = parse(codeBefore)
if (err(ast)) throw ast
const execState = await enginelessExecutor(ast)
// deleteFromSelection
const range: [number, number, boolean] = [
const range: [number, number] = [
codeBefore.indexOf(lineOfInterest),
codeBefore.indexOf(lineOfInterest) + lineOfInterest.length,
true,
]
const artifact = { type } as Artifact
const newAst = await deleteFromSelection(

View File

@ -346,37 +346,6 @@ export function extrudeSketch(
}
}
export function loftSketches(
node: Node<Program>,
declarators: VariableDeclarator[]
): {
modifiedAst: Node<Program>
pathToNode: PathToNode
} {
const modifiedAst = structuredClone(node)
const name = findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.LOFT)
const elements = declarators.map((d) => createIdentifier(d.id.name))
const loft = createCallExpressionStdLib('loft', [
createArrayExpression(elements),
])
const declaration = createVariableDeclaration(name, loft)
modifiedAst.body.push(declaration)
const pathToNode: PathToNode = [
['body', ''],
[modifiedAst.body.length - 1, 'index'],
['declarations', 'VariableDeclaration'],
['0', 'index'],
['init', 'VariableDeclarator'],
['arguments', 'CallExpression'],
[0, 'index'],
]
return {
modifiedAst,
pathToNode,
}
}
export function revolveSketch(
node: Node<Program>,
pathToNode: PathToNode,

View File

@ -1,5 +1,5 @@
import {
assertParse,
parse,
recast,
initPromise,
PathToNode,
@ -18,7 +18,7 @@ import {
FilletParameters,
ChamferParameters,
EdgeTreatmentParameters,
} from './addEdgeTreatment'
} from './addFillet'
import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst'
import { createLiteral } from 'lang/modifyAst'
import { err } from 'lib/trap'
@ -78,10 +78,9 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
code: string,
expectedExtrudeSnippet: string
): CallExpression | PipeExpression | Error {
const extrudeRange: [number, number, boolean] = [
const extrudeRange: [number, number] = [
code.indexOf(expectedExtrudeSnippet),
code.indexOf(expectedExtrudeSnippet) + expectedExtrudeSnippet.length,
true,
]
const expectedExtrudePath = getNodePathFromSourceRange(ast, extrudeRange)
const expectedExtrudeNodeResult = getNodeFromPath<
@ -110,13 +109,14 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
}
// ast
const ast = assertParse(code)
const astOrError = parse(code)
if (err(astOrError)) return new Error('AST not found')
const ast = astOrError
// selection
const segmentRange: [number, number, boolean] = [
const segmentRange: [number, number] = [
code.indexOf(selectedSegmentSnippet),
code.indexOf(selectedSegmentSnippet) + selectedSegmentSnippet.length,
true,
]
const selection: Selections = {
graphSelections: [
@ -263,14 +263,17 @@ const runModifyAstCloneWithEdgeTreatmentAndTag = async (
expectedCode: string
) => {
// ast
const ast = assertParse(code)
const astOrError = parse(code)
if (err(astOrError)) {
return new Error('AST not found')
}
const ast = astOrError
// selection
const segmentRanges: Array<[number, number, boolean]> = selectionSnippets.map(
const segmentRanges: Array<[number, number]> = selectionSnippets.map(
(selectionSnippet) => [
code.indexOf(selectionSnippet),
code.indexOf(selectionSnippet) + selectionSnippet.length,
true,
]
)
@ -600,12 +603,12 @@ extrude001 = extrude(-5, sketch001)
}, %)
`
it('should correctly identify getOppositeEdge and baseEdge edges', () => {
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) return
const lineOfInterest = `line([7.11, 3.48], %, $seg01)`
const range: [number, number, boolean] = [
const range: [number, number] = [
code.indexOf(lineOfInterest),
code.indexOf(lineOfInterest) + lineOfInterest.length,
true,
]
const pathToNode = getNodePathFromSourceRange(ast, range)
if (err(pathToNode)) return
@ -619,12 +622,12 @@ extrude001 = extrude(-5, sketch001)
expect(edges).toEqual(['getOppositeEdge', 'baseEdge'])
})
it('should correctly identify getPreviousAdjacentEdge edges', () => {
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) return
const lineOfInterest = `line([-6.37, 3.88], %, $seg02)`
const range: [number, number, boolean] = [
const range: [number, number] = [
code.indexOf(lineOfInterest),
code.indexOf(lineOfInterest) + lineOfInterest.length,
true,
]
const pathToNode = getNodePathFromSourceRange(ast, range)
if (err(pathToNode)) return
@ -638,12 +641,12 @@ extrude001 = extrude(-5, sketch001)
expect(edges).toEqual(['getPreviousAdjacentEdge'])
})
it('should correctly identify no edges', () => {
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) return
const lineOfInterest = `line([-3.29, -13.85], %)`
const range: [number, number, boolean] = [
const range: [number, number] = [
code.indexOf(lineOfInterest),
code.indexOf(lineOfInterest) + lineOfInterest.length,
true,
]
const pathToNode = getNodePathFromSourceRange(ast, range)
if (err(pathToNode)) return
@ -664,15 +667,19 @@ describe('Testing button states', () => {
segmentSnippet: string,
expectedState: boolean
) => {
const ast = assertParse(code)
// ast
const astOrError = parse(code)
if (err(astOrError)) {
return new Error('AST not found')
}
const ast = astOrError
const range: [number, number, boolean] = segmentSnippet
const range: [number, number] = segmentSnippet
? [
code.indexOf(segmentSnippet),
code.indexOf(segmentSnippet) + segmentSnippet.length,
true,
]
: [ast.end, ast.end, true] // empty line in the end of the code
: [ast.end, ast.end] // empty line in the end of the code
const selectionRanges: Selections = {
graphSelections: [

View File

@ -1,10 +1,4 @@
import {
assertParse,
recast,
initPromise,
PathToNode,
Identifier,
} from './wasm'
import { parse, recast, initPromise, PathToNode, Identifier } from './wasm'
import {
findAllPreviousVariables,
isNodeSafeToReplace,
@ -51,13 +45,14 @@ part001 = startSketchOn('XY')
variableBelowShouldNotBeIncluded = 3
`
const rangeStart = code.indexOf('// selection-range-7ish-before-this') - 7
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const execState = await enginelessExecutor(ast)
const { variables, bodyPath, insertIndex } = findAllPreviousVariables(
ast,
execState.memory,
[rangeStart, rangeStart, true]
[rangeStart, rangeStart]
)
expect(variables).toEqual([
{ key: 'baseThick', value: 1 },
@ -85,9 +80,10 @@ describe('testing argIsNotIdentifier', () => {
yo = 5 + 6
yo2 = hmm([identifierGuy + 5])`
it('find a safe binaryExpression', () => {
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const rangeStart = code.indexOf('100 + 100') + 2
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
if (err(result)) throw result
expect(result.isSafe).toBe(true)
expect(result.value?.type).toBe('BinaryExpression')
@ -98,18 +94,20 @@ yo2 = hmm([identifierGuy + 5])`
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
})
it('find a safe Identifier', () => {
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const rangeStart = code.indexOf('abc')
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
if (err(result)) throw result
expect(result.isSafe).toBe(true)
expect(result.value?.type).toBe('Identifier')
expect(code.slice(result.value.start, result.value.end)).toBe('abc')
})
it('find a safe CallExpression', () => {
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const rangeStart = code.indexOf('def')
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
if (err(result)) throw result
expect(result.isSafe).toBe(true)
expect(result.value?.type).toBe('CallExpression')
@ -120,9 +118,10 @@ yo2 = hmm([identifierGuy + 5])`
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
})
it('find an UNsafe CallExpression, as it has a PipeSubstitution', () => {
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const rangeStart = code.indexOf('ghi')
const range: [number, number, boolean] = [rangeStart, rangeStart, true]
const range: [number, number] = [rangeStart, rangeStart]
const result = isNodeSafeToReplace(ast, range)
if (err(result)) throw result
expect(result.isSafe).toBe(false)
@ -130,9 +129,10 @@ yo2 = hmm([identifierGuy + 5])`
expect(code.slice(result.value.start, result.value.end)).toBe('ghi(%)')
})
it('find an UNsafe Identifier, as it is a callee', () => {
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const rangeStart = code.indexOf('ine([2.8,')
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
if (err(result)) throw result
expect(result.isSafe).toBe(false)
expect(result.value?.type).toBe('CallExpression')
@ -141,9 +141,10 @@ yo2 = hmm([identifierGuy + 5])`
)
})
it("find a safe BinaryExpression that's assigned to a variable", () => {
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const rangeStart = code.indexOf('5 + 6') + 1
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
if (err(result)) throw result
expect(result.isSafe).toBe(true)
expect(result.value?.type).toBe('BinaryExpression')
@ -154,9 +155,10 @@ yo2 = hmm([identifierGuy + 5])`
expect(outCode).toContain(`yo = replaceName`)
})
it('find a safe BinaryExpression that has a CallExpression within', () => {
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const rangeStart = code.indexOf('jkl') + 1
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
if (err(result)) throw result
expect(result.isSafe).toBe(true)
expect(result.value?.type).toBe('BinaryExpression')
@ -170,10 +172,11 @@ yo2 = hmm([identifierGuy + 5])`
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
})
it('find a safe BinaryExpression within a CallExpression', () => {
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const rangeStart = code.indexOf('identifierGuy') + 1
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
if (err(result)) throw result
expect(result.isSafe).toBe(true)
@ -220,13 +223,10 @@ describe('testing getNodePathFromSourceRange', () => {
it('finds the second line when cursor is put at the end', () => {
const searchLn = `line([0.94, 2.61], %)`
const sourceIndex = code.indexOf(searchLn) + searchLn.length
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const result = getNodePathFromSourceRange(ast, [
sourceIndex,
sourceIndex,
true,
])
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
expect(result).toEqual([
['body', ''],
[0, 'index'],
@ -240,13 +240,10 @@ describe('testing getNodePathFromSourceRange', () => {
it('finds the last line when cursor is put at the end', () => {
const searchLn = `line([-0.21, -1.4], %)`
const sourceIndex = code.indexOf(searchLn) + searchLn.length
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const result = getNodePathFromSourceRange(ast, [
sourceIndex,
sourceIndex,
true,
])
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
const expected = [
['body', ''],
[0, 'index'],
@ -262,14 +259,12 @@ describe('testing getNodePathFromSourceRange', () => {
const startResult = getNodePathFromSourceRange(ast, [
startSourceIndex,
startSourceIndex,
true,
])
expect(startResult).toEqual([...expected, ['callee', 'CallExpression']])
// expect similar result when whole line is selected
const selectWholeThing = getNodePathFromSourceRange(ast, [
startSourceIndex,
sourceIndex,
true,
])
expect(selectWholeThing).toEqual(expected)
})
@ -283,13 +278,10 @@ describe('testing getNodePathFromSourceRange', () => {
}`
const searchLn = `x > y`
const sourceIndex = code.indexOf(searchLn)
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const result = getNodePathFromSourceRange(ast, [
sourceIndex,
sourceIndex,
true,
])
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
expect(result).toEqual([
['body', ''],
[1, 'index'],
@ -314,13 +306,10 @@ describe('testing getNodePathFromSourceRange', () => {
}`
const searchLn = `x + 1`
const sourceIndex = code.indexOf(searchLn)
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const result = getNodePathFromSourceRange(ast, [
sourceIndex,
sourceIndex,
true,
])
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
expect(result).toEqual([
['body', ''],
[1, 'index'],
@ -343,13 +332,10 @@ describe('testing getNodePathFromSourceRange', () => {
const code = `import foo, bar as baz from 'thing.kcl'`
const searchLn = `bar`
const sourceIndex = code.indexOf(searchLn)
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
const result = getNodePathFromSourceRange(ast, [
sourceIndex,
sourceIndex,
true,
])
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
expect(result).toEqual([
['body', ''],
[0, 'index'],
@ -374,13 +360,14 @@ part001 = startSketchAt([-1.41, 3.46])
|> angledLine([-175, segLen(seg01)], %)
|> close(%)
`
const ast = assertParse(exampleCode)
const ast = parse(exampleCode)
if (err(ast)) throw ast
const result = doesPipeHaveCallExp({
calleeName: 'close',
ast,
selection: {
codeRef: codeRefFromRange([100, 101, true], ast),
codeRef: codeRefFromRange([100, 101], ast),
},
})
expect(result).toEqual(true)
@ -395,13 +382,14 @@ part001 = startSketchAt([-1.41, 3.46])
|> close(%)
|> extrude(1, %)
`
const ast = assertParse(exampleCode)
const ast = parse(exampleCode)
if (err(ast)) throw ast
const result = doesPipeHaveCallExp({
calleeName: 'extrude',
ast,
selection: {
codeRef: codeRefFromRange([100, 101, true], ast),
codeRef: codeRefFromRange([100, 101], ast),
},
})
expect(result).toEqual(true)
@ -414,26 +402,28 @@ part001 = startSketchAt([-1.41, 3.46])
|> line([-3.22, -7.36], %)
|> angledLine([-175, segLen(seg01)], %)
`
const ast = assertParse(exampleCode)
const ast = parse(exampleCode)
if (err(ast)) throw ast
const result = doesPipeHaveCallExp({
calleeName: 'close',
ast,
selection: {
codeRef: codeRefFromRange([100, 101, true], ast),
codeRef: codeRefFromRange([100, 101], ast),
},
})
expect(result).toEqual(false)
})
it('returns false if not a pipe', () => {
const exampleCode = `length001 = 2`
const ast = assertParse(exampleCode)
const ast = parse(exampleCode)
if (err(ast)) throw ast
const result = doesPipeHaveCallExp({
calleeName: 'close',
ast,
selection: {
codeRef: codeRefFromRange([9, 10, true], ast),
codeRef: codeRefFromRange([9, 10], ast),
},
})
expect(result).toEqual(false)
@ -448,13 +438,14 @@ part001 = startSketchAt([-1.41, 3.46])
|> angledLine([-35, length001], %)
|> line([-3.22, -7.36], %)
|> angledLine([-175, segLen(seg01)], %)`
const ast = assertParse(exampleCode)
const ast = parse(exampleCode)
if (err(ast)) throw ast
const execState = await enginelessExecutor(ast)
const result = hasExtrudeSketch({
ast,
selection: {
codeRef: codeRefFromRange([100, 101, true], ast),
codeRef: codeRefFromRange([100, 101], ast),
},
programMemory: execState.memory,
})
@ -468,13 +459,14 @@ part001 = startSketchAt([-1.41, 3.46])
|> line([-3.22, -7.36], %)
|> angledLine([-175, segLen(seg01)], %)
|> extrude(1, %)`
const ast = assertParse(exampleCode)
const ast = parse(exampleCode)
if (err(ast)) throw ast
const execState = await enginelessExecutor(ast)
const result = hasExtrudeSketch({
ast,
selection: {
codeRef: codeRefFromRange([100, 101, true], ast),
codeRef: codeRefFromRange([100, 101], ast),
},
programMemory: execState.memory,
})
@ -482,13 +474,14 @@ part001 = startSketchAt([-1.41, 3.46])
})
it('finds nothing', async () => {
const exampleCode = `length001 = 2`
const ast = assertParse(exampleCode)
const ast = parse(exampleCode)
if (err(ast)) throw ast
const execState = await enginelessExecutor(ast)
const result = hasExtrudeSketch({
ast,
selection: {
codeRef: codeRefFromRange([10, 11, true], ast),
codeRef: codeRefFromRange([10, 11], ast),
},
programMemory: execState.memory,
})
@ -505,7 +498,8 @@ describe('Testing findUsesOfTagInPipe', () => {
|> line([306.21, 198.87], %)
|> angledLine([65, segLen(seg01)], %)`
it('finds the current segment', async () => {
const ast = assertParse(exampleCode)
const ast = parse(exampleCode)
if (err(ast)) throw ast
const lineOfInterest = `198.85], %, $seg01`
const characterIndex =
@ -513,7 +507,6 @@ describe('Testing findUsesOfTagInPipe', () => {
const pathToNode = getNodePathFromSourceRange(ast, [
characterIndex,
characterIndex,
true,
])
const result = findUsesOfTagInPipe(ast, pathToNode)
expect(result).toHaveLength(2)
@ -522,7 +515,8 @@ describe('Testing findUsesOfTagInPipe', () => {
})
})
it('find no tag if line has no tag', () => {
const ast = assertParse(exampleCode)
const ast = parse(exampleCode)
if (err(ast)) throw ast
const lineOfInterest = `line([306.21, 198.82], %)`
const characterIndex =
@ -530,7 +524,6 @@ describe('Testing findUsesOfTagInPipe', () => {
const pathToNode = getNodePathFromSourceRange(ast, [
characterIndex,
characterIndex,
true,
])
const result = findUsesOfTagInPipe(ast, pathToNode)
expect(result).toHaveLength(0)
@ -571,39 +564,42 @@ sketch003 = startSketchOn(extrude001, 'END')
|> extrude(3.14, %)
`
it('identifies sketch001 pipe as extruded (extrusion after pipe)', async () => {
const ast = assertParse(exampleCode)
const ast = parse(exampleCode)
if (err(ast)) throw ast
const lineOfInterest = `line([4.99, -0.46], %, $seg01)`
const characterIndex =
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
const extruded = hasSketchPipeBeenExtruded(
{
codeRef: codeRefFromRange([characterIndex, characterIndex, true], ast),
codeRef: codeRefFromRange([characterIndex, characterIndex], ast),
},
ast
)
expect(extruded).toBeTruthy()
})
it('identifies sketch002 pipe as not extruded', async () => {
const ast = assertParse(exampleCode)
const ast = parse(exampleCode)
if (err(ast)) throw ast
const lineOfInterest = `line([2.45, -0.2], %)`
const characterIndex =
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
const extruded = hasSketchPipeBeenExtruded(
{
codeRef: codeRefFromRange([characterIndex, characterIndex, true], ast),
codeRef: codeRefFromRange([characterIndex, characterIndex], ast),
},
ast
)
expect(extruded).toBeFalsy()
})
it('identifies sketch003 pipe as extruded (extrusion within pipe)', async () => {
const ast = assertParse(exampleCode)
const ast = parse(exampleCode)
if (err(ast)) throw ast
const lineOfInterest = `|> line([3.12, 1.74], %)`
const characterIndex =
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
const extruded = hasSketchPipeBeenExtruded(
{
codeRef: codeRefFromRange([characterIndex, characterIndex, true], ast),
codeRef: codeRefFromRange([characterIndex, characterIndex], ast),
},
ast
)
@ -627,21 +623,11 @@ sketch002 = startSketchOn(extrude001, $seg01)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
`
const ast = assertParse(exampleCode)
const ast = parse(exampleCode)
if (err(ast)) throw ast
const extrudable = doesSceneHaveSweepableSketch(ast)
expect(extrudable).toBeTruthy()
})
it('finds sketch001 and sketch002 pipes to be lofted', async () => {
const exampleCode = `sketch001 = startSketchOn('XZ')
|> circle({ center = [0, 0], radius = 1 }, %)
plane001 = offsetPlane('XZ', 2)
sketch002 = startSketchOn(plane001)
|> circle({ center = [0, 0], radius = 3 }, %)
`
const ast = assertParse(exampleCode)
const extrudable = doesSceneHaveSweepableSketch(ast, 2)
expect(extrudable).toBeTruthy()
})
it('find sketch002 NOT pipe to be extruded', async () => {
const exampleCode = `sketch001 = startSketchOn('XZ')
|> startProfileAt([3.29, 7.86], %)
@ -651,7 +637,8 @@ sketch002 = startSketchOn(plane001)
|> close(%)
extrude001 = extrude(10, sketch001)
`
const ast = assertParse(exampleCode)
const ast = parse(exampleCode)
if (err(ast)) throw ast
const extrudable = doesSceneHaveSweepableSketch(ast)
expect(extrudable).toBeFalsy()
})
@ -679,7 +666,8 @@ myNestedVar = [
}
]
`
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) throw ast
let pathToNode: PathToNode = []
traverse(ast, {
enter: (node, path) => {
@ -701,7 +689,6 @@ myNestedVar = [
const pathToNode2 = getNodePathFromSourceRange(ast, [
literalIndex + 2,
literalIndex + 2,
true,
])
expect(pathToNode).toEqual(pathToNode2)
})

View File

@ -16,7 +16,6 @@ import {
sketchFromKclValue,
sketchFromKclValueOptional,
SourceRange,
sourceRangeFromRust,
SyntaxType,
VariableDeclaration,
VariableDeclarator,
@ -174,30 +173,6 @@ function moreNodePathFromSourceRange(
}
return path
}
if (_node.type === 'CallExpressionKw' && isInRange) {
const { callee, arguments: args } = _node
if (
callee.type === 'Identifier' &&
callee.start <= start &&
callee.end >= end
) {
path.push(['callee', 'CallExpressionKw'])
return path
}
if (args.length > 0) {
for (let argIndex = 0; argIndex < args.length; argIndex++) {
const arg = args[argIndex].arg
if (arg.start <= start && arg.end >= end) {
path.push(['arguments', 'CallExpressionKw'])
path.push([argIndex, 'index'])
return moreNodePathFromSourceRange(arg, sourceRange, path)
}
}
}
return path
}
if (_node.type === 'BinaryExpression' && isInRange) {
const { left, right } = _node
if (left.start <= start && left.end >= end) {
@ -670,7 +645,7 @@ export function isNodeSafeToReplacePath(
export function isNodeSafeToReplace(
ast: Node<Program>,
sourceRange: SourceRange
sourceRange: [number, number]
):
| {
isSafe: boolean
@ -822,7 +797,7 @@ export function isLinesParallelAndConstrained(
return {
isParallelAndConstrained,
selection: {
codeRef: codeRefFromRange(sourceRangeFromRust(prevSourceRange), ast),
codeRef: codeRefFromRange(prevSourceRange, ast),
artifact: artifactGraph.get(prevSegment.__geoMeta.id),
},
}
@ -958,8 +933,7 @@ export function findUsesOfTagInPipe(
return
const tagArgValue =
tagArg.type === 'TagDeclarator' ? String(tagArg.value) : tagArg.name
if (tagArgValue === tag)
dependentRanges.push([node.start, node.end, true])
if (tagArgValue === tag) dependentRanges.push([node.start, node.end])
},
})
return dependentRanges
@ -1001,9 +975,7 @@ export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) {
if (
node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
(node.callee.name === 'extrude' ||
node.callee.name === 'revolve' ||
node.callee.name === 'loft') &&
(node.callee.name === 'extrude' || node.callee.name === 'revolve') &&
node.arguments?.[1]?.type === 'Identifier' &&
node.arguments[1].name === varDec.id.name
) {
@ -1016,7 +988,7 @@ export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) {
}
/** File must contain at least one sketch that has not been extruded already */
export function doesSceneHaveSweepableSketch(ast: Node<Program>, count = 1) {
export function doesSceneHaveSweepableSketch(ast: Node<Program>) {
const theMap: any = {}
traverse(ast as any, {
enter(node) {
@ -1065,7 +1037,7 @@ export function doesSceneHaveSweepableSketch(ast: Node<Program>, count = 1) {
}
},
})
return Object.keys(theMap).length >= count
return Object.keys(theMap).length > 0
}
export function getObjExprProperty(

View File

@ -1,4 +1,4 @@
import { assertParse, Program, recast, initPromise } from './wasm'
import { parse, Program, recast, initPromise } from './wasm'
import fs from 'node:fs'
import { err } from 'lib/trap'
@ -394,6 +394,8 @@ describe('it recasts binary expression using brackets where needed', () => {
// helpers
function code2ast(code: string): { ast: Program } {
const ast = assertParse(code)
const ast = parse(code)
// eslint-ignore-next-line
if (err(ast)) throw ast
return { ast }
}

View File

@ -11,8 +11,8 @@ Map {
],
],
"range": [
12,
31,
37,
64,
0,
],
},

View File

@ -1,4 +1,4 @@
import { makeDefaultPlanes, assertParse, initPromise, Program } from 'lang/wasm'
import { makeDefaultPlanes, parse, initPromise, Program } from 'lang/wasm'
import { Models } from '@kittycad/lib'
import {
OrderedCommand,
@ -148,7 +148,11 @@ beforeAll(async () => {
][]
const cacheToWriteToFileTemp: Partial<CacheShape> = {}
for (const [codeKey, code] of cacheEntries) {
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) {
console.error(ast)
return Promise.reject(ast)
}
await kclManager.executeAst({ ast })
cacheToWriteToFileTemp[codeKey] = {
@ -399,7 +403,11 @@ describe('capture graph of sketchOnFaceOnFace...', () => {
})
function getCommands(codeKey: CodeKey): CacheShape[CodeKey] & { ast: Program } {
const ast = assertParse(codeKey)
const ast = parse(codeKey)
if (err(ast)) {
console.error(ast)
throw ast
}
const file = fs.readFileSync(fullPath, 'utf-8')
const parsed: CacheShape = JSON.parse(file)
// these either already exist from the last run, or were created in

Binary file not shown.

Before

Width:  |  Height:  |  Size: 357 KiB

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 577 KiB

After

Width:  |  Height:  |  Size: 613 KiB

View File

@ -1,4 +1,4 @@
import { defaultSourceRange, SourceRange } from 'lang/wasm'
import { SourceRange } from 'lang/wasm'
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_DEV_TOKEN } from 'env'
import { Models } from '@kittycad/lib'
import { exportSave } from 'lib/exportSave'
@ -1879,7 +1879,7 @@ export class EngineCommandManager extends EventTarget {
}
return JSON.stringify(this.defaultPlanes)
}
clearScene(): void {
endSession() {
const deleteCmd: EngineCommand = {
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
@ -2014,7 +2014,7 @@ export class EngineCommandManager extends EventTarget {
{
command,
idToRangeMap: {},
range: defaultSourceRange(),
range: [0, 0],
},
true // isSceneCommand
)

View File

@ -8,7 +8,7 @@ import {
getConstraintInfo,
} from './sketch'
import {
assertParse,
parse,
recast,
initPromise,
SourceRange,
@ -115,7 +115,8 @@ describe('testing changeSketchArguments', () => {
`
const code = genCode(lineToChange)
const expectedCode = genCode(lineAfterChange)
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) return ast
const execState = await enginelessExecutor(ast)
const sourceStart = code.indexOf(lineToChange)
@ -124,7 +125,7 @@ describe('testing changeSketchArguments', () => {
execState.memory,
{
type: 'sourceRange',
sourceRange: [sourceStart, sourceStart + lineToChange.length, true],
sourceRange: [sourceStart, sourceStart + lineToChange.length],
},
{
type: 'straight-segment',
@ -147,7 +148,8 @@ mySketch001 = startSketchOn('XY')
// |> rx(45, %)
|> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %)`
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) return ast
const execState = await enginelessExecutor(ast)
const sourceStart = code.indexOf(lineToChange)
@ -218,13 +220,12 @@ describe('testing addTagForSketchOnFace', () => {
|> lineTo([0.46, -5.82], %)
`
const code = genCode(originalLine)
const ast = assertParse(code)
const ast = parse(code)
await enginelessExecutor(ast)
const sourceStart = code.indexOf(originalLine)
const sourceRange: [number, number, boolean] = [
const sourceRange: [number, number] = [
sourceStart,
sourceStart + originalLine.length,
true,
]
if (err(ast)) return ast
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
@ -290,14 +291,13 @@ extrude001 = extrude(100, sketch001)
${insertCode}
`
const code = genCode(originalChamfer)
const ast = assertParse(code)
const ast = parse(code)
await enginelessExecutor(ast)
const sourceStart = code.indexOf(originalChamfer)
const extraChars = originalChamfer.indexOf('chamfer')
const sourceRange: [number, number, boolean] = [
const sourceRange: [number, number] = [
sourceStart + extraChars,
sourceStart + originalChamfer.length - extraChars,
true,
]
if (err(ast)) throw ast
@ -335,7 +335,7 @@ describe('testing getConstraintInfo', () => {
|> lineTo([6.14, 3.14], %)
|> xLineTo(8, %)
|> yLineTo(5, %)
|> yLine(3.14, %, $a)
|> yLine(3.14, %, 'a')
|> xLine(3.14, %)
|> angledLineOfXLength({
angle = 3.14,
@ -355,11 +355,11 @@ describe('testing getConstraintInfo', () => {
}, %)
|> angledLineThatIntersects({
angle = 3.14,
intersectTag = a,
intersectTag = 'a',
offset = 0
}, %)
|> tangentialArcTo([3.14, 13.14], %)`
const ast = assertParse(code)
const ast = parse(code)
test.each([
[
'line',
@ -368,7 +368,7 @@ describe('testing getConstraintInfo', () => {
type: 'xRelative',
isConstrained: false,
value: '3',
sourceRange: [78, 79, true],
sourceRange: [78, 79],
argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array),
stdLibFnName: 'line',
@ -377,7 +377,7 @@ describe('testing getConstraintInfo', () => {
type: 'yRelative',
isConstrained: false,
value: '4',
sourceRange: [81, 82, true],
sourceRange: [81, 82],
argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array),
stdLibFnName: 'line',
@ -391,7 +391,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: false,
value: '3.14',
sourceRange: [118, 122, true],
sourceRange: [117, 121],
argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLine',
@ -400,7 +400,7 @@ describe('testing getConstraintInfo', () => {
type: 'length',
isConstrained: false,
value: '3.14',
sourceRange: [137, 141, true],
sourceRange: [135, 139],
argPosition: { type: 'objectProperty', key: 'length' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLine',
@ -414,7 +414,7 @@ describe('testing getConstraintInfo', () => {
type: 'xAbsolute',
isConstrained: false,
value: '6.14',
sourceRange: [164, 168, true],
sourceRange: [162, 166],
argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array),
stdLibFnName: 'lineTo',
@ -423,7 +423,7 @@ describe('testing getConstraintInfo', () => {
type: 'yAbsolute',
isConstrained: false,
value: '3.14',
sourceRange: [170, 174, true],
sourceRange: [168, 172],
argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array),
stdLibFnName: 'lineTo',
@ -437,7 +437,7 @@ describe('testing getConstraintInfo', () => {
type: 'horizontal',
isConstrained: true,
value: 'xLineTo',
sourceRange: [185, 192, true],
sourceRange: [183, 190],
argPosition: undefined,
pathToNode: expect.any(Array),
stdLibFnName: 'xLineTo',
@ -446,7 +446,7 @@ describe('testing getConstraintInfo', () => {
type: 'xAbsolute',
isConstrained: false,
value: '8',
sourceRange: [193, 194, true],
sourceRange: [191, 192],
argPosition: { type: 'singleValue' },
pathToNode: expect.any(Array),
stdLibFnName: 'xLineTo',
@ -460,7 +460,7 @@ describe('testing getConstraintInfo', () => {
type: 'vertical',
isConstrained: true,
value: 'yLineTo',
sourceRange: [204, 211, true],
sourceRange: [202, 209],
argPosition: undefined,
pathToNode: expect.any(Array),
stdLibFnName: 'yLineTo',
@ -469,7 +469,7 @@ describe('testing getConstraintInfo', () => {
type: 'yAbsolute',
isConstrained: false,
value: '5',
sourceRange: [212, 213, true],
sourceRange: [210, 211],
argPosition: { type: 'singleValue' },
pathToNode: expect.any(Array),
stdLibFnName: 'yLineTo',
@ -483,7 +483,7 @@ describe('testing getConstraintInfo', () => {
type: 'vertical',
isConstrained: true,
value: 'yLine',
sourceRange: [223, 228, true],
sourceRange: [221, 226],
argPosition: undefined,
pathToNode: expect.any(Array),
stdLibFnName: 'yLine',
@ -492,7 +492,7 @@ describe('testing getConstraintInfo', () => {
type: 'yRelative',
isConstrained: false,
value: '3.14',
sourceRange: [229, 233, true],
sourceRange: [227, 231],
argPosition: { type: 'singleValue' },
pathToNode: expect.any(Array),
stdLibFnName: 'yLine',
@ -506,7 +506,7 @@ describe('testing getConstraintInfo', () => {
type: 'horizontal',
isConstrained: true,
value: 'xLine',
sourceRange: [247, 252, true],
sourceRange: [246, 251],
argPosition: undefined,
pathToNode: expect.any(Array),
stdLibFnName: 'xLine',
@ -515,7 +515,7 @@ describe('testing getConstraintInfo', () => {
type: 'xRelative',
isConstrained: false,
value: '3.14',
sourceRange: [253, 257, true],
sourceRange: [252, 256],
argPosition: { type: 'singleValue' },
pathToNode: expect.any(Array),
stdLibFnName: 'xLine',
@ -529,7 +529,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: false,
value: '3.14',
sourceRange: [301, 305, true],
sourceRange: [299, 303],
argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfXLength',
@ -538,7 +538,7 @@ describe('testing getConstraintInfo', () => {
type: 'xRelative',
isConstrained: false,
value: '3.14',
sourceRange: [320, 324, true],
sourceRange: [317, 321],
argPosition: { type: 'objectProperty', key: 'length' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfXLength',
@ -552,7 +552,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: false,
value: '30',
sourceRange: [373, 375, true],
sourceRange: [369, 371],
argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfYLength',
@ -561,7 +561,7 @@ describe('testing getConstraintInfo', () => {
type: 'yRelative',
isConstrained: false,
value: '3',
sourceRange: [390, 391, true],
sourceRange: [385, 386],
argPosition: { type: 'objectProperty', key: 'length' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfYLength',
@ -575,7 +575,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: false,
value: '12.14',
sourceRange: [434, 439, true],
sourceRange: [428, 433],
argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToX',
@ -584,7 +584,7 @@ describe('testing getConstraintInfo', () => {
type: 'xAbsolute',
isConstrained: false,
value: '12',
sourceRange: [450, 452, true],
sourceRange: [443, 445],
argPosition: { type: 'objectProperty', key: 'to' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToX',
@ -598,7 +598,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: false,
value: '30',
sourceRange: [495, 497, true],
sourceRange: [487, 489],
argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToY',
@ -607,7 +607,7 @@ describe('testing getConstraintInfo', () => {
type: 'yAbsolute',
isConstrained: false,
value: '10.14',
sourceRange: [508, 513, true],
sourceRange: [499, 504],
argPosition: { type: 'objectProperty', key: 'to' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToY',
@ -621,7 +621,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: false,
value: '3.14',
sourceRange: [567, 571, true],
sourceRange: [557, 561],
argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineThatIntersects',
@ -630,7 +630,7 @@ describe('testing getConstraintInfo', () => {
type: 'intersectionOffset',
isConstrained: false,
value: '0',
sourceRange: [608, 609, true],
sourceRange: [598, 599],
argPosition: { type: 'objectProperty', key: 'offset' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineThatIntersects',
@ -638,8 +638,8 @@ describe('testing getConstraintInfo', () => {
{
type: 'intersectionTag',
isConstrained: false,
value: 'a',
sourceRange: [592, 593, true],
value: "'a'",
sourceRange: [581, 584],
argPosition: {
key: 'intersectTag',
type: 'objectProperty',
@ -656,7 +656,7 @@ describe('testing getConstraintInfo', () => {
type: 'tangentialWithPrevious',
isConstrained: true,
value: 'tangentialArcTo',
sourceRange: [623, 638, true],
sourceRange: [613, 628],
argPosition: undefined,
pathToNode: expect.any(Array),
stdLibFnName: 'tangentialArcTo',
@ -665,7 +665,7 @@ describe('testing getConstraintInfo', () => {
type: 'xAbsolute',
isConstrained: false,
value: '3.14',
sourceRange: [640, 644, true],
sourceRange: [630, 634],
argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array),
stdLibFnName: 'tangentialArcTo',
@ -674,7 +674,7 @@ describe('testing getConstraintInfo', () => {
type: 'yAbsolute',
isConstrained: false,
value: '13.14',
sourceRange: [646, 651, true],
sourceRange: [636, 641],
argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array),
stdLibFnName: 'tangentialArcTo',
@ -685,7 +685,6 @@ describe('testing getConstraintInfo', () => {
const sourceRange: SourceRange = [
code.indexOf(functionName),
code.indexOf(functionName) + functionName.length,
true,
]
if (err(ast)) return ast
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
@ -707,7 +706,7 @@ describe('testing getConstraintInfo', () => {
|> lineTo([6.14, 3.14], %)
|> xLineTo(8, %)
|> yLineTo(5, %)
|> yLine(3.14, %, $a)
|> yLine(3.14, %, 'a')
|> xLine(3.14, %)
|> angledLineOfXLength([3.14, 3.14], %)
|> angledLineOfYLength([30, 3], %)
@ -715,11 +714,11 @@ describe('testing getConstraintInfo', () => {
|> angledLineToY([30, 10], %)
|> angledLineThatIntersects({
angle = 3.14,
intersectTag = a,
intersectTag = 'a',
offset = 0
}, %)
|> tangentialArcTo([3.14, 13.14], %)`
const ast = assertParse(code)
const ast = parse(code)
test.each([
[
`angledLine(`,
@ -728,7 +727,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: false,
value: '3.14',
sourceRange: [112, 116, true],
sourceRange: [112, 116],
argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLine',
@ -737,7 +736,7 @@ describe('testing getConstraintInfo', () => {
type: 'length',
isConstrained: false,
value: '3.14',
sourceRange: [118, 122, true],
sourceRange: [118, 122],
argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLine',
@ -751,7 +750,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: false,
value: '3.14',
sourceRange: [277, 281, true],
sourceRange: [278, 282],
argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfXLength',
@ -760,7 +759,7 @@ describe('testing getConstraintInfo', () => {
type: 'xRelative',
isConstrained: false,
value: '3.14',
sourceRange: [283, 287, true],
sourceRange: [284, 288],
argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfXLength',
@ -774,7 +773,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: false,
value: '30',
sourceRange: [321, 323, true],
sourceRange: [322, 324],
argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfYLength',
@ -783,7 +782,7 @@ describe('testing getConstraintInfo', () => {
type: 'yRelative',
isConstrained: false,
value: '3',
sourceRange: [325, 326, true],
sourceRange: [326, 327],
argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfYLength',
@ -797,7 +796,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: false,
value: '12',
sourceRange: [354, 356, true],
sourceRange: [355, 357],
argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToX',
@ -806,7 +805,7 @@ describe('testing getConstraintInfo', () => {
type: 'xAbsolute',
isConstrained: false,
value: '12',
sourceRange: [358, 360, true],
sourceRange: [359, 361],
argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToX',
@ -820,7 +819,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: false,
value: '30',
sourceRange: [388, 390, true],
sourceRange: [389, 391],
argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToY',
@ -829,7 +828,7 @@ describe('testing getConstraintInfo', () => {
type: 'yAbsolute',
isConstrained: false,
value: '10',
sourceRange: [392, 394, true],
sourceRange: [393, 395],
argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToY',
@ -840,7 +839,6 @@ describe('testing getConstraintInfo', () => {
const sourceRange: SourceRange = [
code.indexOf(functionName),
code.indexOf(functionName) + functionName.length,
true,
]
if (err(ast)) return ast
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
@ -862,7 +860,7 @@ describe('testing getConstraintInfo', () => {
|> lineTo([6.14 + 0, 3.14 + 0], %)
|> xLineTo(8 + 0, %)
|> yLineTo(5 + 0, %)
|> yLine(3.14 + 0, %, $a)
|> yLine(3.14 + 0, %, 'a')
|> xLine(3.14 + 0, %)
|> angledLineOfXLength({ angle = 3.14 + 0, length = 3.14 + 0 }, %)
|> angledLineOfYLength({ angle = 30 + 0, length = 3 + 0 }, %)
@ -870,11 +868,11 @@ describe('testing getConstraintInfo', () => {
|> angledLineToY({ angle = 30 + 0, to = 10.14 + 0 }, %)
|> angledLineThatIntersects({
angle = 3.14 + 0,
intersectTag = a,
intersectTag = 'a',
offset = 0 + 0
}, %)
|> tangentialArcTo([3.14 + 0, 13.14 + 0], %)`
const ast = assertParse(code)
const ast = parse(code)
test.each([
[
'line',
@ -883,7 +881,7 @@ describe('testing getConstraintInfo', () => {
type: 'xRelative',
isConstrained: true,
value: '3 + 0',
sourceRange: [83, 88, true],
sourceRange: [83, 88],
argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array),
stdLibFnName: 'line',
@ -892,7 +890,7 @@ describe('testing getConstraintInfo', () => {
type: 'yRelative',
isConstrained: true,
value: '4 + 0',
sourceRange: [90, 95, true],
sourceRange: [90, 95],
argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array),
stdLibFnName: 'line',
@ -906,7 +904,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: true,
value: '3.14 + 0',
sourceRange: [129, 137, true],
sourceRange: [128, 136],
argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLine',
@ -915,7 +913,7 @@ describe('testing getConstraintInfo', () => {
type: 'length',
isConstrained: true,
value: '3.14 + 0',
sourceRange: [148, 156, true],
sourceRange: [146, 154],
argPosition: { type: 'objectProperty', key: 'length' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLine',
@ -929,7 +927,7 @@ describe('testing getConstraintInfo', () => {
type: 'xAbsolute',
isConstrained: true,
value: '6.14 + 0',
sourceRange: [178, 186, true],
sourceRange: [176, 184],
argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array),
stdLibFnName: 'lineTo',
@ -938,7 +936,7 @@ describe('testing getConstraintInfo', () => {
type: 'yAbsolute',
isConstrained: true,
value: '3.14 + 0',
sourceRange: [188, 196, true],
sourceRange: [186, 194],
argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array),
stdLibFnName: 'lineTo',
@ -952,7 +950,7 @@ describe('testing getConstraintInfo', () => {
type: 'horizontal',
isConstrained: true,
value: 'xLineTo',
sourceRange: [209, 216, true],
sourceRange: [207, 214],
argPosition: undefined,
pathToNode: expect.any(Array),
stdLibFnName: 'xLineTo',
@ -961,7 +959,7 @@ describe('testing getConstraintInfo', () => {
type: 'xAbsolute',
isConstrained: true,
value: '8 + 0',
sourceRange: [217, 222, true],
sourceRange: [215, 220],
argPosition: { type: 'singleValue' },
pathToNode: expect.any(Array),
stdLibFnName: 'xLineTo',
@ -975,7 +973,7 @@ describe('testing getConstraintInfo', () => {
type: 'vertical',
isConstrained: true,
value: 'yLineTo',
sourceRange: [234, 241, true],
sourceRange: [232, 239],
argPosition: undefined,
pathToNode: expect.any(Array),
stdLibFnName: 'yLineTo',
@ -984,7 +982,7 @@ describe('testing getConstraintInfo', () => {
type: 'yAbsolute',
isConstrained: true,
value: '5 + 0',
sourceRange: [242, 247, true],
sourceRange: [240, 245],
argPosition: { type: 'singleValue' },
pathToNode: expect.any(Array),
stdLibFnName: 'yLineTo',
@ -998,7 +996,7 @@ describe('testing getConstraintInfo', () => {
type: 'vertical',
isConstrained: true,
value: 'yLine',
sourceRange: [259, 264, true],
sourceRange: [257, 262],
argPosition: undefined,
pathToNode: expect.any(Array),
stdLibFnName: 'yLine',
@ -1007,7 +1005,7 @@ describe('testing getConstraintInfo', () => {
type: 'yRelative',
isConstrained: true,
value: '3.14 + 0',
sourceRange: [265, 273, true],
sourceRange: [263, 271],
argPosition: { type: 'singleValue' },
pathToNode: expect.any(Array),
stdLibFnName: 'yLine',
@ -1021,7 +1019,7 @@ describe('testing getConstraintInfo', () => {
type: 'horizontal',
isConstrained: true,
value: 'xLine',
sourceRange: [289, 294, true],
sourceRange: [288, 293],
argPosition: undefined,
pathToNode: expect.any(Array),
stdLibFnName: 'xLine',
@ -1030,7 +1028,7 @@ describe('testing getConstraintInfo', () => {
type: 'xRelative',
isConstrained: true,
value: '3.14 + 0',
sourceRange: [295, 303, true],
sourceRange: [294, 302],
argPosition: { type: 'singleValue' },
pathToNode: expect.any(Array),
stdLibFnName: 'xLine',
@ -1044,7 +1042,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: true,
value: '3.14 + 0',
sourceRange: [345, 353, true],
sourceRange: [343, 351],
argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfXLength',
@ -1053,7 +1051,7 @@ describe('testing getConstraintInfo', () => {
type: 'xRelative',
isConstrained: true,
value: '3.14 + 0',
sourceRange: [364, 372, true],
sourceRange: [361, 369],
argPosition: { type: 'objectProperty', key: 'length' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfXLength',
@ -1067,7 +1065,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: true,
value: '30 + 0',
sourceRange: [416, 422, true],
sourceRange: [412, 418],
argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfYLength',
@ -1076,7 +1074,7 @@ describe('testing getConstraintInfo', () => {
type: 'yRelative',
isConstrained: true,
value: '3 + 0',
sourceRange: [433, 438, true],
sourceRange: [428, 433],
argPosition: { type: 'objectProperty', key: 'length' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfYLength',
@ -1090,7 +1088,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: true,
value: '12.14 + 0',
sourceRange: [476, 485, true],
sourceRange: [470, 479],
argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToX',
@ -1099,7 +1097,7 @@ describe('testing getConstraintInfo', () => {
type: 'xAbsolute',
isConstrained: true,
value: '12 + 0',
sourceRange: [492, 498, true],
sourceRange: [485, 491],
argPosition: { type: 'objectProperty', key: 'to' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToX',
@ -1113,7 +1111,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: true,
value: '30 + 0',
sourceRange: [536, 542, true],
sourceRange: [528, 534],
argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToY',
@ -1122,7 +1120,7 @@ describe('testing getConstraintInfo', () => {
type: 'yAbsolute',
isConstrained: true,
value: '10.14 + 0',
sourceRange: [549, 558, true],
sourceRange: [540, 549],
argPosition: { type: 'objectProperty', key: 'to' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToY',
@ -1136,7 +1134,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: true,
value: '3.14 + 0',
sourceRange: [616, 624, true],
sourceRange: [606, 614],
argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineThatIntersects',
@ -1145,7 +1143,7 @@ describe('testing getConstraintInfo', () => {
type: 'intersectionOffset',
isConstrained: true,
value: '0 + 0',
sourceRange: [671, 676, true],
sourceRange: [661, 666],
argPosition: { type: 'objectProperty', key: 'offset' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineThatIntersects',
@ -1153,8 +1151,8 @@ describe('testing getConstraintInfo', () => {
{
type: 'intersectionTag',
isConstrained: false,
value: 'a',
sourceRange: [650, 651, true],
value: "'a'",
sourceRange: [639, 642],
argPosition: { key: 'intersectTag', type: 'objectProperty' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineThatIntersects',
@ -1168,7 +1166,7 @@ describe('testing getConstraintInfo', () => {
type: 'tangentialWithPrevious',
isConstrained: true,
value: 'tangentialArcTo',
sourceRange: [697, 712, true],
sourceRange: [687, 702],
argPosition: undefined,
pathToNode: expect.any(Array),
stdLibFnName: 'tangentialArcTo',
@ -1177,7 +1175,7 @@ describe('testing getConstraintInfo', () => {
type: 'xAbsolute',
isConstrained: true,
value: '3.14 + 0',
sourceRange: [714, 722, true],
sourceRange: [704, 712],
argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array),
stdLibFnName: 'tangentialArcTo',
@ -1186,7 +1184,7 @@ describe('testing getConstraintInfo', () => {
type: 'yAbsolute',
isConstrained: true,
value: '13.14 + 0',
sourceRange: [724, 733, true],
sourceRange: [714, 723],
argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array),
stdLibFnName: 'tangentialArcTo',
@ -1197,7 +1195,6 @@ describe('testing getConstraintInfo', () => {
const sourceRange: SourceRange = [
code.indexOf(functionName),
code.indexOf(functionName) + functionName.length,
true,
]
if (err(ast)) return ast
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)

View File

@ -222,7 +222,7 @@ const commonConstraintInfoHelper = (
code.slice(input1.start, input1.end),
stdLibFnName,
isArr ? abbreviatedInputs[0].arrayInput : abbreviatedInputs[0].objInput,
[input1.start, input1.end, true],
[input1.start, input1.end],
pathToFirstArg
)
)
@ -234,7 +234,7 @@ const commonConstraintInfoHelper = (
code.slice(input2.start, input2.end),
stdLibFnName,
isArr ? abbreviatedInputs[1].arrayInput : abbreviatedInputs[1].objInput,
[input2.start, input2.end, true],
[input2.start, input2.end],
pathToSecondArg
)
)
@ -266,7 +266,7 @@ const horzVertConstraintInfoHelper = (
callee.name,
stdLibFnName,
undefined,
[callee.start, callee.end, true],
[callee.start, callee.end],
pathToCallee
),
constrainInfo(
@ -275,7 +275,7 @@ const horzVertConstraintInfoHelper = (
code.slice(firstArg.start, firstArg.end),
stdLibFnName,
abbreviatedInput,
[firstArg.start, firstArg.end, true],
[firstArg.start, firstArg.end],
pathToFirstArg
),
]
@ -905,7 +905,7 @@ export const tangentialArcTo: SketchLineHelper = {
callee.name,
'tangentialArcTo',
undefined,
[callee.start, callee.end, true],
[callee.start, callee.end],
pathToCallee
),
constrainInfo(
@ -914,7 +914,7 @@ export const tangentialArcTo: SketchLineHelper = {
code.slice(firstArg.elements[0].start, firstArg.elements[0].end),
'tangentialArcTo',
0,
[firstArg.elements[0].start, firstArg.elements[0].end, true],
[firstArg.elements[0].start, firstArg.elements[0].end],
pathToFirstArg
),
constrainInfo(
@ -923,7 +923,7 @@ export const tangentialArcTo: SketchLineHelper = {
code.slice(firstArg.elements[1].start, firstArg.elements[1].end),
'tangentialArcTo',
1,
[firstArg.elements[1].start, firstArg.elements[1].end, true],
[firstArg.elements[1].start, firstArg.elements[1].end],
pathToSecondArg
),
]
@ -1052,7 +1052,7 @@ export const circle: SketchLineHelper = {
code.slice(radiusDetails.expr.start, radiusDetails.expr.end),
'circle',
'radius',
[radiusDetails.expr.start, radiusDetails.expr.end, true],
[radiusDetails.expr.start, radiusDetails.expr.end],
pathToRadiusLiteral
),
{
@ -1064,7 +1064,6 @@ export const circle: SketchLineHelper = {
sourceRange: [
centerDetails.expr.elements[0].start,
centerDetails.expr.elements[0].end,
true,
],
pathToNode: pathToXArg,
value: code.slice(
@ -1086,7 +1085,6 @@ export const circle: SketchLineHelper = {
sourceRange: [
centerDetails.expr.elements[1].start,
centerDetails.expr.elements[1].end,
true,
],
pathToNode: pathToYArg,
value: code.slice(
@ -1763,7 +1761,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
code.slice(angle.start, angle.end),
'angledLineThatIntersects',
'angle',
[angle.start, angle.end, true],
[angle.start, angle.end],
pathToAngleProp
)
)
@ -1782,7 +1780,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
code.slice(offset.start, offset.end),
'angledLineThatIntersects',
'offset',
[offset.start, offset.end, true],
[offset.start, offset.end],
pathToOffsetProp
)
)
@ -1801,7 +1799,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
code.slice(tag.start, tag.end),
'angledLineThatIntersects',
'intersectTag',
[tag.start, tag.end, true],
[tag.start, tag.end],
pathToTagProp
)
returnVal.push(info)

View File

@ -1,5 +1,5 @@
import {
assertParse,
parse,
Sketch,
recast,
initPromise,
@ -31,11 +31,12 @@ async function testingSwapSketchFnCall({
constraintType: ConstraintType
}): Promise<{
newCode: string
originalRange: [number, number, boolean]
originalRange: [number, number]
}> {
const startIndex = inputCode.indexOf(callToSwap)
const range: SourceRange = [startIndex, startIndex + callToSwap.length, true]
const ast = assertParse(inputCode)
const range: SourceRange = [startIndex, startIndex + callToSwap.length]
const ast = parse(inputCode)
if (err(ast)) return Promise.reject(ast)
const execState = await enginelessExecutor(ast)
const selections = {
@ -369,13 +370,13 @@ part001 = startSketchOn('XY')
|> line([2.14, 1.35], %) // normal-segment
|> xLine(3.54, %)`
it('normal case works', async () => {
const execState = await enginelessExecutor(assertParse(code))
const execState = await enginelessExecutor(parse(code))
const index = code.indexOf('// normal-segment') - 7
const sg = sketchFromKclValue(
execState.memory.get('part001'),
'part001'
) as Sketch
const _segment = getSketchSegmentFromSourceRange(sg, [index, index, true])
const _segment = getSketchSegmentFromSourceRange(sg, [index, index])
if (err(_segment)) throw _segment
const { __geoMeta, ...segment } = _segment.segment
expect(segment).toEqual({
@ -386,11 +387,11 @@ part001 = startSketchOn('XY')
})
})
it('verify it works when the segment is in the `start` property', async () => {
const execState = await enginelessExecutor(assertParse(code))
const execState = await enginelessExecutor(parse(code))
const index = code.indexOf('// segment-in-start') - 7
const _segment = getSketchSegmentFromSourceRange(
sketchFromKclValue(execState.memory.get('part001'), 'part001') as Sketch,
[index, index, true]
[index, index]
)
if (err(_segment)) throw _segment
const { __geoMeta, ...segment } = _segment.segment

View File

@ -31,7 +31,7 @@ export function getSketchSegmentFromPathToNode(
const node = nodeMeta.node
if (!node || typeof node.start !== 'number' || !node.end)
return new Error('no node found')
const sourceRange: SourceRange = [node.start, node.end, true]
const sourceRange: SourceRange = [node.start, node.end]
return getSketchSegmentFromSourceRange(sketch, sourceRange)
}
export function getSketchSegmentFromSourceRange(

View File

@ -1,4 +1,4 @@
import { assertParse, Expr, recast, initPromise, Program } from '../wasm'
import { parse, Expr, recast, initPromise, Program } from '../wasm'
import {
getConstraintType,
getTransformInfos,
@ -66,7 +66,8 @@ describe('testing getConstraintType', () => {
function getConstraintTypeFromSourceHelper(
code: string
): ReturnType<typeof getConstraintType> | Error {
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) return ast
const args = (ast.body[0] as any).expression.arguments[0].elements as [
Expr,
@ -78,7 +79,8 @@ function getConstraintTypeFromSourceHelper(
function getConstraintTypeFromSourceHelper2(
code: string
): ReturnType<typeof getConstraintType> | Error {
const ast = assertParse(code)
const ast = parse(code)
if (err(ast)) return ast
const arg = (ast.body[0] as any).expression.arguments[0] as Expr
const fnName = (ast.body[0] as any).expression.callee.name as ToolTip
@ -125,7 +127,7 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
)
}
const start = codeBeforeLine + line.indexOf('|> ' + 5)
const range: [number, number, boolean] = [start, start, true]
const range: [number, number] = [start, start]
return {
codeRef: codeRefFromRange(range, ast),
}
@ -135,7 +137,8 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
inputCode: string,
selectionRanges: Selections['graphSelections']
) {
const ast = assertParse(inputCode)
const ast = parse(inputCode)
if (err(ast)) return Promise.reject(ast)
const execState = await enginelessExecutor(ast)
const transformInfos = getTransformInfos(
makeSelections(selectionRanges.slice(1)),
@ -158,7 +161,8 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
}
it(`Should reorder when user selects first-to-last`, async () => {
const ast = assertParse(inputScript)
const ast = parse(inputScript)
if (err(ast)) return Promise.reject(ast)
const selectionRanges: Selections['graphSelections'] = [
selectLine(inputScript, 3, ast),
selectLine(inputScript, 4, ast),
@ -169,7 +173,8 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
})
it(`Should reorder when user selects last-to-first`, async () => {
const ast = assertParse(inputScript)
const ast = parse(inputScript)
if (err(ast)) return Promise.reject(ast)
const selectionRanges: Selections['graphSelections'] = [
selectLine(inputScript, 4, ast),
selectLine(inputScript, 3, ast),
@ -288,7 +293,8 @@ part001 = startSketchOn('XY')
|> yLine(segLen(seg01), %) // ln-yLineTo-free should convert to yLine
`
it('should transform the ast', async () => {
const ast = assertParse(inputScript)
const ast = parse(inputScript)
if (err(ast)) return Promise.reject(ast)
const selectionRanges: Selections['graphSelections'] = inputScript
.split('\n')
@ -297,7 +303,7 @@ part001 = startSketchOn('XY')
const comment = ln.split('//')[1]
const start = inputScript.indexOf('//' + comment) - 7
return {
codeRef: codeRefFromRange([start, start, true], ast),
codeRef: codeRefFromRange([start, start], ast),
}
})
@ -377,7 +383,8 @@ part001 = startSketchOn('XY')
|> xLineTo(myVar3, %) // select for horizontal constraint 10
|> angledLineToY([301, myVar], %) // select for vertical constraint 10
`
const ast = assertParse(inputScript)
const ast = parse(inputScript)
if (err(ast)) return Promise.reject(ast)
const selectionRanges: Selections['graphSelections'] = inputScript
.split('\n')
@ -386,7 +393,7 @@ part001 = startSketchOn('XY')
const comment = ln.split('//')[1]
const start = inputScript.indexOf('//' + comment) - 7
return {
codeRef: codeRefFromRange([start, start, true], ast),
codeRef: codeRefFromRange([start, start], ast),
}
})
@ -437,7 +444,8 @@ part001 = startSketchOn('XY')
|> angledLineToX([333, myVar3], %) // select for horizontal constraint 10
|> yLineTo(myVar, %) // select for vertical constraint 10
`
const ast = assertParse(inputScript)
const ast = parse(inputScript)
if (err(ast)) return Promise.reject(ast)
const selectionRanges: Selections['graphSelections'] = inputScript
.split('\n')
@ -446,7 +454,7 @@ part001 = startSketchOn('XY')
const comment = ln.split('//')[1]
const start = inputScript.indexOf('//' + comment) - 7
return {
codeRef: codeRefFromRange([start, start, true], ast),
codeRef: codeRefFromRange([start, start], ast),
}
})
@ -530,7 +538,8 @@ async function helperThing(
linesOfInterest: string[],
constraint: ConstraintType
): Promise<string> {
const ast = assertParse(inputScript)
const ast = parse(inputScript)
if (err(ast)) return Promise.reject(ast)
const selectionRanges: Selections['graphSelections'] = inputScript
.split('\n')
@ -541,7 +550,7 @@ async function helperThing(
const comment = ln.split('//')[1]
const start = inputScript.indexOf('//' + comment) - 7
return {
codeRef: codeRefFromRange([start, start, true], ast),
codeRef: codeRefFromRange([start, start], ast),
}
})
@ -597,7 +606,7 @@ part001 = startSketchOn('XY')
|> line([-1.49, 1.06], %) // free
|> xLine(-3.43 + 0, %) // full
|> angledLineOfXLength([243 + 0, 1.2 + 0], %) // full`
const ast = assertParse(code)
const ast = parse(code)
const constraintLevels: ConstraintLevel[] = ['full', 'partial', 'free']
constraintLevels.forEach((constraintLevel) => {
const recursivelySearchCommentsAndCheckConstraintLevel = (
@ -610,7 +619,7 @@ part001 = startSketchOn('XY')
}
const offsetIndex = index - 7
const expectedConstraintLevel = getConstraintLevelFromSourceRange(
[offsetIndex, offsetIndex, true],
[offsetIndex, offsetIndex],
ast
)
if (err(expectedConstraintLevel)) {

View File

@ -1,4 +1,4 @@
import { assertParse, initPromise } from '../wasm'
import { parse, initPromise } from '../wasm'
import { enginelessExecutor } from '../../lib/testHelpers'
beforeAll(async () => {
@ -17,9 +17,9 @@ describe('testing angledLineThatIntersects', () => {
offset: ${offset},
}, %, $yo2)
intersect = segEndX(yo2)`
const execState = await enginelessExecutor(assertParse(code('-1')))
const execState = await enginelessExecutor(parse(code('-1')))
expect(execState.memory.get('intersect')?.value).toBe(1 + Math.sqrt(2))
const noOffset = await enginelessExecutor(assertParse(code('0')))
const noOffset = await enginelessExecutor(parse(code('0')))
expect(noOffset.memory.get('intersect')?.value).toBeCloseTo(1)
})
})

View File

@ -1,18 +1,13 @@
import { err } from 'lib/trap'
import { parse, ParseResult } from './wasm'
import { parse } from './wasm'
import { enginelessExecutor } from 'lib/testHelpers'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import { Program } from '../wasm-lib/kcl/bindings/Program'
it('can execute parsed AST', async () => {
const code = `x = 1
// A comment.`
const result = parse(code)
expect(err(result)).toEqual(false)
const pResult = result as ParseResult
expect(pResult.errors.length).toEqual(0)
expect(pResult.program).not.toEqual(null)
const execState = await enginelessExecutor(pResult.program as Node<Program>)
expect(err(execState)).toEqual(false)
const ast = parse(code)
expect(err(ast)).toEqual(false)
const execState = await enginelessExecutor(ast)
expect(err(ast)).toEqual(false)
expect(execState.memory.get('x')?.value).toEqual(1)
})

View File

@ -35,13 +35,12 @@ import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
import { DeepPartial } from 'lib/types'
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
import { Sketch } from '../wasm-lib/kcl/bindings/Sketch'
import { IdGenerator } from 'wasm-lib/kcl/bindings/IdGenerator'
import { ExecState as RawExecState } from '../wasm-lib/kcl/bindings/ExecState'
import { ProgramMemory as RawProgramMemory } from '../wasm-lib/kcl/bindings/ProgramMemory'
import { EnvironmentRef } from '../wasm-lib/kcl/bindings/EnvironmentRef'
import { Environment } from '../wasm-lib/kcl/bindings/Environment'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import { CompilationError } from 'wasm-lib/kcl/bindings/CompilationError'
import { SourceRange as RustSourceRange } from 'wasm-lib/kcl/bindings/SourceRange'
export type { Program } from '../wasm-lib/kcl/bindings/Program'
export type { Expr } from '../wasm-lib/kcl/bindings/Expr'
@ -85,22 +84,13 @@ export type SyntaxType =
| 'NonCodeNode'
| 'UnaryExpression'
export type { SourceRange } from '../wasm-lib/kcl/bindings/SourceRange'
export type { Path } from '../wasm-lib/kcl/bindings/Path'
export type { Sketch } from '../wasm-lib/kcl/bindings/Sketch'
export type { Solid } from '../wasm-lib/kcl/bindings/Solid'
export type { KclValue } from '../wasm-lib/kcl/bindings/KclValue'
export type { ExtrudeSurface } from '../wasm-lib/kcl/bindings/ExtrudeSurface'
export type SourceRange = [number, number, boolean]
export function sourceRangeFromRust(s: RustSourceRange): SourceRange {
return [s[0], s[1], s[2] === 0]
}
export function defaultSourceRange(): SourceRange {
return [0, 0, true]
}
export const wasmUrl = () => {
// For when we're in electron (file based) or web server (network based)
// For some reason relative paths don't work as expected. Otherwise we would
@ -130,81 +120,26 @@ const initialise = async () => {
export const initPromise = initialise()
const splitErrors = (
input: CompilationError[]
): { errors: CompilationError[]; warnings: CompilationError[] } => {
let errors = []
let warnings = []
for (const i of input) {
if (i.severity === 'Warning') {
warnings.push(i)
} else {
errors.push(i)
}
}
export const rangeTypeFix = (ranges: number[][]): [number, number, number][] =>
ranges.map(([start, end, moduleId]) => [start, end, moduleId])
return { errors, warnings }
}
export class ParseResult {
program: Node<Program> | null
errors: CompilationError[]
warnings: CompilationError[]
constructor(
program: Node<Program> | null,
errors: CompilationError[],
warnings: CompilationError[]
) {
this.program = program
this.errors = errors
this.warnings = warnings
}
}
class SuccessParseResult extends ParseResult {
program: Node<Program>
constructor(
program: Node<Program>,
errors: CompilationError[],
warnings: CompilationError[]
) {
super(program, errors, warnings)
this.program = program
}
}
export function resultIsOk(result: ParseResult): result is SuccessParseResult {
return !!result.program && result.errors.length === 0
}
export const parse = (code: string | Error): ParseResult | Error => {
export const parse = (code: string | Error): Node<Program> | Error => {
if (err(code)) return code
try {
const parsed: [Node<Program>, CompilationError[]] = parse_wasm(code)
let errs = splitErrors(parsed[1])
return new ParseResult(parsed[0], errs.errors, errs.warnings)
const program: Node<Program> = parse_wasm(code)
return program
} catch (e: any) {
// throw e
const parsed: RustKclError = JSON.parse(e.toString())
return new KCLError(
parsed.kind,
parsed.msg,
sourceRangeFromRust(parsed.sourceRanges[0])
rangeTypeFix(parsed.sourceRanges)
)
}
}
// Parse and throw an exception if there are any errors (probably not suitable for use outside of testing).
export const assertParse = (code: string): Node<Program> => {
const result = parse(code)
// eslint-disable-next-line suggest-no-throw/suggest-no-throw
if (err(result) || !resultIsOk(result)) throw result
return result.program
}
export type PathToNode = [string | number, string][]
export const isPathToNodeNumber = (
@ -215,6 +150,7 @@ export const isPathToNodeNumber = (
export interface ExecState {
memory: ProgramMemory
idGenerator: IdGenerator
}
/**
@ -224,12 +160,21 @@ export interface ExecState {
export function emptyExecState(): ExecState {
return {
memory: ProgramMemory.empty(),
idGenerator: defaultIdGenerator(),
}
}
function execStateFromRaw(raw: RawExecState): ExecState {
return {
memory: ProgramMemory.fromRaw(raw.memory),
idGenerator: raw.idGenerator,
}
}
export function defaultIdGenerator(): IdGenerator {
return {
nextId: 0,
ids: [],
}
}
@ -243,19 +188,6 @@ function emptyEnvironment(): Environment {
return { bindings: {}, parent: null }
}
function emptyRootEnvironment(): Environment {
return {
// This is dumb this is copied from rust.
bindings: {
ZERO: { type: 'Number', value: 0.0, __meta: [] },
QUARTER_TURN: { type: 'Number', value: 90.0, __meta: [] },
HALF_TURN: { type: 'Number', value: 180.0, __meta: [] },
THREE_QUARTER_TURN: { type: 'Number', value: 270.0, __meta: [] },
},
parent: null,
}
}
/**
* This duplicates logic in Rust. The hope is to keep ProgramMemory internals
* isolated from the rest of the TypeScript code so that we can move it to Rust
@ -278,7 +210,7 @@ export class ProgramMemory {
}
constructor(
environments: Environment[] = [emptyRootEnvironment()],
environments: Environment[] = [emptyEnvironment()],
currentEnv: EnvironmentRef = ROOT_ENVIRONMENT_REF,
returnVal: KclValue | null = null
) {
@ -465,31 +397,36 @@ export function sketchFromKclValue(
export const executor = async (
node: Node<Program>,
programMemory: ProgramMemory | Error = ProgramMemory.empty(),
idGenerator: IdGenerator = defaultIdGenerator(),
engineCommandManager: EngineCommandManager,
programMemoryOverride: ProgramMemory | Error | null = null
isMock: boolean = false
): Promise<ExecState> => {
if (programMemoryOverride !== null && err(programMemoryOverride))
return Promise.reject(programMemoryOverride)
if (err(programMemory)) return Promise.reject(programMemory)
// eslint-disable-next-line @typescript-eslint/no-floating-promises
engineCommandManager.startNewSession()
const _programMemory = await _executor(
node,
programMemory,
idGenerator,
engineCommandManager,
programMemoryOverride
isMock
)
await engineCommandManager.waitForAllCommands()
engineCommandManager.endSession()
return _programMemory
}
export const _executor = async (
node: Node<Program>,
programMemory: ProgramMemory | Error = ProgramMemory.empty(),
idGenerator: IdGenerator = defaultIdGenerator(),
engineCommandManager: EngineCommandManager,
programMemoryOverride: ProgramMemory | Error | null = null
isMock: boolean
): Promise<ExecState> => {
if (programMemoryOverride !== null && err(programMemoryOverride))
return Promise.reject(programMemoryOverride)
if (err(programMemory)) return Promise.reject(programMemory)
try {
let baseUnit = 'mm'
@ -502,10 +439,13 @@ export const _executor = async (
}
const execState: RawExecState = await execute_wasm(
JSON.stringify(node),
JSON.stringify(programMemoryOverride?.toRaw() || null),
JSON.stringify(programMemory.toRaw()),
JSON.stringify(idGenerator),
baseUnit,
engineCommandManager,
fileSystemManager
fileSystemManager,
undefined,
isMock
)
return execStateFromRaw(execState)
} catch (e: any) {
@ -514,7 +454,7 @@ export const _executor = async (
const kclError = new KCLError(
parsed.kind,
parsed.msg,
sourceRangeFromRust(parsed.sourceRanges[0])
rangeTypeFix(parsed.sourceRanges)
)
return Promise.reject(kclError)
@ -587,7 +527,7 @@ export const modifyAstForSketch = async (
const kclError = new KCLError(
parsed.kind,
parsed.msg,
sourceRangeFromRust(parsed.sourceRanges[0])
rangeTypeFix(parsed.sourceRanges)
)
console.log(kclError)
@ -655,7 +595,7 @@ export function programMemoryInit(): ProgramMemory | Error {
return new KCLError(
parsed.kind,
parsed.msg,
sourceRangeFromRust(parsed.sourceRanges[0])
rangeTypeFix(parsed.sourceRanges)
)
}
}

View File

@ -1,12 +0,0 @@
import { NODE_ENV } from 'env'
import { isDesktop } from './isDesktop'
import { isTestEnv } from './isTestEnv'
/** Version number of the app */
export const APP_VERSION =
isTestEnv && NODE_ENV === 'development'
? '11.22.33'
: isDesktop()
? // @ts-ignore
window.electron.packageJson.version
: 'main'

View File

@ -31,9 +31,6 @@ export type ModelingCommandSchema = {
// result: (typeof EXTRUSION_RESULTS)[number]
distance: KclCommandValue
}
Loft: {
selection: Selections
}
Revolve: {
selection: Selections
angle: KclCommandValue
@ -263,20 +260,6 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
},
},
},
Loft: {
description: 'Create a 3D body by blending between two or more sketches',
icon: 'loft',
needsReview: true,
args: {
selection: {
inputType: 'selection',
selectionTypes: ['solid2D'],
multiple: true,
required: true,
skip: false,
},
},
},
// TODO: Update this configuration, copied from extrude for MVP of revolve, specifically the args.selection
Revolve: {
description: 'Create a 3D body by rotating a sketch region about an axis.',

View File

@ -52,7 +52,6 @@ export const ONBOARDING_PROJECT_NAME = 'Tutorial Project $nn'
export const KCL_DEFAULT_CONSTANT_PREFIXES = {
SKETCH: 'sketch',
EXTRUDE: 'extrude',
LOFT: 'loft',
SEGMENT: 'seg',
REVOLVE: 'revolve',
PLANE: 'plane',

View File

@ -2,7 +2,7 @@ import { CommandLog, EngineCommandManager } from 'lang/std/engineConnection'
import { WebrtcStats } from 'wasm-lib/kcl/bindings/WebrtcStats'
import { OsInfo } from 'wasm-lib/kcl/bindings/OsInfo'
import { isDesktop } from 'lib/isDesktop'
import { APP_VERSION } from 'lib/appVersion'
import { APP_VERSION } from 'routes/Settings'
import { UAParser } from 'ua-parser-js'
import screenshot from 'lib/screenshot'
import { VITE_KC_API_BASE_URL } from 'env'

View File

@ -116,8 +116,8 @@ export async function createAndOpenNewTutorialProject({
) => void
navigate: (path: string) => void
}) {
// Clear the scene.
engineCommandManager.clearScene()
// Clear the scene and end the session.
engineCommandManager.endSession()
// Create a new project with the onboarding project name
const configuration = await readAppSettingsFile()

View File

@ -30,27 +30,27 @@ const bracketLeg1Sketch = startSketchOn('XY')
|> line([-shelfMountL + filletRadius, 0], %)
|> close(%)
|> hole(circle({
center = [1, 1],
radius = mountingHoleDiameter / 2
center: [1, 1],
radius: mountingHoleDiameter / 2
}, %), %)
|> hole(circle({
center = [shelfMountL - 1.5, width - 1],
radius = mountingHoleDiameter / 2
center: [shelfMountL - 1.5, width - 1],
radius: mountingHoleDiameter / 2
}, %), %)
|> hole(circle({
center = [1, width - 1],
radius = mountingHoleDiameter / 2
center: [1, width - 1],
radius: mountingHoleDiameter / 2
}, %), %)
|> hole(circle({
center = [shelfMountL - 1.5, 1],
radius = mountingHoleDiameter / 2
center: [shelfMountL - 1.5, 1],
radius: mountingHoleDiameter / 2
}, %), %)
// Extrude the leg 2 bracket sketch
const bracketLeg1Extrude = extrude(thickness, bracketLeg1Sketch)
|> fillet({
radius = extFilletRadius,
tags = [
radius: extFilletRadius,
tags: [
getNextAdjacentEdge(fillet1),
getNextAdjacentEdge(fillet2)
]
@ -61,15 +61,15 @@ const filletSketch = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> line([0, thickness], %)
|> arc({
angleEnd = 180,
angleStart = 90,
radius = filletRadius + thickness
angleEnd: 180,
angleStart: 90,
radius: filletRadius + thickness
}, %)
|> line([thickness, 0], %)
|> arc({
angleEnd = 90,
angleStart = 180,
radius = filletRadius
angleEnd: 90,
angleStart: 180,
radius: filletRadius
}, %)
// Sketch the bend
@ -77,11 +77,11 @@ const filletExtrude = extrude(-width, filletSketch)
// Create a custom plane for the leg that sits on the wall
const customPlane = {
plane = {
origin = { x = -filletRadius, y = 0, z = 0 },
xAxis = { x = 0, y = 1, z = 0 },
yAxis = { x = 0, y = 0, z = 1 },
zAxis = { x = 1, y = 0, z = 0 }
plane: {
origin: { x: -filletRadius, y: 0, z: 0 },
xAxis: { x: 0, y: 1, z: 0 },
yAxis: { x: 0, y: 0, z: 1 },
zAxis: { x: 1, y: 0, z: 0 }
}
}
@ -93,19 +93,19 @@ const bracketLeg2Sketch = startSketchOn(customPlane)
|> line([-width, 0], %, $fillet4)
|> close(%)
|> hole(circle({
center = [1, -1.5],
radius = mountingHoleDiameter / 2
center: [1, -1.5],
radius: mountingHoleDiameter / 2
}, %), %)
|> hole(circle({
center = [5, -1.5],
radius = mountingHoleDiameter / 2
center: [5, -1.5],
radius: mountingHoleDiameter / 2
}, %), %)
// Extrude the second leg
const bracketLeg2Extrude = extrude(-thickness, bracketLeg2Sketch)
|> fillet({
radius = extFilletRadius,
tags = [
radius: extFilletRadius,
tags: [
getNextAdjacentEdge(fillet3),
getNextAdjacentEdge(fillet4)
]

View File

@ -1,4 +0,0 @@
import { IS_PLAYWRIGHT_KEY } from '../../e2e/playwright/storageStates'
export const isTestEnv =
globalThis.window?.localStorage.getItem(IS_PLAYWRIGHT_KEY) === 'true'

View File

@ -5,12 +5,7 @@ import {
kclManager,
sceneEntitiesManager,
} from 'lib/singletons'
import {
CallExpression,
SourceRange,
Expr,
defaultSourceRange,
} from 'lang/wasm'
import { CallExpression, SourceRange, Expr } from 'lang/wasm'
import { ModelingMachineEvent } from 'machines/modelingMachine'
import { isNonNullable, uuidv4 } from 'lib/utils'
import { EditorSelection, SelectionRange } from '@codemirror/state'
@ -271,7 +266,7 @@ export function getEventForSegmentSelection(
selectionType: 'singleCodeCursor',
selection: {
codeRef: {
range: [node.node.start, node.node.end, true],
range: [node.node.start, node.node.end],
pathToNode: group.userData.pathToNode,
},
},
@ -314,11 +309,10 @@ export function handleSelectionBatch({
selectionToEngine.push({
type: 'default',
id: artifact?.id,
range:
getCodeRefsByArtifactId(
artifact.id,
engineCommandManager.artifactGraph
)?.[0].range || defaultSourceRange(),
range: getCodeRefsByArtifactId(
artifact.id,
engineCommandManager.artifactGraph
)?.[0].range || [0, 0],
})
})
const engineEvents: Models['WebSocketRequest_type'][] =
@ -382,10 +376,10 @@ export function processCodeMirrorRanges({
if (!isChange) return null
const codeBasedSelections: Selections['graphSelections'] =
codeMirrorRanges.map(({ from, to }) => {
const pathToNode = getNodePathFromSourceRange(ast, [from, to, true])
const pathToNode = getNodePathFromSourceRange(ast, [from, to])
return {
codeRef: {
range: [from, to, true],
range: [from, to],
pathToNode,
},
}
@ -448,7 +442,7 @@ function updateSceneObjectColors(codeBasedSelections: Selection[]) {
if (err(nodeMeta)) return
const node = nodeMeta.node
const groupHasCursor = codeBasedSelections.some((selection) => {
return isOverlap(selection?.codeRef?.range, [node.start, node.end, true])
return isOverlap(selection?.codeRef?.range, [node.start, node.end])
})
const color = groupHasCursor
@ -535,10 +529,6 @@ function nodeHasExtrude(node: CommonASTNode) {
doesPipeHaveCallExp({
calleeName: 'revolve',
...node,
}) ||
doesPipeHaveCallExp({
calleeName: 'loft',
...node,
})
)
}
@ -569,22 +559,6 @@ export function canSweepSelection(selection: Selections) {
)
}
export function canLoftSelection(selection: Selections) {
const commonNodes = selection.graphSelections.map((_, i) =>
buildCommonNodeFromSelection(selection, i)
)
return (
!!isCursorInSketchCommandRange(
engineCommandManager.artifactGraph,
selection
) &&
commonNodes.length > 1 &&
commonNodes.every((n) => !hasSketchPipeBeenExtruded(n.selection, n.ast)) &&
commonNodes.every((n) => nodeHasClose(n) || nodeHasCircle(n)) &&
commonNodes.every((n) => !nodeHasExtrude(n))
)
}
// This accounts for non-geometry selections under "other"
export type ResolvedSelectionType = Artifact['type'] | 'other'
export type SelectionCountsByType = Map<ResolvedSelectionType, number>
@ -931,7 +905,7 @@ export function updateSelections(
return {
artifact: artifact,
codeRef: {
range: [node.start, node.end, true],
range: [node.start, node.end],
pathToNode: pathToNode,
},
}
@ -945,7 +919,7 @@ export function updateSelections(
if (err(node)) return node
pathToNodeBasedSelections.push({
codeRef: {
range: [node.node.start, node.node.end, true],
range: [node.node.start, node.node.end],
pathToNode: pathToNode,
},
})

View File

@ -4,6 +4,7 @@ import {
_executor,
SourceRange,
ExecState,
defaultIdGenerator,
} from '../lang/wasm'
import {
EngineCommandManager,
@ -15,6 +16,7 @@ import { v4 as uuidv4 } from 'uuid'
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
import { err, reportRejection } from 'lib/trap'
import { toSync } from './utils'
import { IdGenerator } from 'wasm-lib/kcl/bindings/IdGenerator'
import { Node } from 'wasm-lib/kcl/bindings/Node'
type WebSocketResponse = Models['WebSocketResponse_type']
@ -83,10 +85,12 @@ class MockEngineCommandManager {
}
export async function enginelessExecutor(
ast: Node<Program>,
pmo: ProgramMemory | Error = ProgramMemory.empty()
ast: Node<Program> | Error,
pm: ProgramMemory | Error = ProgramMemory.empty(),
idGenerator: IdGenerator = defaultIdGenerator()
): Promise<ExecState> {
if (pmo !== null && err(pmo)) return Promise.reject(pmo)
if (err(ast)) return Promise.reject(ast)
if (err(pm)) return Promise.reject(pm)
const mockEngineCommandManager = new MockEngineCommandManager({
setIsStreamReady: () => {},
@ -94,14 +98,21 @@ export async function enginelessExecutor(
}) as any as EngineCommandManager
// eslint-disable-next-line @typescript-eslint/no-floating-promises
mockEngineCommandManager.startNewSession()
const execState = await _executor(ast, mockEngineCommandManager, pmo)
const execState = await _executor(
ast,
pm,
idGenerator,
mockEngineCommandManager,
true
)
await mockEngineCommandManager.waitForAllCommands()
return execState
}
export async function executor(
ast: Node<Program>,
pmo: ProgramMemory = ProgramMemory.empty()
pm: ProgramMemory = ProgramMemory.empty(),
idGenerator: IdGenerator = defaultIdGenerator()
): Promise<ExecState> {
const engineCommandManager = new EngineCommandManager()
engineCommandManager.start({
@ -123,7 +134,13 @@ export async function executor(
toSync(async () => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
engineCommandManager.startNewSession()
const execState = await _executor(ast, engineCommandManager, pmo)
const execState = await _executor(
ast,
pm,
idGenerator,
engineCommandManager,
false
)
await engineCommandManager.waitForAllCommands()
resolve(execState)
}, reportRejection)

View File

@ -139,14 +139,9 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
},
{
id: 'loft',
onClick: ({ commandBarSend }) =>
commandBarSend({
type: 'Find and select command',
data: { name: 'Loft', groupId: 'modeling' },
}),
disabled: (state) => !state.can({ type: 'Loft' }),
onClick: () => console.error('Loft not yet implemented'),
icon: 'loft',
status: 'available',
status: 'kcl-only',
title: 'Loft',
hotkey: 'L',
description:

View File

@ -3,7 +3,7 @@ import { kclManager, engineCommandManager } from 'lib/singletons'
import { useKclContext } from 'lang/KclProvider'
import { findUniqueName } from 'lang/modifyAst'
import { PrevVariable, findAllPreviousVariables } from 'lang/queryAst'
import { ProgramMemory, Expr, parse, resultIsOk } from 'lang/wasm'
import { ProgramMemory, Expr, parse } from 'lang/wasm'
import { useEffect, useRef, useState } from 'react'
import { executeAst } from 'lang/langHelpers'
import { err, trap } from 'lib/trap'
@ -87,9 +87,9 @@ export function useCalculateKclExpression({
useEffect(() => {
const execAstAndSetResult = async () => {
const _code = `const __result__ = ${value}`
const pResult = parse(_code)
if (err(pResult) || !resultIsOk(pResult)) return
const ast = pResult.program
const ast = parse(_code)
if (err(ast)) return
if (trap(ast, { suppress: true })) return
const _programMem: ProgramMemory = ProgramMemory.empty()
for (const { key, value } of availableVarInfo.variables) {
@ -103,8 +103,9 @@ export function useCalculateKclExpression({
const { execState } = await executeAst({
ast,
engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
useFakeExecutor: true,
programMemoryOverride: kclManager.programMemory.clone(),
idGenerator: kclManager.execState.idGenerator,
})
const resultDeclaration = ast.body.find(
(a) =>

View File

@ -9,13 +9,13 @@ import {
import { SourceRange } from '../lang/wasm'
describe('testing isOverlapping', () => {
testBothOrders([0, 3, true], [3, 10, true])
testBothOrders([0, 5, true], [3, 4, true])
testBothOrders([0, 5, true], [5, 10, true])
testBothOrders([0, 5, true], [6, 10, true], false)
testBothOrders([0, 5, true], [-1, 1, true])
testBothOrders([0, 5, true], [-1, 0, true])
testBothOrders([0, 5, true], [-2, -1, true], false)
testBothOrders([0, 3], [3, 10])
testBothOrders([0, 5], [3, 4])
testBothOrders([0, 5], [5, 10])
testBothOrders([0, 5], [6, 10], false)
testBothOrders([0, 5], [-1, 1])
testBothOrders([0, 5], [-1, 0])
testBothOrders([0, 5], [-2, -1], false)
})
function testBothOrders(a: SourceRange, b: SourceRange, result = true) {

View File

@ -1,22 +0,0 @@
import { AnyStateMachine, StateFrom } from 'xstate'
/**
* Convert an XState state value to a pretty string,
* with nested states separated by slashes
*/
export function xStateValueToString(
stateValue: StateFrom<AnyStateMachine>['value']
) {
const sep = ' / '
let output = ''
let remainingValues = stateValue
let isFirstStep = true
while (remainingValues instanceof Object) {
const key: keyof typeof remainingValues = Object.keys(remainingValues)[0]
output += (isFirstStep ? '' : sep) + key
remainingValues = remainingValues[key]
isFirstStep = false
}
if (typeof remainingValues === 'string' && remainingValues.trim().length)
return output + sep + remainingValues.trim()
}

View File

@ -1,11 +1,9 @@
import {
PathToNode,
ProgramMemory,
VariableDeclaration,
VariableDeclarator,
parse,
recast,
resultIsOk,
} from 'lang/wasm'
import {
Axis,
@ -46,14 +44,13 @@ import {
addOffsetPlane,
deleteFromSelection,
extrudeSketch,
loftSketches,
revolveSketch,
} from 'lang/modifyAst'
import {
applyEdgeTreatmentToSelection,
EdgeTreatmentType,
FilletParameters,
} from 'lang/modifyAst/addEdgeTreatment'
} from 'lang/modifyAst/addFillet'
import { getNodeFromPath } from '../lang/queryAst'
import {
applyConstraintEqualAngle,
@ -259,7 +256,6 @@ export type ModelingMachineEvent =
| { type: 'Export'; data: ModelingCommandSchema['Export'] }
| { type: 'Make'; data: ModelingCommandSchema['Make'] }
| { type: 'Extrude'; data?: ModelingCommandSchema['Extrude'] }
| { type: 'Loft'; data?: ModelingCommandSchema['Loft'] }
| { type: 'Revolve'; data?: ModelingCommandSchema['Revolve'] }
| { type: 'Fillet'; data?: ModelingCommandSchema['Fillet'] }
| { type: 'Offset plane'; data: ModelingCommandSchema['Offset plane'] }
@ -391,7 +387,6 @@ export const modelingMachine = setup({
guards: {
'Selection is on face': () => false,
'has valid sweep selection': () => false,
'has valid loft selection': () => false,
'has valid edge treatment selection': () => false,
'Has exportable geometry': () => false,
'has valid selection for deletion': () => false,
@ -544,11 +539,8 @@ export const modelingMachine = setup({
if (event.type !== 'Convert to variable') return false
if (!event.data) return false
const ast = parse(recast(kclManager.ast))
if (err(ast) || !ast.program || ast.errors.length > 0) return false
const isSafeRetVal = isNodeSafeToReplacePath(
ast.program,
event.data.pathToNode
)
if (err(ast)) return false
const isSafeRetVal = isNodeSafeToReplacePath(ast, event.data.pathToNode)
if (err(isSafeRetVal)) return false
return isSafeRetVal.isSafe
},
@ -731,9 +723,9 @@ export const modelingMachine = setup({
const testExecute = await executeAst({
ast: modifiedAst,
idGenerator: kclManager.execState.idGenerator,
useFakeExecutor: true,
engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride: ProgramMemory.empty(),
})
if (testExecute.errors.length) {
toast.error('Unable to delete part')
@ -1337,12 +1329,9 @@ export const modelingMachine = setup({
return
}
const recastAst = parse(recast(modifiedAst))
if (err(recastAst) || !resultIsOk(recastAst)) return
const updatedAst = await sceneEntitiesManager.updateAstAndRejigSketch(
sketchDetails?.sketchPathToNode || [],
recastAst.program,
parse(recast(modifiedAst)),
sketchDetails.zAxis,
sketchDetails.yAxis,
sketchDetails.origin
@ -1540,50 +1529,6 @@ export const modelingMachine = setup({
updateAstResult.newAst
)
if (updateAstResult?.selections) {
editorManager.selectRange(updateAstResult?.selections)
}
}
),
loftAstMod: fromPromise(
async ({
input,
}: {
input: ModelingCommandSchema['Loft'] | undefined
}) => {
if (!input) return new Error('No input provided')
// Extract inputs
const ast = kclManager.ast
const { selection } = input
const declarators = selection.graphSelections.flatMap((s) => {
const path = getNodePathFromSourceRange(ast, s?.codeRef.range)
const nodeFromPath = getNodeFromPath<VariableDeclarator>(
ast,
path,
'VariableDeclarator'
)
return err(nodeFromPath) ? [] : nodeFromPath.node
})
// TODO: add better validation on selection
if (!(declarators && declarators.length > 1)) {
trap('Not enough sketches selected')
}
// Perform the loft
const loftSketchesRes = loftSketches(ast, declarators)
const updateAstResult = await kclManager.updateAst(
loftSketchesRes.modifiedAst,
true,
{
focusPath: [loftSketchesRes.pathToNode],
}
)
await codeManager.updateEditorWithAstAndWriteToFile(
updateAstResult.newAst
)
if (updateAstResult?.selections) {
editorManager.selectRange(updateAstResult?.selections)
}
@ -1625,11 +1570,6 @@ export const modelingMachine = setup({
reenter: false,
},
Loft: {
target: 'Applying loft',
reenter: true,
},
Fillet: {
target: 'idle',
guard: 'has valid edge treatment selection',
@ -2378,19 +2318,6 @@ export const modelingMachine = setup({
onError: ['idle'],
},
},
'Applying loft': {
invoke: {
src: 'loftAstMod',
id: 'loftAstMod',
input: ({ event }) => {
if (event.type !== 'Loft') return undefined
return event.data
},
onDone: ['idle'],
onError: ['idle'],
},
},
},
initial: 'idle',

View File

@ -13,6 +13,18 @@ import { AllSettingsFields } from 'components/Settings/AllSettingsFields'
import { AllKeybindingsFields } from 'components/Settings/AllKeybindingsFields'
import { KeybindingsSectionsList } from 'components/Settings/KeybindingsSectionsList'
import { isDesktop } from 'lib/isDesktop'
import { IS_PLAYWRIGHT_KEY } from '../../e2e/playwright/storageStates'
import { NODE_ENV } from 'env'
const isTestEnv = window?.localStorage.getItem(IS_PLAYWRIGHT_KEY) === 'true'
export const APP_VERSION =
isTestEnv && NODE_ENV === 'development'
? '11.22.33'
: isDesktop()
? // @ts-ignore
window.electron.packageJson.version
: 'main'
export const PACKAGE_NAME = isDesktop()
? window.electron.packageJson.name

View File

@ -5,11 +5,11 @@ import { Themes, getSystemTheme } from '../lib/theme'
import { PATHS } from 'lib/paths'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { APP_NAME } from 'lib/constants'
import { APP_VERSION } from 'lib/appVersion'
import { CSSProperties, useCallback, useState } from 'react'
import { Logo } from 'components/Logo'
import { CustomIcon } from 'components/CustomIcon'
import { Link } from 'react-router-dom'
import { APP_VERSION } from './Settings'
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
import { toSync } from 'lib/utils'
import { reportRejection } from 'lib/trap'

View File

@ -316,7 +316,7 @@ dependencies = [
"bitvec",
"chrono",
"hex",
"indexmap 2.7.0",
"indexmap 2.6.0",
"js-sys",
"once_cell",
"rand 0.8.5",
@ -712,6 +712,29 @@ version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2"
[[package]]
name = "databake"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a04fbfbecca8f0679c8c06fef907594adcc3e2052e11163a6d30535a1a5604d"
dependencies = [
"databake-derive",
"proc-macro2",
"quote",
]
[[package]]
name = "databake-derive"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4078275de501a61ceb9e759d37bdd3d7210e654dbc167ac1a3678ef4435ed57b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"synstructure",
]
[[package]]
name = "deranged"
version = "0.3.11"
@ -723,7 +746,7 @@ dependencies = [
[[package]]
name = "derive-docs"
version = "0.1.32"
version = "0.1.31"
dependencies = [
"Inflector",
"anyhow",
@ -1113,7 +1136,7 @@ dependencies = [
"futures-core",
"futures-sink",
"http 1.1.0",
"indexmap 2.7.0",
"indexmap 2.6.0",
"slab",
"tokio",
"tokio-util",
@ -1159,9 +1182,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "hashbrown"
version = "0.15.2"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
[[package]]
name = "heck"
@ -1561,12 +1584,12 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.7.0"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
dependencies = [
"equivalent",
"hashbrown 0.15.2",
"hashbrown 0.15.0",
"serde",
]
@ -1683,7 +1706,7 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.2.28"
version = "0.2.27"
dependencies = [
"anyhow",
"approx 0.5.1",
@ -1696,6 +1719,7 @@ dependencies = [
"convert_case",
"criterion",
"dashmap 6.1.0",
"databake",
"derive-docs",
"dhat",
"expectorate",
@ -1708,7 +1732,7 @@ dependencies = [
"http 1.1.0",
"iai",
"image",
"indexmap 2.7.0",
"indexmap 2.6.0",
"insta",
"itertools 0.13.0",
"js-sys",
@ -1749,7 +1773,7 @@ dependencies = [
[[package]]
name = "kcl-test-server"
version = "0.1.18"
version = "0.1.17"
dependencies = [
"anyhow",
"hyper 0.14.30",
@ -1766,7 +1790,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"indexmap 2.7.0",
"indexmap 2.6.0",
"kcl-lib",
"kittycad",
"kittycad-modeling-cmds",
@ -1776,9 +1800,9 @@ dependencies = [
[[package]]
name = "kittycad"
version = "0.3.28"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "933cb5f77624386c87d296e3fd493daf50156d1cbfa03b9f333a6d4da2896369"
checksum = "f6359cc0a1bbccbcf78775eea17a033cf2aa89d3fe6a9784f8ce94e5f882c185"
dependencies = [
"anyhow",
"async-trait",
@ -1807,7 +1831,7 @@ dependencies = [
"serde_bytes",
"serde_json",
"serde_urlencoded",
"thiserror 2.0.0",
"thiserror 1.0.68",
"tokio",
"tracing",
"url",
@ -2897,9 +2921,9 @@ dependencies = [
[[package]]
name = "reqwest-conditional-middleware"
version = "0.4.0"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f67ad7fdf5c0a015763fcd164bee294b13fb7b6f89f1b55961d40f00c3e32d6b"
checksum = "b1663d9d4fbb6e3900f91455d6d7833301c91ae3c7fc6e116fd7acd40e478a93"
dependencies = [
"async-trait",
"http 1.1.0",
@ -2909,9 +2933,9 @@ dependencies = [
[[package]]
name = "reqwest-middleware"
version = "0.4.0"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1ccd3b55e711f91a9885a2fa6fbbb2e39db1776420b062efc058c6410f7e5e3"
checksum = "562ceb5a604d3f7c885a792d42c199fd8af239d0a51b2fa6a78aafa092452b04"
dependencies = [
"anyhow",
"async-trait",
@ -2924,9 +2948,9 @@ dependencies = [
[[package]]
name = "reqwest-retry"
version = "0.7.0"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29c73e4195a6bfbcb174b790d9b3407ab90646976c55de58a6515da25d851178"
checksum = "a83df1aaec00176d0fabb65dea13f832d2a446ca99107afc17c5d2d4981221d0"
dependencies = [
"anyhow",
"async-trait",
@ -2938,7 +2962,6 @@ dependencies = [
"reqwest",
"reqwest-middleware",
"retry-policies",
"thiserror 1.0.68",
"tokio",
"tracing",
"wasm-timer",
@ -2946,9 +2969,9 @@ dependencies = [
[[package]]
name = "reqwest-tracing"
version = "0.5.4"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff82cf5730a1311fb9413b0bc2b8e743e0157cd73f010ab4ec374a923873b6a2"
checksum = "bfdd9bfa64c72233d8dd99ab7883efcdefe9e16d46488ecb9228b71a2e2ceb45"
dependencies = [
"anyhow",
"async-trait",
@ -3134,7 +3157,7 @@ dependencies = [
"chrono",
"dyn-clone",
"indexmap 1.9.3",
"indexmap 2.7.0",
"indexmap 2.6.0",
"schemars_derive",
"serde",
"serde_json",
@ -3235,7 +3258,7 @@ version = "1.0.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
dependencies = [
"indexmap 2.7.0",
"indexmap 2.6.0",
"itoa",
"memchr",
"ryu",
@ -3823,7 +3846,7 @@ version = "0.22.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
dependencies = [
"indexmap 2.7.0",
"indexmap 2.6.0",
"serde",
"serde_spanned",
"toml_datetime",
@ -3989,7 +4012,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a2f31991cee3dce1ca4f929a8a04fdd11fd8801aac0f2030b0fa8a0a3fef6b9"
dependencies = [
"chrono",
"indexmap 2.7.0",
"indexmap 2.6.0",
"lazy_static",
"serde_json",
"thiserror 1.0.68",
@ -4308,7 +4331,6 @@ dependencies = [
"kcl-lib",
"kittycad",
"kittycad-modeling-cmds",
"lazy_static",
"pretty_assertions",
"reqwest",
"serde_json",
@ -4747,7 +4769,7 @@ dependencies = [
"crc32fast",
"crossbeam-utils",
"displaydoc",
"indexmap 2.7.0",
"indexmap 2.6.0",
"memchr",
"thiserror 1.0.68",
]

View File

@ -15,7 +15,6 @@ data-encoding = "2.6.0"
gloo-utils = "0.2.0"
kcl-lib = { path = "kcl" }
kittycad.workspace = true
lazy_static = "1.5.0"
serde_json = "1.0.128"
tokio = { version = "1.41.1", features = ["sync"] }
toml = "0.8.19"
@ -75,11 +74,8 @@ members = [
[workspace.dependencies]
http = "1"
kittycad = { version = "0.3.28", default-features = false, features = ["js", "requests"] }
kittycad-modeling-cmds = { version = "0.2.77", features = ["websocket"] }
[workspace.lints.clippy]
iter_over_hash_type = "warn"
kittycad = { version = "0.3.25", default-features = false, features = ["js", "requests"] }
kittycad-modeling-cmds = { version = "0.2.76", features = ["websocket"] }
[[test]]
name = "executor"

View File

@ -1,7 +1,7 @@
[package]
name = "derive-docs"
description = "A tool for generating documentation from Rust derive macros"
version = "0.1.32"
version = "0.1.31"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"
@ -27,6 +27,3 @@ anyhow = "1.0.93"
expectorate = "1.1.0"
pretty_assertions = "1.4.1"
rustfmt-wrapper = "0.2.1"
[lints]
workspace = true

View File

@ -23,30 +23,17 @@ use unbox::unbox;
struct StdlibMetadata {
/// The name of the function in the API.
name: String,
/// Tags for the function.
#[serde(default)]
tags: Vec<String>,
/// Whether the function is unpublished.
/// Then docs will not be generated.
#[serde(default)]
unpublished: bool,
/// Whether the function is deprecated.
/// Then specific docs detailing that this is deprecated will be generated.
#[serde(default)]
deprecated: bool,
/// If true, expects keyword arguments.
/// If false, expects positional arguments.
#[serde(default)]
keywords: bool,
/// If true, the first argument is unlabeled.
/// If false, all arguments require labels.
#[serde(default)]
unlabeled_first: bool,
}
#[proc_macro_attribute]
@ -182,9 +169,9 @@ fn do_stdlib_inner(
quote! {
let code_blocks = vec![#(#cb),*];
code_blocks.iter().map(|cb| {
let program = crate::Program::parse_no_errs(cb).unwrap();
let program = crate::Program::parse(cb).unwrap();
let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false;
program.ast.recast(&options, 0)
}).collect::<Vec<String>>()
@ -238,12 +225,6 @@ fn do_stdlib_inner(
quote! { false }
};
let uses_keyword_arguments = if metadata.keywords {
quote! { true }
} else {
quote! { false }
};
let docs_crate = get_crate(None);
// When the user attaches this proc macro to a function with the wrong type
@ -252,7 +233,7 @@ fn do_stdlib_inner(
// of the various parameters. We do this by calling dummy functions that
// require a type that satisfies SharedExtractor or ExclusiveExtractor.
let mut arg_types = Vec::new();
for (i, arg) in ast.sig.inputs.iter().enumerate() {
for arg in ast.sig.inputs.iter() {
// Get the name of the argument.
let arg_name = match arg {
syn::FnArg::Receiver(pat) => {
@ -282,7 +263,7 @@ fn do_stdlib_inner(
let ty_string = rust_type_to_openapi_type(&ty_string);
let required = !ty_ident.to_string().starts_with("Option <");
let label_required = !(i == 0 && metadata.unlabeled_first);
if ty_string != "ExecState" && ty_string != "Args" {
let schema = quote! {
generator.root_schema_for::<#ty_ident>()
@ -293,7 +274,6 @@ fn do_stdlib_inner(
type_: #ty_string.to_string(),
schema: #schema,
required: #required,
label_required: #label_required,
}
});
}
@ -354,7 +334,6 @@ fn do_stdlib_inner(
type_: #ret_ty_string.to_string(),
schema,
required: true,
label_required: true,
})
}
} else {
@ -421,10 +400,6 @@ fn do_stdlib_inner(
vec![#(#tags),*]
}
fn keyword_arguments(&self) -> bool {
#uses_keyword_arguments
}
fn args(&self, inline_subschemas: bool) -> Vec<#docs_crate::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3();
// We set this to false so we can recurse them later.
@ -769,7 +744,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
quote! {
#[tokio::test(flavor = "multi_thread")]
async fn #test_name_mock() {
let program = crate::Program::parse_no_errs(#code_block).unwrap();
let program = crate::Program::parse(#code_block).unwrap();
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await.unwrap())),
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
@ -778,7 +753,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
context_type: crate::executor::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default()).await.unwrap();
ctx.run(&program, &mut crate::ExecState::default()).await.unwrap();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]

View File

@ -2,7 +2,7 @@
mod test_examples_someFn {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_someFn0() {
let program = crate::Program::parse_no_errs("someFn()").unwrap();
let program = crate::Program::parse("someFn()").unwrap();
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
@ -14,7 +14,7 @@ mod test_examples_someFn {
settings: Default::default(),
context_type: crate::executor::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
}
@ -74,10 +74,6 @@ impl crate::docs::StdLibFn for SomeFn {
vec![]
}
fn keyword_arguments(&self) -> bool {
false
}
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = inline_subschemas;
@ -87,7 +83,6 @@ impl crate::docs::StdLibFn for SomeFn {
type_: "Foo".to_string(),
schema: generator.root_schema_for::<Foo>(),
required: true,
label_required: true,
}]
}
@ -101,7 +96,6 @@ impl crate::docs::StdLibFn for SomeFn {
type_: "i32".to_string(),
schema,
required: true,
label_required: true,
})
}
@ -118,8 +112,8 @@ impl crate::docs::StdLibFn for SomeFn {
code_blocks
.iter()
.map(|cb| {
let program = crate::Program::parse_no_errs(cb).unwrap();
let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false;
program.ast.recast(&options, 0)
})

View File

@ -2,7 +2,7 @@
mod test_examples_someFn {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_someFn0() {
let program = crate::Program::parse_no_errs("someFn()").unwrap();
let program = crate::Program::parse("someFn()").unwrap();
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
@ -14,7 +14,7 @@ mod test_examples_someFn {
settings: Default::default(),
context_type: crate::executor::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
}
@ -74,10 +74,6 @@ impl crate::docs::StdLibFn for SomeFn {
vec![]
}
fn keyword_arguments(&self) -> bool {
false
}
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = inline_subschemas;
@ -87,7 +83,6 @@ impl crate::docs::StdLibFn for SomeFn {
type_: "string".to_string(),
schema: generator.root_schema_for::<str>(),
required: true,
label_required: true,
}]
}
@ -101,7 +96,6 @@ impl crate::docs::StdLibFn for SomeFn {
type_: "i32".to_string(),
schema,
required: true,
label_required: true,
})
}
@ -118,8 +112,8 @@ impl crate::docs::StdLibFn for SomeFn {
code_blocks
.iter()
.map(|cb| {
let program = crate::Program::parse_no_errs(cb).unwrap();
let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false;
program.ast.recast(&options, 0)
})

View File

@ -3,7 +3,7 @@ mod test_examples_show {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_show0() {
let program =
crate::Program::parse_no_errs("This is another code block.\nyes sirrr.\nshow").unwrap();
crate::Program::parse("This is another code block.\nyes sirrr.\nshow").unwrap();
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
@ -15,7 +15,7 @@ mod test_examples_show {
settings: Default::default(),
context_type: crate::executor::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
}
@ -36,8 +36,7 @@ mod test_examples_show {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_show1() {
let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nshow").unwrap();
let program = crate::Program::parse("This is code.\nIt does other shit.\nshow").unwrap();
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
@ -49,7 +48,7 @@ mod test_examples_show {
settings: Default::default(),
context_type: crate::executor::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
}
@ -109,10 +108,6 @@ impl crate::docs::StdLibFn for Show {
vec![]
}
fn keyword_arguments(&self) -> bool {
false
}
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = inline_subschemas;
@ -122,7 +117,6 @@ impl crate::docs::StdLibFn for Show {
type_: "[number]".to_string(),
schema: generator.root_schema_for::<[f64; 2usize]>(),
required: true,
label_required: true,
}]
}
@ -136,7 +130,6 @@ impl crate::docs::StdLibFn for Show {
type_: "number".to_string(),
schema,
required: true,
label_required: true,
})
}
@ -156,8 +149,8 @@ impl crate::docs::StdLibFn for Show {
code_blocks
.iter()
.map(|cb| {
let program = crate::Program::parse_no_errs(cb).unwrap();
let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false;
program.ast.recast(&options, 0)
})

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