Compare commits
6 Commits
derive-doc
...
cut-releas
Author | SHA1 | Date | |
---|---|---|---|
04a77efae3 | |||
379cd1e067 | |||
8c3d438f6d | |||
ac15049e2c | |||
466da6be55 | |||
38d5be001b |
31
.github/workflows/build-test-publish-apps.yml
vendored
@ -5,6 +5,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- cut-release-v0.25.1-updater-test-build-1
|
||||
release:
|
||||
types: [published]
|
||||
schedule:
|
||||
@ -13,8 +14,8 @@ on:
|
||||
# Will checkout the last commit from the default branch (main as of 2023-10-04)
|
||||
|
||||
env:
|
||||
CUT_RELEASE_PR: ${{ github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }}
|
||||
BUILD_RELEASE: ${{ github.event_name == 'release' || github.event_name == 'schedule' || github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }}
|
||||
CUT_RELEASE_PR: true
|
||||
BUILD_RELEASE: true
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
@ -44,7 +45,7 @@ jobs:
|
||||
|
||||
# TODO: see if we can fetch from main instead if no diff at src/wasm-lib
|
||||
- name: Run build:wasm
|
||||
run: "yarn build:wasm${{ env.BUILD_RELEASE == 'true' && '-dev' || ''}}"
|
||||
run: "yarn build:wasm"
|
||||
|
||||
- name: Set nightly version
|
||||
if: github.event_name == 'schedule'
|
||||
@ -156,15 +157,15 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
contents: write
|
||||
if: ${{ github.event_name == 'release' || github.event_name == 'schedule' }}
|
||||
# if: ${{ github.event_name == 'release' || github.event_name == 'schedule' }}
|
||||
needs: [prepare-files, build-apps]
|
||||
env:
|
||||
VERSION_NO_V: ${{ needs.prepare-files.outputs.version }}
|
||||
VERSION: ${{ github.event_name == 'schedule' && needs.prepare-files.outputs.version || format('v{0}', needs.prepare-files.outputs.version) }}
|
||||
PUB_DATE: ${{ github.event_name == 'release' && github.event.release.created_at || github.event.repository.updated_at }}
|
||||
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Non-release build, commit {0}', github.sha) }}
|
||||
BUCKET_DIR: ${{ github.event_name == 'schedule' && 'dl.kittycad.io/releases/modeling-app/nightly' || 'dl.kittycad.io/releases/modeling-app' }}
|
||||
WEBSITE_DIR: ${{ github.event_name == 'schedule' && 'dl.zoo.dev/releases/modeling-app/nightly' || 'dl.zoo.dev/releases/modeling-app' }}
|
||||
BUCKET_DIR: ${{ github.event_name == 'schedule' && 'dl.kittycad.io/releases/modeling-app/nightly' || 'dl.kittycad.io/releases/modeling-app/test/cut-release-v0.25.1-updater-test' }}
|
||||
WEBSITE_DIR: ${{ github.event_name == 'schedule' && 'dl.zoo.dev/releases/modeling-app/nightly' || 'dl.zoo.dev/releases/modeling-app/test/cut-release-v0.25.1-updater-test' }}
|
||||
URL_CODED_NAME: ${{ github.event_name == 'schedule' && 'Zoo%20Modeling%20App%20%28Nightly%29' || 'Zoo%20Modeling%20App' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@ -245,6 +246,15 @@ jobs:
|
||||
parent: false
|
||||
destination: ${{ env.BUCKET_DIR }}
|
||||
|
||||
# TODO: remove workaround introduced in https://github.com/KittyCAD/modeling-app/issues/3817
|
||||
- name: Upload release files to public bucket (test/electron-builder workaround)
|
||||
uses: google-github-actions/upload-cloud-storage@v2.2.0
|
||||
with:
|
||||
path: out
|
||||
glob: 'Zoo*'
|
||||
parent: false
|
||||
destination: '${{ env.BUCKET_DIR }}/test/electron-builder'
|
||||
|
||||
- name: Upload update endpoint to public bucket
|
||||
uses: google-github-actions/upload-cloud-storage@v2.2.0
|
||||
with:
|
||||
@ -253,6 +263,15 @@ jobs:
|
||||
parent: false
|
||||
destination: ${{ env.BUCKET_DIR }}
|
||||
|
||||
# TODO: remove workaround introduced in https://github.com/KittyCAD/modeling-app/issues/3817
|
||||
- name: Upload update endpoint to public bucket (test/electron-builder workaround)
|
||||
uses: google-github-actions/upload-cloud-storage@v2.2.0
|
||||
with:
|
||||
path: out
|
||||
glob: 'latest*'
|
||||
parent: false
|
||||
destination: '${{ env.BUCKET_DIR }}/test/electron-builder'
|
||||
|
||||
- name: Upload download endpoint to public bucket
|
||||
uses: google-github-actions/upload-cloud-storage@v2.2.0
|
||||
with:
|
||||
|
3
.github/workflows/cargo-clippy.yml
vendored
@ -28,6 +28,7 @@ jobs:
|
||||
dir: ['src/wasm-lib']
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: taiki-e/install-action@just
|
||||
- name: Install latest rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
@ -41,7 +42,7 @@ jobs:
|
||||
- name: Run clippy
|
||||
run: |
|
||||
cd "${{ matrix.dir }}"
|
||||
cargo clippy --all --tests --benches -- -D warnings
|
||||
just lint
|
||||
# If this fails, run "cargo check" to update Cargo.lock,
|
||||
# then add Cargo.lock to the PR.
|
||||
- name: Check Cargo.lock doesn't need updating
|
||||
|
@ -112,7 +112,8 @@ test.describe('when using the file tree to', () => {
|
||||
})
|
||||
|
||||
const {
|
||||
panesOpen,
|
||||
openKclCodePanel,
|
||||
openFilePanel,
|
||||
createAndSelectProject,
|
||||
pasteCodeInEditor,
|
||||
createNewFileAndSelect,
|
||||
@ -124,9 +125,9 @@ test.describe('when using the file tree to', () => {
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
await panesOpen(['files', 'code'])
|
||||
|
||||
await createAndSelectProject('project-000')
|
||||
await openKclCodePanel()
|
||||
await openFilePanel()
|
||||
// File the main.kcl with contents
|
||||
const kclCube = await fsp.readFile(
|
||||
'src/wasm-lib/tests/executor/inputs/cube.kcl',
|
||||
|
@ -548,13 +548,16 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
||||
|
||||
createNewFileAndSelect: async (name: string) => {
|
||||
return test?.step(`Create a file named ${name}, select it`, async () => {
|
||||
await openFilePanel(page)
|
||||
await page.getByTestId('create-file-button').click()
|
||||
await page.getByTestId('file-rename-field').fill(name)
|
||||
await page.keyboard.press('Enter')
|
||||
await page
|
||||
const newFile = page
|
||||
.locator('[data-testid="file-pane-scroll-container"] button')
|
||||
.filter({ hasText: name })
|
||||
.click()
|
||||
|
||||
await expect(newFile).toBeVisible()
|
||||
await newFile.click()
|
||||
})
|
||||
},
|
||||
|
||||
@ -585,6 +588,15 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* @deprecated Sorry I don't have time to fix this right now, but runs like
|
||||
* the one linked below show me that setting the open panes in this manner is not reliable.
|
||||
* You can either set `openPanes` as a part of the same initScript we run in setupElectron/setup,
|
||||
* or you can imperatively open the panes with functions like {openKclCodePanel}
|
||||
* (or we can make a general openPane function that takes a paneId).,
|
||||
* but having a separate initScript does not seem to work reliably.
|
||||
* @link https://github.com/KittyCAD/modeling-app/actions/runs/10731890169/job/29762700806?pr=3807#step:20:19553
|
||||
*/
|
||||
panesOpen: async (paneIds: PaneId[]) => {
|
||||
return test?.step(`Setting ${paneIds} panes to be open`, async () => {
|
||||
await page.addInitScript(
|
||||
|
@ -288,7 +288,7 @@ test.describe('Testing settings', () => {
|
||||
})
|
||||
|
||||
await test.step('Refresh the application and see project setting applied', async () => {
|
||||
await page.reload()
|
||||
await page.reload({ waitUntil: 'domcontentloaded' })
|
||||
|
||||
await expect(logoLink).toHaveCSS('--primary-hue', projectThemeColor)
|
||||
await settingsCloseButton.click()
|
||||
@ -364,47 +364,48 @@ test.describe('Testing settings', () => {
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async () => {},
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'project-000')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cube.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(bracketDir, '2.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
const kclCube = await fsp.readFile(executorInputPath('cube.kcl'), 'utf-8')
|
||||
const kclCylinder = await fsp.readFile(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
'utf8'
|
||||
)
|
||||
|
||||
const {
|
||||
panesOpen,
|
||||
createAndSelectProject,
|
||||
pasteCodeInEditor,
|
||||
clickPane,
|
||||
createNewFileAndSelect,
|
||||
openKclCodePanel,
|
||||
openFilePanel,
|
||||
waitForPageLoad,
|
||||
selectFile,
|
||||
editorTextMatches,
|
||||
} = await getUtils(page, test)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
await panesOpen([])
|
||||
|
||||
await test.step('Precondition: No projects exist', async () => {
|
||||
await test.step('Precondition: Open to second project file', async () => {
|
||||
await expect(page.getByTestId('home-section')).toBeVisible()
|
||||
const projectLinksPre = page.getByTestId('project-link')
|
||||
await expect(projectLinksPre).toHaveCount(0)
|
||||
await page.getByText('project-000').click()
|
||||
await waitForPageLoad()
|
||||
await openKclCodePanel()
|
||||
await openFilePanel()
|
||||
await editorTextMatches(kclCube)
|
||||
|
||||
await selectFile('2.kcl')
|
||||
await editorTextMatches(kclCylinder)
|
||||
})
|
||||
|
||||
await createAndSelectProject('project-000')
|
||||
|
||||
await clickPane('code')
|
||||
const kclCube = await fsp.readFile(
|
||||
'src/wasm-lib/tests/executor/inputs/cube.kcl',
|
||||
'utf-8'
|
||||
)
|
||||
await pasteCodeInEditor(kclCube)
|
||||
|
||||
await clickPane('files')
|
||||
await createNewFileAndSelect('2.kcl')
|
||||
|
||||
const kclCylinder = await fsp.readFile(
|
||||
'src/wasm-lib/tests/executor/inputs/cylinder.kcl',
|
||||
'utf-8'
|
||||
)
|
||||
await pasteCodeInEditor(kclCylinder)
|
||||
|
||||
const settingsOpenButton = page.getByRole('link', {
|
||||
name: 'settings Settings',
|
||||
})
|
||||
@ -412,6 +413,9 @@ test.describe('Testing settings', () => {
|
||||
|
||||
await test.step('Open and close settings', async () => {
|
||||
await settingsOpenButton.click()
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Settings', exact: true })
|
||||
).toBeVisible()
|
||||
await settingsCloseButton.click()
|
||||
})
|
||||
|
||||
|
@ -534,7 +534,7 @@ test.describe('Text-to-CAD tests', () => {
|
||||
|
||||
// Ensure the final toast remains.
|
||||
await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible()
|
||||
await expect(page.getByText(`a 2x8 lego`)).not.toBeVisible()
|
||||
await expect(page.getByText(`Prompt: "a 2x8 lego`)).not.toBeVisible()
|
||||
await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
|
||||
|
||||
// Ensure you can copy the code for the final model.
|
||||
@ -690,40 +690,53 @@ test(
|
||||
'Text-to-CAD functionality',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const projectName = 'project-000'
|
||||
const prompt = 'lego 2x4'
|
||||
const textToCadFileName = 'lego-2x4.kcl'
|
||||
|
||||
const { electronApp, page, dir } = await setupElectron({ testInfo })
|
||||
const fileExists = () =>
|
||||
fs.existsSync(join(dir, 'project-000', 'lego-2x4.kcl'))
|
||||
fs.existsSync(join(dir, projectName, textToCadFileName))
|
||||
|
||||
const { createAndSelectProject, panesOpen } = await getUtils(page, test)
|
||||
const {
|
||||
createAndSelectProject,
|
||||
openFilePanel,
|
||||
openKclCodePanel,
|
||||
waitForPageLoad,
|
||||
} = await getUtils(page, test)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await panesOpen(['code', 'files'])
|
||||
// Locators
|
||||
const projectMenuButton = page.getByRole('button', { name: projectName })
|
||||
const textToCadFileButton = page.getByRole('listitem').filter({
|
||||
has: page.getByRole('button', { name: textToCadFileName }),
|
||||
})
|
||||
const textToCadComment = page.getByText(
|
||||
`// Generated by Text-to-CAD: ${prompt}`
|
||||
)
|
||||
|
||||
// Create and navigate to the project
|
||||
await createAndSelectProject('project-000')
|
||||
|
||||
// Wait for Start Sketch otherwise you will not have access Text-to-CAD command
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).toBeEnabled({
|
||||
timeout: 20_000,
|
||||
})
|
||||
await waitForPageLoad()
|
||||
await openFilePanel()
|
||||
await openKclCodePanel()
|
||||
|
||||
await test.step(`Test file creation`, async () => {
|
||||
await sendPromptFromCommandBar(page, 'lego 2x4')
|
||||
await sendPromptFromCommandBar(page, prompt)
|
||||
// File is considered created if it shows up in the Project Files pane
|
||||
const file = page.getByRole('button', { name: 'lego-2x4.kcl' })
|
||||
await expect(file).toBeVisible({ timeout: 20_000 })
|
||||
await expect(textToCadFileButton).toBeVisible({ timeout: 20_000 })
|
||||
expect(fileExists()).toBeTruthy()
|
||||
})
|
||||
|
||||
await test.step(`Test file navigation`, async () => {
|
||||
const file = page.getByRole('button', { name: 'lego-2x4.kcl' })
|
||||
await file.click()
|
||||
const kclComment = page.getByText('Lego 2x4 Brick')
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
await textToCadFileButton.click()
|
||||
// File can be navigated and loaded assuming a specific KCL comment is loaded into the KCL code pane
|
||||
await expect(kclComment).toBeVisible({ timeout: 20_000 })
|
||||
await expect(textToCadComment).toBeVisible({ timeout: 20_000 })
|
||||
await expect(projectMenuButton).toContainText(textToCadFileName)
|
||||
})
|
||||
|
||||
await test.step(`Test file deletion on rejection`, async () => {
|
||||
@ -737,6 +750,8 @@ test(
|
||||
)
|
||||
await expect(submittingToastMessage).toBeVisible()
|
||||
expect(fileExists()).toBeFalsy()
|
||||
// Confirm we've navigated back to the main.kcl file after deletion
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
|
@ -79,5 +79,5 @@ linux:
|
||||
|
||||
publish:
|
||||
- provider: generic
|
||||
url: https://dl.zoo.dev/releases/modeling-app/test/electron-builder
|
||||
url: https://dl.zoo.dev/releases/modeling-app/test/cut-release-v0.25.1-updater-test
|
||||
channel: latest
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "zoo-modeling-app",
|
||||
"version": "0.25.0",
|
||||
"version": "0.25.1",
|
||||
"private": true,
|
||||
"productName": "Zoo Modeling App",
|
||||
"author": {
|
||||
|
@ -8,7 +8,7 @@ import { moveValueIntoNewVariable } from 'lang/modifyAst'
|
||||
import { isNodeSafeToReplace } from 'lang/queryAst'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useModelingContext } from './useModelingContext'
|
||||
import { PathToNode, SourceRange, parse, recast } from 'lang/wasm'
|
||||
import { PathToNode, SourceRange } from 'lang/wasm'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
|
||||
export const getVarNameModal = createSetVarNameModal(SetVarNameModal)
|
||||
@ -23,8 +23,7 @@ export function useConvertToVariable(range?: SourceRange) {
|
||||
}, [enable])
|
||||
|
||||
useEffect(() => {
|
||||
const parsed = parse(recast(ast))
|
||||
if (trap(parsed)) return
|
||||
const parsed = ast
|
||||
|
||||
const meta = isNodeSafeToReplace(
|
||||
parsed,
|
||||
|
@ -56,11 +56,6 @@ body.dark {
|
||||
.dark .body-bg {
|
||||
@apply bg-chalkboard-100;
|
||||
}
|
||||
|
||||
body {
|
||||
scrollbar-color: var(--color-chalkboard-70) var(--color-chalkboard-90);
|
||||
@apply text-chalkboard-10;
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
@ -300,32 +295,11 @@ code {
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
/* Modified from the very helpful https://www.transition.style/#in:circle:hesitate */
|
||||
@keyframes circle-in-hesitate {
|
||||
0% {
|
||||
clip-path: circle(
|
||||
var(--circle-size-start, 0%) at var(--circle-x, 50%)
|
||||
var(--circle-y, 50%)
|
||||
);
|
||||
}
|
||||
40% {
|
||||
clip-path: circle(
|
||||
var(--circle-size-mid, 40%) at var(--circle-x, 50%) var(--circle-y, 50%)
|
||||
);
|
||||
}
|
||||
100% {
|
||||
clip-path: circle(
|
||||
var(--circle-size-end, 125%) at var(--circle-x, 50%)
|
||||
var(--circle-y, 50%)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
.in-circle-hesitate {
|
||||
animation: var(--circle-duration, 2.5s)
|
||||
var(--circle-timing, cubic-bezier(0.25, 1, 0.3, 1)) circle-in-hesitate
|
||||
both;
|
||||
}
|
||||
/*
|
||||
This is where your own custom Tailwind utility classes can go,
|
||||
which lets you use them with @apply in your CSS, and get
|
||||
autocomplete in classNames in your JSX.
|
||||
*/
|
||||
}
|
||||
|
||||
#code-mirror-override .cm-scroller,
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
kclManager,
|
||||
sceneEntitiesManager,
|
||||
} from 'lib/singletons'
|
||||
import { CallExpression, SourceRange, Expr, parse, recast } from 'lang/wasm'
|
||||
import { CallExpression, SourceRange, Expr, parse } from 'lang/wasm'
|
||||
import { ModelingMachineEvent } from 'machines/modelingMachine'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { EditorSelection, SelectionRange } from '@codemirror/state'
|
||||
@ -300,8 +300,7 @@ export function processCodeMirrorRanges({
|
||||
}
|
||||
|
||||
function updateSceneObjectColors(codeBasedSelections: Selection[]) {
|
||||
const updated = parse(recast(kclManager.ast))
|
||||
if (err(updated)) return
|
||||
const updated = kclManager.ast
|
||||
|
||||
Object.values(sceneEntitiesManager.activeSegments).forEach((segmentGroup) => {
|
||||
if (
|
||||
|
@ -70,17 +70,11 @@ const SignIn = () => {
|
||||
>
|
||||
<div
|
||||
style={
|
||||
{
|
||||
height: 'calc(100vh - 16px)',
|
||||
'--circle-x': '14%',
|
||||
'--circle-y': '12%',
|
||||
'--circle-size-mid': '15%',
|
||||
'--circle-size-end': '200%',
|
||||
'--circle-timing': 'cubic-bezier(0.25, 1, 0.4, 0.9)',
|
||||
...(isDesktop() ? { '-webkit-app-region': 'no-drag' } : {}),
|
||||
} as CSSProperties
|
||||
isDesktop()
|
||||
? ({ '-webkit-app-region': 'no-drag' } as CSSProperties)
|
||||
: {}
|
||||
}
|
||||
className="in-circle-hesitate body-bg py-5 px-12 rounded-lg grid place-items-center overflow-y-auto"
|
||||
className="body-bg py-5 px-12 rounded-lg grid place-items-center overflow-y-auto"
|
||||
>
|
||||
<div className="max-w-7xl grid gap-5 grid-cols-3 xl:grid-cols-4 xl:grid-rows-5">
|
||||
<div className="col-span-2 xl:col-span-3 xl:row-span-3 max-w-3xl mr-8 mb-8">
|
||||
@ -204,7 +198,7 @@ const SignIn = () => {
|
||||
<div className="flex gap-4 flex-wrap items-center">
|
||||
<ActionButton
|
||||
Element="externalLink"
|
||||
to="https://zoo.dev/docs/kcl-samples/ball-bearing"
|
||||
to="https://zoo.dev/docs/kcl-samples/a-parametric-bearing-pillow-block"
|
||||
iconStart={{ icon: 'settings' }}
|
||||
className="border-chalkboard-30 dark:border-chalkboard-80"
|
||||
>
|
||||
|
19
src/wasm-lib/Cargo.lock
generated
@ -620,9 +620,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "6.0.1"
|
||||
version = "6.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "804c8821570c3f8b70230c2ba75ffa5c0f9a4189b9a432b6656c536712acae28"
|
||||
checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
@ -1357,7 +1357,7 @@ dependencies = [
|
||||
"clap",
|
||||
"convert_case",
|
||||
"criterion",
|
||||
"dashmap 6.0.1",
|
||||
"dashmap 6.1.0",
|
||||
"databake",
|
||||
"derive-docs",
|
||||
"expectorate",
|
||||
@ -1399,7 +1399,7 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"winnow 0.5.40",
|
||||
"winnow",
|
||||
"zip",
|
||||
]
|
||||
|
||||
@ -3117,7 +3117,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow 0.6.18",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3800,15 +3800,6 @@ version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.6.18"
|
||||
|
@ -2,3 +2,6 @@
|
||||
new-test name:
|
||||
echo "kcl_test!(\"{{name}}\", {{name}});" >> tests/executor/visuals.rs
|
||||
TWENTY_TWENTY=overwrite cargo nextest run --test executor -E 'test(=visuals::{{name}})'
|
||||
|
||||
lint:
|
||||
cargo clippy --all --tests --benches -- -D warnings
|
||||
|
@ -18,7 +18,7 @@ base64 = "0.22.1"
|
||||
chrono = "0.4.38"
|
||||
clap = { version = "4.5.17", default-features = false, optional = true, features = ["std", "derive"] }
|
||||
convert_case = "0.6.0"
|
||||
dashmap = "6.0.1"
|
||||
dashmap = "6.1.0"
|
||||
databake = { version = "0.1.8", features = ["derive"] }
|
||||
derive-docs = { version = "0.1.26", path = "../derive-docs" }
|
||||
form_urlencoded = "1.2.1"
|
||||
@ -47,7 +47,7 @@ url = { version = "2.5.2", features = ["serde"] }
|
||||
urlencoding = "2.1.3"
|
||||
uuid = { version = "1.10.0", features = ["v4", "js", "serde"] }
|
||||
validator = { version = "0.18.1", features = ["derive"] }
|
||||
winnow = "0.5.40"
|
||||
winnow = "0.6.18"
|
||||
zip = { version = "2.0.0", default-features = false }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
|
@ -1,5 +1,5 @@
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||
use kcl_lib::test_server;
|
||||
use kcl_lib::{settings::types::UnitLength::Mm, test_server};
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
pub fn bench_execute(c: &mut Criterion) {
|
||||
@ -13,26 +13,42 @@ pub fn bench_execute(c: &mut Criterion) {
|
||||
// Configure Criterion.rs to detect smaller differences and increase sample size to improve
|
||||
// precision and counteract the resulting noise.
|
||||
group.sample_size(10);
|
||||
group.bench_with_input(BenchmarkId::new("execute_", name), &code, |b, &s| {
|
||||
group.bench_with_input(BenchmarkId::new("execute", name), &code, |b, &s| {
|
||||
let rt = Runtime::new().unwrap();
|
||||
|
||||
// Spawn a future onto the runtime
|
||||
b.iter(|| {
|
||||
rt.block_on(test_server::execute_and_snapshot(
|
||||
s,
|
||||
kcl_lib::settings::types::UnitLength::Mm,
|
||||
))
|
||||
.unwrap();
|
||||
rt.block_on(test_server::execute_and_snapshot(s, Mm)).unwrap();
|
||||
});
|
||||
});
|
||||
group.finish();
|
||||
}
|
||||
}
|
||||
|
||||
criterion_group!(benches, bench_execute);
|
||||
pub fn bench_lego(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("executor_lego_pattern");
|
||||
// Configure Criterion.rs to detect smaller differences and increase sample size to improve
|
||||
// precision and counteract the resulting noise.
|
||||
group.sample_size(10);
|
||||
// Create lego bricks with N x 10 bumps, where N is each element of `sizes`.
|
||||
let sizes = vec![1, 2, 4];
|
||||
for size in sizes {
|
||||
group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| {
|
||||
let rt = Runtime::new().unwrap();
|
||||
let code = LEGO_PROGRAM.replace("{{N}}", &size.to_string());
|
||||
// Spawn a future onto the runtime
|
||||
b.iter(|| {
|
||||
rt.block_on(test_server::execute_and_snapshot(&code, Mm)).unwrap();
|
||||
});
|
||||
});
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(benches, bench_lego, bench_execute);
|
||||
criterion_main!(benches);
|
||||
|
||||
const KITT_PROGRAM: &str = include_str!("../../tests/executor/inputs/kittycad_svg.kcl");
|
||||
const CUBE_PROGRAM: &str = include_str!("../../tests/executor/inputs/cube.kcl");
|
||||
const SERVER_RACK_HEAVY_PROGRAM: &str = include_str!("../../tests/executor/inputs/server-rack-heavy.kcl");
|
||||
const SERVER_RACK_LITE_PROGRAM: &str = include_str!("../../tests/executor/inputs/server-rack-lite.kcl");
|
||||
const LEGO_PROGRAM: &str = include_str!("../../tests/executor/inputs/slow_lego.kcl.tmpl");
|
||||
|
@ -927,7 +927,7 @@ pub fn function_body(i: TokenSlice) -> PResult<Program> {
|
||||
|
||||
match body_items_within_function.parse_next(i) {
|
||||
Err(ErrMode::Backtrack(_)) => {
|
||||
i.reset(start);
|
||||
i.reset(&start);
|
||||
break;
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
@ -937,7 +937,7 @@ pub fn function_body(i: TokenSlice) -> PResult<Program> {
|
||||
}
|
||||
}
|
||||
(Err(ErrMode::Backtrack(_)), _) => {
|
||||
i.reset(start);
|
||||
i.reset(&start);
|
||||
break;
|
||||
}
|
||||
(Err(e), _) => return Err(e),
|
||||
@ -1276,7 +1276,7 @@ fn unary_expression(i: TokenSlice) -> PResult<UnaryExpression> {
|
||||
|
||||
/// Consume tokens that make up a binary expression, but don't actually return them.
|
||||
/// Why not?
|
||||
/// Because this is designed to be used with .recognize() within the `binary_expression` parser.
|
||||
/// Because this is designed to be used with .take() within the `binary_expression` parser.
|
||||
fn binary_expression_tokens(i: TokenSlice) -> PResult<Vec<BinaryExpressionToken>> {
|
||||
let first = operand.parse_next(i).map(BinaryExpressionToken::from)?;
|
||||
let remaining: Vec<_> = repeat(
|
||||
@ -1308,7 +1308,7 @@ fn binary_expression(i: TokenSlice) -> PResult<BinaryExpression> {
|
||||
}
|
||||
|
||||
fn binary_expr_in_parens(i: TokenSlice) -> PResult<BinaryExpression> {
|
||||
let span_with_brackets = bracketed_section.recognize().parse_next(i)?;
|
||||
let span_with_brackets = bracketed_section.take().parse_next(i)?;
|
||||
let n = span_with_brackets.len();
|
||||
let mut span_no_brackets = &span_with_brackets[1..n - 1];
|
||||
let expr = binary_expression.parse_next(&mut span_no_brackets)?;
|
||||
|
@ -1,5 +1,6 @@
|
||||
use winnow::{
|
||||
error::{ErrorKind, ParseError, StrContext},
|
||||
stream::Stream,
|
||||
Located,
|
||||
};
|
||||
|
||||
@ -102,14 +103,17 @@ impl<C> std::default::Default for ContextError<C> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, C> winnow::error::ParserError<I> for ContextError<C> {
|
||||
impl<I, C> winnow::error::ParserError<I> for ContextError<C>
|
||||
where
|
||||
I: Stream,
|
||||
{
|
||||
#[inline]
|
||||
fn from_error_kind(_input: &I, _kind: ErrorKind) -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn append(self, _input: &I, _kind: ErrorKind) -> Self {
|
||||
fn append(self, _input: &I, _input_checkpoint: &<I as Stream>::Checkpoint, _kind: ErrorKind) -> Self {
|
||||
self
|
||||
}
|
||||
|
||||
@ -119,9 +123,12 @@ impl<I, C> winnow::error::ParserError<I> for ContextError<C> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, I> winnow::error::AddContext<I, C> for ContextError<C> {
|
||||
impl<C, I> winnow::error::AddContext<I, C> for ContextError<C>
|
||||
where
|
||||
I: Stream,
|
||||
{
|
||||
#[inline]
|
||||
fn add_context(mut self, _input: &I, ctx: C) -> Self {
|
||||
fn add_context(mut self, _input: &I, _input_checkpoint: &<I as Stream>::Checkpoint, ctx: C) -> Self {
|
||||
self.context.push(ctx);
|
||||
self
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
//! Functions related to extruding.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use derive_docs::stdlib;
|
||||
use kittycad::types::ExtrusionFaceCapType;
|
||||
use kittycad::types::{ExtrusionFaceCapType, ExtrusionFaceInfo};
|
||||
use schemars::JsonSchema;
|
||||
use uuid::Uuid;
|
||||
|
||||
@ -99,7 +101,7 @@ async fn inner_extrude(length: f64, sketch_group_set: SketchGroupSet, args: Args
|
||||
)
|
||||
.await?;
|
||||
|
||||
args.send_modeling_cmd(
|
||||
args.batch_modeling_cmd(
|
||||
id,
|
||||
kittycad::types::ModelingCmd::Extrude {
|
||||
target: sketch_group.id,
|
||||
@ -112,7 +114,7 @@ async fn inner_extrude(length: f64, sketch_group_set: SketchGroupSet, args: Args
|
||||
// Disable the sketch mode.
|
||||
args.batch_modeling_cmd(uuid::Uuid::new_v4(), kittycad::types::ModelingCmd::SketchModeDisable {})
|
||||
.await?;
|
||||
extrude_groups.push(do_post_extrude(sketch_group.clone(), length, id, args.clone()).await?);
|
||||
extrude_groups.push(do_post_extrude(sketch_group.clone(), length, args.clone()).await?);
|
||||
}
|
||||
|
||||
Ok(extrude_groups.into())
|
||||
@ -121,7 +123,6 @@ async fn inner_extrude(length: f64, sketch_group_set: SketchGroupSet, args: Args
|
||||
pub(crate) async fn do_post_extrude(
|
||||
sketch_group: SketchGroup,
|
||||
length: f64,
|
||||
id: Uuid,
|
||||
args: Args,
|
||||
) -> Result<Box<ExtrudeGroup>, KclError> {
|
||||
// Bring the object to the front of the scene.
|
||||
@ -165,7 +166,7 @@ pub(crate) async fn do_post_extrude(
|
||||
|
||||
let solid3d_info = args
|
||||
.send_modeling_cmd(
|
||||
id,
|
||||
uuid::Uuid::new_v4(),
|
||||
kittycad::types::ModelingCmd::Solid3DGetExtrusionFaceInfo {
|
||||
edge_id,
|
||||
object_id: sketch_group.id,
|
||||
@ -218,26 +219,11 @@ pub(crate) async fn do_post_extrude(
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Create a hashmap for quick id lookup
|
||||
let mut face_id_map = std::collections::HashMap::new();
|
||||
// creating fake ids for start and end caps is to make extrudes mock-execute safe
|
||||
let (mut start_cap_id, mut end_cap_id) = if args.ctx.is_mock {
|
||||
(Some(Uuid::new_v4()), Some(Uuid::new_v4()))
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
for face_info in face_infos {
|
||||
match face_info.cap {
|
||||
ExtrusionFaceCapType::Bottom => start_cap_id = face_info.face_id,
|
||||
ExtrusionFaceCapType::Top => end_cap_id = face_info.face_id,
|
||||
ExtrusionFaceCapType::None => {
|
||||
if let Some(curve_id) = face_info.curve_id {
|
||||
face_id_map.insert(curve_id, face_info.face_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let Faces {
|
||||
sides: face_id_map,
|
||||
start_cap_id,
|
||||
end_cap_id,
|
||||
} = analyze_faces(&args, face_infos);
|
||||
// Iterate over the sketch_group.value array and add face_id to GeoMeta
|
||||
let new_value = sketch_group
|
||||
.value
|
||||
@ -301,3 +287,37 @@ pub(crate) async fn do_post_extrude(
|
||||
edge_cuts: vec![],
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Faces {
|
||||
/// Maps curve ID to face ID for each side.
|
||||
sides: HashMap<Uuid, Option<Uuid>>,
|
||||
/// Top face ID.
|
||||
end_cap_id: Option<Uuid>,
|
||||
/// Bottom face ID.
|
||||
start_cap_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
fn analyze_faces(args: &Args, face_infos: Vec<ExtrusionFaceInfo>) -> Faces {
|
||||
let mut faces = Faces {
|
||||
sides: HashMap::with_capacity(face_infos.len()),
|
||||
..Default::default()
|
||||
};
|
||||
if args.ctx.is_mock {
|
||||
// Create fake IDs for start and end caps, to make extrudes mock-execute safe
|
||||
faces.start_cap_id = Some(Uuid::new_v4());
|
||||
faces.end_cap_id = Some(Uuid::new_v4());
|
||||
}
|
||||
for face_info in face_infos {
|
||||
match face_info.cap {
|
||||
ExtrusionFaceCapType::Bottom => faces.start_cap_id = face_info.face_id,
|
||||
ExtrusionFaceCapType::Top => faces.end_cap_id = face_info.face_id,
|
||||
ExtrusionFaceCapType::None => {
|
||||
if let Some(curve_id) = face_info.curve_id {
|
||||
faces.sides.insert(curve_id, face_info.face_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
faces
|
||||
}
|
||||
|
@ -170,5 +170,5 @@ async fn inner_loft(
|
||||
.await?;
|
||||
|
||||
// Using the first sketch as the base curve, idk we might want to change this later.
|
||||
do_post_extrude(sketch_groups[0].clone(), 0.0, id, args).await
|
||||
do_post_extrude(sketch_groups[0].clone(), 0.0, args).await
|
||||
}
|
||||
|
@ -299,7 +299,7 @@ async fn inner_revolve(
|
||||
}
|
||||
}
|
||||
|
||||
do_post_extrude(sketch_group, 0.0, id, args).await
|
||||
do_post_extrude(sketch_group, 0.0, args).await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -50,13 +50,13 @@ pub fn token(i: &mut Located<&str>) -> PResult<Token> {
|
||||
}
|
||||
|
||||
fn block_comment(i: &mut Located<&str>) -> PResult<Token> {
|
||||
let inner = ("/*", take_until(0.., "*/"), "*/").recognize();
|
||||
let inner = ("/*", take_until(0.., "*/"), "*/").take();
|
||||
let (value, range) = inner.with_span().parse_next(i)?;
|
||||
Ok(Token::from_range(range, TokenType::BlockComment, value.to_string()))
|
||||
}
|
||||
|
||||
fn line_comment(i: &mut Located<&str>) -> PResult<Token> {
|
||||
let inner = (r#"//"#, take_till(0.., ['\n', '\r'])).recognize();
|
||||
let inner = (r#"//"#, take_till(0.., ['\n', '\r'])).take();
|
||||
let (value, range) = inner.with_span().parse_next(i)?;
|
||||
Ok(Token::from_range(range, TokenType::LineComment, value.to_string()))
|
||||
}
|
||||
@ -68,7 +68,7 @@ fn number(i: &mut Located<&str>) -> PResult<Token> {
|
||||
// No digits before the decimal point.
|
||||
('.', digit1).map(|_| ()),
|
||||
));
|
||||
let (value, range) = number_parser.recognize().with_span().parse_next(i)?;
|
||||
let (value, range) = number_parser.take().with_span().parse_next(i)?;
|
||||
Ok(Token::from_range(range, TokenType::Number, value.to_string()))
|
||||
}
|
||||
|
||||
@ -84,7 +84,7 @@ fn inner_word(i: &mut Located<&str>) -> PResult<()> {
|
||||
}
|
||||
|
||||
fn word(i: &mut Located<&str>) -> PResult<Token> {
|
||||
let (value, range) = inner_word.recognize().with_span().parse_next(i)?;
|
||||
let (value, range) = inner_word.take().with_span().parse_next(i)?;
|
||||
Ok(Token::from_range(range, TokenType::Word, value.to_string()))
|
||||
}
|
||||
|
||||
@ -162,9 +162,9 @@ fn inner_single_quote(i: &mut Located<&str>) -> PResult<()> {
|
||||
}
|
||||
|
||||
fn string(i: &mut Located<&str>) -> PResult<Token> {
|
||||
let single_quoted_string = ('\'', inner_single_quote.recognize(), '\'');
|
||||
let double_quoted_string = ('"', inner_double_quote.recognize(), '"');
|
||||
let either_quoted_string = alt((single_quoted_string.recognize(), double_quoted_string.recognize()));
|
||||
let single_quoted_string = ('\'', inner_single_quote.take(), '\'');
|
||||
let double_quoted_string = ('"', inner_double_quote.take(), '"');
|
||||
let either_quoted_string = alt((single_quoted_string.take(), double_quoted_string.take()));
|
||||
let (value, range): (&str, _) = either_quoted_string.with_span().parse_next(i)?;
|
||||
Ok(Token::from_range(range, TokenType::String, value.to_string()))
|
||||
}
|
||||
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 107 KiB |
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 135 KiB |
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 135 KiB |
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 101 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 91 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 65 KiB |
81
src/wasm-lib/tests/executor/inputs/slow_lego.kcl.tmpl
Normal file
@ -0,0 +1,81 @@
|
||||
// 2x8 Lego Brick
|
||||
// A standard Lego brick with 2 bumps wide and 8 bumps long.
|
||||
// Define constants
|
||||
const lbumps = 10 // number of bumps long
|
||||
const wbumps = {{N}} // number of bumps wide
|
||||
const pitch = 8.0
|
||||
const clearance = 0.1
|
||||
const bumpDiam = 4.8
|
||||
const bumpHeight = 1.8
|
||||
const height = 9.6
|
||||
const t = (pitch - (2 * clearance) - bumpDiam) / 2.0
|
||||
const totalLength = lbumps * pitch - (2.0 * clearance)
|
||||
const totalWidth = wbumps * pitch - (2.0 * clearance)
|
||||
// Create the plane for the pegs. This is a hack so that the pegs can be patterned along the face of the lego base.
|
||||
const pegFace = {
|
||||
plane: {
|
||||
origin: { x: 0, y: 0, z: height },
|
||||
xAxis: { x: 1, y: 0, z: 0 },
|
||||
yAxis: { x: 0, y: 1, z: 0 },
|
||||
zAxis: { x: 0, y: 0, z: 1 }
|
||||
}
|
||||
}
|
||||
// Create the plane for the tubes underneath the lego. This is a hack so that the tubes can be patterned underneath the lego.
|
||||
const tubeFace = {
|
||||
plane: {
|
||||
origin: { x: 0, y: 0, z: height - t },
|
||||
xAxis: { x: 1, y: 0, z: 0 },
|
||||
yAxis: { x: 0, y: 1, z: 0 },
|
||||
zAxis: { x: 0, y: 0, z: 1 }
|
||||
}
|
||||
}
|
||||
// Make the base
|
||||
const s = startSketchOn('XY')
|
||||
|> startProfileAt([-totalWidth / 2, -totalLength / 2], %)
|
||||
|> line([totalWidth, 0], %)
|
||||
|> line([0, totalLength], %)
|
||||
|> line([-totalWidth, 0], %)
|
||||
|> close(%)
|
||||
|> extrude(height, %)
|
||||
|
||||
// Sketch and extrude a rectangular shape to create the shell underneath the lego. This is a hack until we have a shell function.
|
||||
const shellExtrude = startSketchOn(s, "start")
|
||||
|> startProfileAt([
|
||||
-(totalWidth / 2 - t),
|
||||
-(totalLength / 2 - t)
|
||||
], %)
|
||||
|> line([totalWidth - (2 * t), 0], %)
|
||||
|> line([0, totalLength - (2 * t)], %)
|
||||
|> line([-(totalWidth - (2 * t)), 0], %)
|
||||
|> close(%)
|
||||
|> extrude(-(height - t), %)
|
||||
|
||||
fn tr = (i) => {
|
||||
let j = i + 1
|
||||
let x = (j/wbumps) * pitch
|
||||
let y = (j % wbumps) * pitch
|
||||
return {
|
||||
translate: [x, y, 0],
|
||||
}
|
||||
}
|
||||
|
||||
// Create the pegs on the top of the base
|
||||
const totalBumps = (wbumps * lbumps)-1
|
||||
const peg = startSketchOn(s, 'end')
|
||||
|> circle([
|
||||
-(pitch*(wbumps-1)/2),
|
||||
-(pitch*(lbumps-1)/2)
|
||||
], bumpDiam / 2, %)
|
||||
|> patternLinear2d({
|
||||
axis: [1, 0],
|
||||
repetitions: wbumps-1,
|
||||
distance: pitch
|
||||
}, %)
|
||||
|> patternLinear2d({
|
||||
axis: [0, 1],
|
||||
repetitions: lbumps-1,
|
||||
distance: pitch
|
||||
}, %)
|
||||
|> extrude(bumpHeight, %)
|
||||
// |> patternTransform(int(totalBumps-1), tr, %)
|
||||
|