Compare commits
66 Commits
reuse-exam
...
v0.21.3
Author | SHA1 | Date | |
---|---|---|---|
e63bf5db11 | |||
863e4e206f | |||
f1cd2355c6 | |||
164b675a86 | |||
b1afe1c541 | |||
26ef7218b2 | |||
e5a4fb439c | |||
97ad66a358 | |||
26438270ff | |||
a0cfda6d7a | |||
58a62b8097 | |||
e2909c509f | |||
07eaf93e78 | |||
6a5ca3088a | |||
6501072d80 | |||
726fd02bad | |||
d0f9ae475f | |||
da323e22d4 | |||
8dc3628e9b | |||
253744867b | |||
c45eb1e3e3 | |||
758aac9328 | |||
309943cf2c | |||
b3d4ab91fc | |||
5e73fa45f0 | |||
17d23a17db | |||
0460f8eaee | |||
2077cdb6fc | |||
cb0b7e8169 | |||
3a05211d30 | |||
d12d103cba | |||
04f6d3dcc8 | |||
9c9ffa0d03 | |||
c62b9f1f04 | |||
fcac3c72e4 | |||
1e2f577a9f | |||
1814f340fb | |||
43928f88aa | |||
6959036688 | |||
570d0473c6 | |||
44f0d7c25c | |||
3ccb04c4e7 | |||
00058f699a | |||
5a478fe0b3 | |||
723cf4f746 | |||
3950de0a4d | |||
901d474986 | |||
e7ab645267 | |||
cf830f9895 | |||
2c1f53f0f0 | |||
d39e2502d0 | |||
51fed9c541 | |||
b3a09abe01 | |||
cd3a2fea07 | |||
c29c4a8567 | |||
39ccd94884 | |||
d99ab22b56 | |||
20a8f2aa6a | |||
93266a9819 | |||
a9c7a7cb13 | |||
8dd9b8d192 | |||
23181d8144 | |||
834967df6a | |||
deacaac33a | |||
c55603853b | |||
93f652647e |
@ -3,3 +3,4 @@ VITE_KC_API_BASE_URL=https://api.dev.zoo.dev
|
|||||||
VITE_KC_SITE_BASE_URL=https://dev.zoo.dev
|
VITE_KC_SITE_BASE_URL=https://dev.zoo.dev
|
||||||
VITE_KC_SKIP_AUTH=false
|
VITE_KC_SKIP_AUTH=false
|
||||||
VITE_KC_CONNECTION_TIMEOUT_MS=5000
|
VITE_KC_CONNECTION_TIMEOUT_MS=5000
|
||||||
|
VITE_KC_DEV_TOKEN="your token from dev.zoo.dev should go in .env.development.local"
|
||||||
|
82
.github/workflows/ci.yml
vendored
@ -130,7 +130,9 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
os: [macos-14, ubuntu-latest, windows-latest]
|
os: [macos-14, ubuntu-latest, windows-latest]
|
||||||
env:
|
env:
|
||||||
|
# Specific Apple Universal target for macos
|
||||||
TAURI_ARGS_MACOS: ${{ matrix.os == 'macos-14' && '--target universal-apple-darwin' || '' }}
|
TAURI_ARGS_MACOS: ${{ matrix.os == 'macos-14' && '--target universal-apple-darwin' || '' }}
|
||||||
|
# Only build executable on linux (no appimage or deb)
|
||||||
TAURI_ARGS_UBUNTU: ${{ matrix.os == 'ubuntu-latest' && '--bundles' || '' }}
|
TAURI_ARGS_UBUNTU: ${{ matrix.os == 'ubuntu-latest' && '--bundles' || '' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@ -237,9 +239,9 @@ jobs:
|
|||||||
includeDebug: true
|
includeDebug: true
|
||||||
args: "${{ env.TAURI_ARGS_MACOS }} ${{ env.TAURI_ARGS_UBUNTU }}"
|
args: "${{ env.TAURI_ARGS_MACOS }} ${{ env.TAURI_ARGS_UBUNTU }}"
|
||||||
|
|
||||||
|
- name: Build for Mac TestFlight (nightly)
|
||||||
- name: Mac App Store
|
if: ${{ github.event_name == 'schedule' && matrix.os == 'macos-14' }}
|
||||||
if: ${{ env.BUILD_RELEASE == 'true' && matrix.os == 'macos-14' }}
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
unset APPLE_SIGNING_IDENTITY
|
unset APPLE_SIGNING_IDENTITY
|
||||||
unset APPLE_CERTIFICATE
|
unset APPLE_CERTIFICATE
|
||||||
@ -248,45 +250,40 @@ jobs:
|
|||||||
profile="src-tauri/entitlements/Mac_App_Distribution.provisionprofile"
|
profile="src-tauri/entitlements/Mac_App_Distribution.provisionprofile"
|
||||||
|
|
||||||
mkdir -p src-tauri/entitlements
|
mkdir -p src-tauri/entitlements
|
||||||
echo "${APPLE_STORE_PROVISIONING_PROFILE}" | base64 --decode > "${profile}"
|
echo -n "${APPLE_STORE_PROVISIONING_PROFILE}" | base64 --decode -o "${profile}"
|
||||||
|
|
||||||
echo "${APPLE_STORE_DISTRIBUTION_CERT}" | base64 --decode > "dist.cer"
|
echo -n "${APPLE_STORE_DISTRIBUTION_CERT}" | base64 --decode -o "dist.cer"
|
||||||
echo "${APPLE_STORE_INSTALLER_CERT}" | base64 --decode > "installer.cer"
|
echo -n "${APPLE_STORE_INSTALLER_CERT}" | base64 --decode -o "installer.cer"
|
||||||
|
|
||||||
# load the certificates into the keychain
|
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
|
||||||
# Create a custom keychain
|
KEYCHAIN_PASSWORD="password"
|
||||||
security create-keychain -p gh_actions refine-build.keychain
|
|
||||||
|
|
||||||
# Make the custom keychain default, so xcodebuild will use it for signing
|
# create temporary keychain
|
||||||
security default-keychain -s refine-build.keychain
|
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
|
||||||
|
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
|
||||||
|
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
|
||||||
|
|
||||||
# Unlock the keychain
|
# import certificate to keychain
|
||||||
security unlock-keychain -p gh_actions refine-build.keychain
|
security import "dist.cer" -P "$APPLE_STORE_P12_PASSWORD" -k $KEYCHAIN_PATH -f pkcs12 -t cert -A
|
||||||
|
security import "installer.cer" -P "$APPLE_STORE_P12_PASSWORD" -k $KEYCHAIN_PATH -f pkcs12 -t cert -A
|
||||||
|
|
||||||
# Set keychain timeout to 1 hour for long builds
|
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
|
||||||
security set-keychain-settings -t 3600 -l ~/Library/Keychains/refine-build.keychain
|
security list-keychain -d user -s $KEYCHAIN_PATH
|
||||||
|
|
||||||
# Add certificates to keychain and allow codesign to access them
|
|
||||||
security import "dist.cer" -k ~/Library/Keychains/refine-build.keychain -T /usr/bin/codesign
|
|
||||||
security import "installer.cer" -k ~/Library/Keychains/refine-build.keychain -T /usr/bin/codesign
|
|
||||||
|
|
||||||
security set-key-partition-list -S apple-tool:,apple: -s -k gh_actions refine-build.keychain
|
|
||||||
|
|
||||||
target="universal-apple-darwin"
|
target="universal-apple-darwin"
|
||||||
|
|
||||||
# Turn off the default target
|
# Turn off the default target
|
||||||
sed -i "s/default =/# default =/" src-tauri/Cargo.toml
|
# We don't want to install the updater for the apple store build
|
||||||
yarn tauri build --target "${target}" --verbose
|
sed -i.bu "s/default =/# default =/" src-tauri/Cargo.toml
|
||||||
|
rm src-tauri/Cargo.toml.bu
|
||||||
|
git diff src-tauri/Cargo.toml
|
||||||
|
|
||||||
ls -l src-tauri/target/${target}
|
yarn tauri build --target "${target}" --verbose --config src-tauri/tauri.app-store.conf.json
|
||||||
ls -l src-tauri/target
|
|
||||||
ls -l src-tauri/target/${target}/release/bundle/macos
|
|
||||||
ls -l src-tauri/entitlements
|
|
||||||
|
|
||||||
app_path="src-tauri/target/${target}/release/bundle/macos/Zoo Modeling App.app"
|
app_path="src-tauri/target/${target}/release/bundle/macos/Zoo Modeling App.app"
|
||||||
build_name="src-tauri/target/${target}/release/bundle/macos/Zoo Modeling App.pkg"
|
build_name="src-tauri/target/${target}/release/bundle/macos/Zoo Modeling App.pkg"
|
||||||
cp_dir="src-tauri/target/${target}/release/bundle/macos/Zoo Modeling App.app/Contents/embedded.provisionprofile"
|
cp_dir="src-tauri/target/${target}/release/bundle/macos/Zoo Modeling App.app/Contents/embedded.provisionprofile"
|
||||||
entitlements="src-tauri/entitlements/Zoo Modeling App.entitlements"
|
entitlements="src-tauri/entitlements/app-store.entitlements"
|
||||||
|
|
||||||
cp "${profile}" "${cp_dir}"
|
cp "${profile}" "${cp_dir}"
|
||||||
|
|
||||||
@ -296,20 +293,38 @@ jobs:
|
|||||||
|
|
||||||
# Undo the changes to the Cargo.toml
|
# Undo the changes to the Cargo.toml
|
||||||
git checkout src-tauri/Cargo.toml
|
git checkout src-tauri/Cargo.toml
|
||||||
|
|
||||||
env:
|
env:
|
||||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||||
APPLE_STORE_PROVISIONING_PROFILE: ${{ secrets.APPLE_STORE_PROVISIONING_PROFILE }}
|
APPLE_STORE_PROVISIONING_PROFILE: ${{ secrets.APPLE_STORE_PROVISIONING_PROFILE }}
|
||||||
APPLE_STORE_DISTRIBUTION_CERT: ${{ secrets.APPLE_STORE_DISTRIBUTION_CERT }}
|
APPLE_STORE_DISTRIBUTION_CERT: ${{ secrets.APPLE_STORE_DISTRIBUTION_CERT }}
|
||||||
APPLE_STORE_INSTALLER_CERT: ${{ secrets.APPLE_STORE_INSTALLER_CERT }}
|
APPLE_STORE_INSTALLER_CERT: ${{ secrets.APPLE_STORE_INSTALLER_CERT }}
|
||||||
|
APPLE_STORE_P12_PASSWORD: ${{ secrets.APPLE_STORE_P12_PASSWORD }}
|
||||||
|
|
||||||
- name: 'Upload app to TestFlight'
|
|
||||||
|
- name: 'Upload to Mac TestFlight (nightly)'
|
||||||
uses: apple-actions/upload-testflight-build@v1
|
uses: apple-actions/upload-testflight-build@v1
|
||||||
if: ${{ env.BUILD_RELEASE == 'true' && matrix.os == 'macos-14' }}
|
if: ${{ github.event_name == 'schedule' && matrix.os == 'macos-14' }}
|
||||||
with:
|
with:
|
||||||
app-path: 'src-tauri/target/universal-apple-darwin/release/bundle/macos/Zoo Modeling App.pkg'
|
app-path: 'src-tauri/target/universal-apple-darwin/release/bundle/macos/Zoo Modeling App.pkg'
|
||||||
issuer-id: ${{ secrets.APPLE_STORE_ISSUER_ID }}
|
issuer-id: ${{ secrets.APPLE_STORE_ISSUER_ID }}
|
||||||
api-key-id: ${{ secrets.APPLE_STORE_API_KEY_ID }}
|
api-key-id: ${{ secrets.APPLE_STORE_API_KEY_ID }}
|
||||||
api-private-key: ${{ secrets.APPLE_STORE_API_PRIVATE_KEY }}
|
api-private-key: ${{ secrets.APPLE_STORE_API_PRIVATE_KEY }}
|
||||||
|
app-type: osx
|
||||||
|
|
||||||
|
|
||||||
|
- name: Clean up after Mac TestFlight (nightly)
|
||||||
|
if: ${{ github.event_name == 'schedule' && matrix.os == 'macos-14' }}
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
git status
|
||||||
|
# remove our target builds because we want to make sure the later build
|
||||||
|
# includes the updater, and that anything we changed with the target
|
||||||
|
# does not persist
|
||||||
|
rm -rf src-tauri/target
|
||||||
|
# Lets get rid of the info.plist for the normal mac builds since its
|
||||||
|
# being sketchy.
|
||||||
|
rm src-tauri/Info.plist
|
||||||
|
|
||||||
# We do this after the apple store because the apple store build is
|
# We do this after the apple store because the apple store build is
|
||||||
# specific and we want to overwrite it with the this new build after and
|
# specific and we want to overwrite it with the this new build after and
|
||||||
@ -338,11 +353,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: "${{ env.PREFIX }}/${{ env.MODE }}/bundle/*/*"
|
path: "${{ env.PREFIX }}/${{ env.MODE }}/bundle/*/*"
|
||||||
|
|
||||||
# TODO: re-enable linux e2e tests when possible
|
|
||||||
- name: Run e2e tests (linux only)
|
- name: Run e2e tests (linux only)
|
||||||
if: false
|
if: ${{ matrix.os == 'ubuntu-latest' && github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||||
run: |
|
run: |
|
||||||
cargo install tauri-driver
|
cargo install tauri-driver --force
|
||||||
source .env.${{ env.BUILD_RELEASE == 'true' && 'production' || 'development' }}
|
source .env.${{ env.BUILD_RELEASE == 'true' && 'production' || 'development' }}
|
||||||
export VITE_KC_API_BASE_URL
|
export VITE_KC_API_BASE_URL
|
||||||
xvfb-run yarn test:e2e:tauri
|
xvfb-run yarn test:e2e:tauri
|
||||||
@ -426,7 +440,7 @@ jobs:
|
|||||||
cat last_download.json
|
cat last_download.json
|
||||||
|
|
||||||
- name: Authenticate to Google Cloud
|
- name: Authenticate to Google Cloud
|
||||||
uses: 'google-github-actions/auth@v2.1.2'
|
uses: 'google-github-actions/auth@v2.1.3'
|
||||||
with:
|
with:
|
||||||
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'
|
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'
|
||||||
|
|
||||||
|
4
.github/workflows/create-release.yml
vendored
@ -17,11 +17,11 @@ jobs:
|
|||||||
name: Read Cut release PR info and create release
|
name: Read Cut release PR info and create release
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const { owner, repo, sha } = context.repo
|
const { owner, repo } = context.repo
|
||||||
const pulls = await github.rest.repos.listPullRequestsAssociatedWithCommit({
|
const pulls = await github.rest.repos.listPullRequestsAssociatedWithCommit({
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
commit_sha: sha,
|
commit_sha: context.sha,
|
||||||
})
|
})
|
||||||
const { title, body } = pulls.data[0]
|
const { title, body } = pulls.data[0]
|
||||||
const version = title.split('Cut release ')[1]
|
const version = title.split('Cut release ')[1]
|
||||||
|
@ -72,7 +72,13 @@ finally, to run the web app only, run:
|
|||||||
yarn start
|
yarn start
|
||||||
```
|
```
|
||||||
|
|
||||||
## Developing in Chrome
|
If you're not an KittyCAD employee you won't be able to access the dev environment, you should copy everything from `.env.production` to `.env.development` to make it point to production instead, then when you navigate to `localhost:3000` the easiest way to sign in is to paste `localStorage.setItem('TOKEN_PERSIST_KEY', "your-token-from-https://zoo.dev/account/api-tokens")` replacing the with a real token from https://zoo.dev/account/api-tokens ofcourse, then navigate to localhost:3000 again. Note that navigating to localhost:3000/signin removes your token so you will need to set the token again.
|
||||||
|
|
||||||
|
### Development environment variables
|
||||||
|
|
||||||
|
The Copilot LSP plugin in the editor requires a Zoo API token to run. In production, we authenticate this with a token via cookie in the browser and device auth token in the desktop environment, but this token is inaccessible in the dev browser version because the cookie is considered "cross-site" (from `localhost` to `dev.zoo.dev`). There is an optional environment variable called `VITE_KC_DEV_TOKEN` that you can populate with a dev token in a `.env.development.local` file to not check it into Git, which will use that token instead of other methods for the LSP service.
|
||||||
|
|
||||||
|
### Developing in Chrome
|
||||||
|
|
||||||
Chrome is in the process of rolling out a new default which
|
Chrome is in the process of rolling out a new default which
|
||||||
[blocks Third-Party Cookies](https://developer.chrome.com/en/docs/privacy-sandbox/third-party-cookie-phase-out/).
|
[blocks Third-Party Cookies](https://developer.chrome.com/en/docs/privacy-sandbox/third-party-cookie-phase-out/).
|
||||||
|
@ -31,7 +31,6 @@ layout: manual
|
|||||||
* [`fillet`](kcl/fillet)
|
* [`fillet`](kcl/fillet)
|
||||||
* [`floor`](kcl/floor)
|
* [`floor`](kcl/floor)
|
||||||
* [`getEdge`](kcl/getEdge)
|
* [`getEdge`](kcl/getEdge)
|
||||||
* [`getExtrudeWallTransform`](kcl/getExtrudeWallTransform)
|
|
||||||
* [`getNextAdjacentEdge`](kcl/getNextAdjacentEdge)
|
* [`getNextAdjacentEdge`](kcl/getNextAdjacentEdge)
|
||||||
* [`getOppositeEdge`](kcl/getOppositeEdge)
|
* [`getOppositeEdge`](kcl/getOppositeEdge)
|
||||||
* [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge)
|
* [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test, expect } from '@playwright/test'
|
||||||
import { getUtils } from './test-utils'
|
import { makeTemplate, getUtils } from './test-utils'
|
||||||
import waitOn from 'wait-on'
|
import waitOn from 'wait-on'
|
||||||
import { roundOff } from 'lib/utils'
|
import { roundOff } from 'lib/utils'
|
||||||
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
|
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
|
||||||
@ -8,9 +8,11 @@ import {
|
|||||||
TEST_SETTINGS,
|
TEST_SETTINGS,
|
||||||
TEST_SETTINGS_KEY,
|
TEST_SETTINGS_KEY,
|
||||||
TEST_SETTINGS_CORRUPTED,
|
TEST_SETTINGS_CORRUPTED,
|
||||||
TEST_SETTINGS_ONBOARDING,
|
TEST_SETTINGS_ONBOARDING_EXPORT,
|
||||||
|
TEST_SETTINGS_ONBOARDING_START,
|
||||||
} from './storageStates'
|
} from './storageStates'
|
||||||
import * as TOML from '@iarna/toml'
|
import * as TOML from '@iarna/toml'
|
||||||
|
import { Coords2d } from 'lang/std/sketch'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
debug helper: unfortunately we do rely on exact coord mouse clicks in a few places
|
debug helper: unfortunately we do rely on exact coord mouse clicks in a few places
|
||||||
@ -129,6 +131,7 @@ test('Basic sketch', async ({ page }) => {
|
|||||||
// selected two lines therefore there should be two cursors
|
// selected two lines therefore there should be two cursors
|
||||||
await expect(page.locator('.cm-cursor')).toHaveCount(2)
|
await expect(page.locator('.cm-cursor')).toHaveCount(2)
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Constrain' }).click()
|
||||||
await page.getByRole('button', { name: 'Equal Length' }).click()
|
await page.getByRole('button', { name: 'Equal Length' }).click()
|
||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
@ -262,6 +265,88 @@ test('Can moving camera', async ({ page, context }) => {
|
|||||||
}, [1, -94, -94])
|
}, [1, -94, -94])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('if you click the format button it formats your code', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
await page.goto('/')
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// check no error to begin with
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
|
await page.click('.cm-content')
|
||||||
|
await page.keyboard.type(`const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`)
|
||||||
|
await page.click('#code-pane button:first-child')
|
||||||
|
await page.click('button:has-text("Format code")')
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('if you use the format keyboard binding it formats your code', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = getUtils(page)
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
const lspStartPromise = page.waitForEvent('console', async (message) => {
|
||||||
|
// it would be better to wait for a message that the kcl lsp has started by looking for the message message.text().includes('[lsp] [window/logMessage]')
|
||||||
|
// but that doesn't seem to make it to the console for macos/safari :(
|
||||||
|
if (message.text().includes('start kcl lsp')) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
await page.goto('/')
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await lspStartPromise
|
||||||
|
|
||||||
|
// check no error to begin with
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
// focus the editor
|
||||||
|
await page.click('.cm-line')
|
||||||
|
|
||||||
|
// Hit alt+shift+f to format the code
|
||||||
|
await page.keyboard.press('Alt+Shift+KeyF')
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`)
|
||||||
|
})
|
||||||
|
|
||||||
test('if you write invalid kcl you get inlined errors', async ({ page }) => {
|
test('if you write invalid kcl you get inlined errors', async ({ page }) => {
|
||||||
const u = getUtils(page)
|
const u = getUtils(page)
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
@ -278,7 +363,7 @@ test('if you write invalid kcl you get inlined errors', async ({ page }) => {
|
|||||||
const bottomAng = 25
|
const bottomAng = 25
|
||||||
*/
|
*/
|
||||||
await page.click('.cm-content')
|
await page.click('.cm-content')
|
||||||
await page.keyboard.type('# error')
|
await page.keyboard.type('$ error')
|
||||||
|
|
||||||
// press arrows to clear autocomplete
|
// press arrows to clear autocomplete
|
||||||
await page.keyboard.press('ArrowLeft')
|
await page.keyboard.press('ArrowLeft')
|
||||||
@ -295,10 +380,10 @@ test('if you write invalid kcl you get inlined errors', async ({ page }) => {
|
|||||||
|
|
||||||
// error text on hover
|
// error text on hover
|
||||||
await page.hover('.cm-lint-marker-error')
|
await page.hover('.cm-lint-marker-error')
|
||||||
await expect(page.getByText("found unknown token '#'")).toBeVisible()
|
await expect(page.getByText("found unknown token '$'")).toBeVisible()
|
||||||
|
|
||||||
// select the line that's causing the error and delete it
|
// select the line that's causing the error and delete it
|
||||||
await page.getByText('# error').click()
|
await page.getByText('$ error').click()
|
||||||
await page.keyboard.press('End')
|
await page.keyboard.press('End')
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
await page.keyboard.press('Home')
|
await page.keyboard.press('Home')
|
||||||
@ -680,6 +765,45 @@ test('Project settings can be set and override user settings', async ({
|
|||||||
await expect(page.locator('select[name="app-theme"]')).toHaveValue('light')
|
await expect(page.locator('select[name="app-theme"]')).toHaveValue('light')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Click through each onboarding step', async ({ page }) => {
|
||||||
|
const u = getUtils(page)
|
||||||
|
|
||||||
|
// Override beforeEach test setup
|
||||||
|
await page.addInitScript(
|
||||||
|
async ({ settingsKey, settings }) => {
|
||||||
|
// Give no initial code, so that the onboarding start is shown immediately
|
||||||
|
localStorage.setItem('persistCode', '')
|
||||||
|
localStorage.setItem(settingsKey, settings)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
settingsKey: TEST_SETTINGS_KEY,
|
||||||
|
settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING_START }),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 1080 })
|
||||||
|
await page.goto('/')
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// Test that the onboarding pane loaded
|
||||||
|
await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible()
|
||||||
|
|
||||||
|
const nextButton = page.getByTestId('onboarding-next')
|
||||||
|
|
||||||
|
while ((await nextButton.innerText()) !== 'Finish') {
|
||||||
|
await expect(nextButton).toBeVisible()
|
||||||
|
await nextButton.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finish the onboarding
|
||||||
|
await expect(nextButton).toBeVisible()
|
||||||
|
await nextButton.click()
|
||||||
|
|
||||||
|
// Test that the onboarding pane is gone
|
||||||
|
await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
|
||||||
|
await expect(page.url()).not.toContain('onboarding')
|
||||||
|
})
|
||||||
|
|
||||||
test('Onboarding redirects and code updating', async ({ page }) => {
|
test('Onboarding redirects and code updating', async ({ page }) => {
|
||||||
const u = getUtils(page)
|
const u = getUtils(page)
|
||||||
|
|
||||||
@ -692,7 +816,7 @@ test('Onboarding redirects and code updating', async ({ page }) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
settingsKey: TEST_SETTINGS_KEY,
|
settingsKey: TEST_SETTINGS_KEY,
|
||||||
settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING }),
|
settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING_EXPORT }),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -817,11 +941,14 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
|||||||
// click a segment hold shift and click an axis, see that a relevant constraint is enabled
|
// click a segment hold shift and click an axis, see that a relevant constraint is enabled
|
||||||
await topHorzSegmentClick()
|
await topHorzSegmentClick()
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
|
const constrainButton = page.getByRole('button', { name: 'Constrain' })
|
||||||
const absYButton = page.getByRole('button', { name: 'ABS Y' })
|
const absYButton = page.getByRole('button', { name: 'ABS Y' })
|
||||||
|
await constrainButton.click()
|
||||||
await expect(absYButton).toBeDisabled()
|
await expect(absYButton).toBeDisabled()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await xAxisClick()
|
await xAxisClick()
|
||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
|
await constrainButton.click()
|
||||||
await absYButton.and(page.locator(':not([disabled])')).waitFor()
|
await absYButton.and(page.locator(':not([disabled])')).waitFor()
|
||||||
await expect(absYButton).not.toBeDisabled()
|
await expect(absYButton).not.toBeDisabled()
|
||||||
|
|
||||||
@ -831,12 +958,14 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
|||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
// same selection but click the axis first
|
// same selection but click the axis first
|
||||||
await xAxisClick()
|
await xAxisClick()
|
||||||
|
await constrainButton.click()
|
||||||
await expect(absYButton).toBeDisabled()
|
await expect(absYButton).toBeDisabled()
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await topHorzSegmentClick()
|
await topHorzSegmentClick()
|
||||||
|
|
||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
|
await constrainButton.click()
|
||||||
await expect(absYButton).not.toBeDisabled()
|
await expect(absYButton).not.toBeDisabled()
|
||||||
|
|
||||||
// clear selection by clicking on nothing
|
// clear selection by clicking on nothing
|
||||||
@ -845,10 +974,12 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
|||||||
// check the same selection again by putting cursor in code first then selecting axis
|
// check the same selection again by putting cursor in code first then selecting axis
|
||||||
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
|
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
|
await constrainButton.click()
|
||||||
await expect(absYButton).toBeDisabled()
|
await expect(absYButton).toBeDisabled()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await xAxisClick()
|
await xAxisClick()
|
||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
|
await constrainButton.click()
|
||||||
await expect(absYButton).not.toBeDisabled()
|
await expect(absYButton).not.toBeDisabled()
|
||||||
|
|
||||||
// clear selection by clicking on nothing
|
// clear selection by clicking on nothing
|
||||||
@ -910,9 +1041,8 @@ test.describe('Command bar tests', () => {
|
|||||||
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||||
|
|
||||||
// First try opening the command bar and closing it
|
// First try opening the command bar and closing it
|
||||||
// It has a different label on mac and windows/linux, "Meta+K" and "Ctrl+/" respectively
|
|
||||||
await page
|
await page
|
||||||
.getByRole('button', { name: 'Ctrl+/' })
|
.getByRole('button', { name: 'Commands', exact: false })
|
||||||
.or(page.getByRole('button', { name: '⌘K' }))
|
.or(page.getByRole('button', { name: '⌘K' }))
|
||||||
.click()
|
.click()
|
||||||
await expect(cmdSearchBar).toBeVisible()
|
await expect(cmdSearchBar).toBeVisible()
|
||||||
@ -957,13 +1087,13 @@ test.describe('Command bar tests', () => {
|
|||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`const distance = sqrt(20)
|
`const distance = sqrt(20)
|
||||||
const part001 = startSketchOn('-XZ')
|
const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt([-6.95, 4.98], %)
|
|> startProfileAt([-6.95, 10.98], %)
|
||||||
|> line([25.1, 0.41], %)
|
|> line([25.1, 0.41], %)
|
||||||
|> line([0.73, -14.93], %)
|
|> line([0.73, -20.93], %)
|
||||||
|> line([-23.44, 0.52], %)
|
|> line([-23.44, 0.52], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -980,7 +1110,6 @@ test.describe('Command bar tests', () => {
|
|||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
).not.toBeDisabled()
|
).not.toBeDisabled()
|
||||||
await u.clearCommandLogs()
|
await u.clearCommandLogs()
|
||||||
await page.getByText('|> line([0.73, -14.93], %)').click()
|
|
||||||
await page.getByRole('button', { name: 'Extrude' }).isEnabled()
|
await page.getByRole('button', { name: 'Extrude' }).isEnabled()
|
||||||
|
|
||||||
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||||
@ -990,6 +1119,12 @@ test.describe('Command bar tests', () => {
|
|||||||
// Search for extrude command and choose it
|
// Search for extrude command and choose it
|
||||||
await page.getByRole('option', { name: 'Extrude' }).click()
|
await page.getByRole('option', { name: 'Extrude' }).click()
|
||||||
|
|
||||||
|
// Assert that we're on the selection step
|
||||||
|
await expect(page.getByRole('button', { name: 'selection' })).toBeDisabled()
|
||||||
|
// Select a face
|
||||||
|
await page.mouse.move(700, 200)
|
||||||
|
await page.mouse.click(700, 200)
|
||||||
|
|
||||||
// Assert that we're on the distance step
|
// Assert that we're on the distance step
|
||||||
await expect(page.getByRole('button', { name: 'distance' })).toBeDisabled()
|
await expect(page.getByRole('button', { name: 'distance' })).toBeDisabled()
|
||||||
|
|
||||||
@ -1023,9 +1158,9 @@ test.describe('Command bar tests', () => {
|
|||||||
`const distance = sqrt(20)
|
`const distance = sqrt(20)
|
||||||
const distance001 = 5 + 7
|
const distance001 = 5 + 7
|
||||||
const part001 = startSketchOn('-XZ')
|
const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt([-6.95, 4.98], %)
|
|> startProfileAt([-6.95, 10.98], %)
|
||||||
|> line([25.1, 0.41], %)
|
|> line([25.1, 0.41], %)
|
||||||
|> line([0.73, -14.93], %)
|
|> line([0.73, -20.93], %)
|
||||||
|> line([-23.44, 0.52], %)
|
|> line([-23.44, 0.52], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(distance001, %)`.replace(/(\r\n|\n|\r)/gm, '') // remove newlines
|
|> extrude(distance001, %)`.replace(/(\r\n|\n|\r)/gm, '') // remove newlines
|
||||||
@ -1216,6 +1351,72 @@ test('ProgramMemory can be serialised', async ({ page }) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Hovering over 3d features highlights code', async ({ page }) => {
|
||||||
|
const u = getUtils(page)
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const part001 = startSketchOn('-XZ')
|
||||||
|
|> startProfileAt([20, 0], %)
|
||||||
|
|> line([7.13, 4 + 0], %)
|
||||||
|
|> angledLine({ angle: 3 + 0, length: 3.14 + 0 }, %)
|
||||||
|
|> lineTo([20.14 + 0, -0.14 + 0], %)
|
||||||
|
|> xLineTo(29 + 0, %)
|
||||||
|
|> yLine(-3.14 + 0, %, 'a')
|
||||||
|
|> xLine(1.63, %)
|
||||||
|
|> angledLineOfXLength({ angle: 3 + 0, length: 3.14 }, %)
|
||||||
|
|> angledLineOfYLength({ angle: 30, length: 3 + 0 }, %)
|
||||||
|
|> angledLineToX({ angle: 22.14 + 0, to: 12 }, %)
|
||||||
|
|> angledLineToY({ angle: 30, to: 11.14 }, %)
|
||||||
|
|> angledLineThatIntersects({
|
||||||
|
angle: 3.14,
|
||||||
|
intersectTag: 'a',
|
||||||
|
offset: 0
|
||||||
|
}, %)
|
||||||
|
|> tangentialArcTo([13.14 + 0, 13.14], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(5 + 7, %)
|
||||||
|
`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
await page.goto('/')
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
const extrusionTop: Coords2d = [800, 240]
|
||||||
|
const flatExtrusionFace: Coords2d = [960, 160]
|
||||||
|
const arc: Coords2d = [840, 160]
|
||||||
|
const close: Coords2d = [720, 200]
|
||||||
|
const nothing: Coords2d = [600, 200]
|
||||||
|
|
||||||
|
await page.mouse.move(nothing[0], nothing[1])
|
||||||
|
await page.mouse.click(nothing[0], nothing[1])
|
||||||
|
|
||||||
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
|
||||||
|
await page.mouse.move(extrusionTop[0], extrusionTop[1])
|
||||||
|
await expect(page.getByTestId('hover-highlight')).toBeVisible()
|
||||||
|
await page.mouse.move(nothing[0], nothing[1])
|
||||||
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||||
|
|
||||||
|
await page.mouse.move(arc[0], arc[1])
|
||||||
|
await expect(page.getByTestId('hover-highlight')).toBeVisible()
|
||||||
|
await page.mouse.move(nothing[0], nothing[1])
|
||||||
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||||
|
|
||||||
|
await page.mouse.move(close[0], close[1])
|
||||||
|
await expect(page.getByTestId('hover-highlight')).toBeVisible()
|
||||||
|
await page.mouse.move(nothing[0], nothing[1])
|
||||||
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||||
|
|
||||||
|
await page.mouse.move(flatExtrusionFace[0], flatExtrusionFace[1])
|
||||||
|
await expect(page.getByTestId('hover-highlight')).toHaveCount(5) // multiple lines
|
||||||
|
await page.mouse.move(nothing[0], nothing[1])
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
test("Various pipe expressions should and shouldn't allow edit and or extrude", async ({
|
test("Various pipe expressions should and shouldn't allow edit and or extrude", async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
@ -1350,7 +1551,7 @@ test('Deselecting line tool should mean nothing happens on click', async ({
|
|||||||
`const part001 = startSketchOn('-XZ')`
|
`const part001 = startSketchOn('-XZ')`
|
||||||
)
|
)
|
||||||
|
|
||||||
await page.waitForTimeout(300)
|
await page.waitForTimeout(600)
|
||||||
|
|
||||||
let previousCodeContent = await page.locator('.cm-content').innerText()
|
let previousCodeContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
@ -1649,14 +1850,13 @@ test('Sketch on face', async ({ page }) => {
|
|||||||
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||||
previousCodeContent = await page.locator('.cm-content').innerText()
|
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
const result = makeTemplate`const part002 = startSketchOn(part001, 'seg01')
|
||||||
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|
|> startProfileAt([-12.83, 6.7], %)
|
||||||
|> startProfileAt([-12.83, 6.7], %)
|
|> line([${[2.28, 2.35]}, -${0.07}], %)
|
||||||
|> line([${process?.env?.CI ? 2.28 : 2.28}, -${
|
|> line([-3.05, -1.47], %)
|
||||||
process?.env?.CI ? 0.07 : 0.07
|
|> close(%)`
|
||||||
}], %)
|
|
||||||
|> line([-3.05, -1.47], %)
|
await expect(page.locator('.cm-content')).toHaveText(result.regExp)
|
||||||
|> close(%)`)
|
|
||||||
|
|
||||||
// exit sketch
|
// exit sketch
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
@ -1675,15 +1875,9 @@ test('Sketch on face', async ({ page }) => {
|
|||||||
await expect(page.getByText('Confirm Extrude')).toBeVisible()
|
await expect(page.getByText('Confirm Extrude')).toBeVisible()
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
const result2 = result.genNext`
|
||||||
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|
|> extrude(${[5, 5]} + 7, %)`
|
||||||
|> startProfileAt([-12.83, 6.7], %)
|
await expect(page.locator('.cm-content')).toHaveText(result2.regExp)
|
||||||
|> line([${process?.env?.CI ? 2.28 : 2.28}, -${
|
|
||||||
process?.env?.CI ? 0.07 : 0.07
|
|
||||||
}], %)
|
|
||||||
|> line([-3.05, -1.47], %)
|
|
||||||
|> close(%)
|
|
||||||
|> extrude(5 + 7, %)`)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Can code mod a line length', async ({ page }) => {
|
test('Can code mod a line length', async ({ page }) => {
|
||||||
@ -1719,6 +1913,7 @@ test('Can code mod a line length', async ({ page }) => {
|
|||||||
const startXPx = 500
|
const startXPx = 500
|
||||||
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
|
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
|
||||||
await page.mouse.click(615, 102)
|
await page.mouse.click(615, 102)
|
||||||
|
await page.getByRole('button', { name: 'Constrain', exact: true }).click()
|
||||||
await page.getByRole('button', { name: 'length', exact: true }).click()
|
await page.getByRole('button', { name: 'length', exact: true }).click()
|
||||||
await page.getByText('Add constraining value').click()
|
await page.getByText('Add constraining value').click()
|
||||||
|
|
||||||
|
@ -507,7 +507,7 @@ test('Draft rectangles should look right', async ({ page, context }) => {
|
|||||||
`const part001 = startSketchOn('-XZ')`
|
`const part001 = startSketchOn('-XZ')`
|
||||||
)
|
)
|
||||||
|
|
||||||
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
|
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
const startXPx = 600
|
const startXPx = 600
|
||||||
@ -597,12 +597,15 @@ test.describe('Client side scene scale should match engine scale', () => {
|
|||||||
|
|
||||||
// exit sketch
|
// exit sketch
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
await u.doAndWaitForImageDiff(
|
||||||
|
() => page.getByRole('button', { name: 'Exit Sketch' }).click(),
|
||||||
|
200
|
||||||
|
)
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
await u.clearAndCloseDebugPanel()
|
await u.clearAndCloseDebugPanel()
|
||||||
await page.waitForTimeout(200)
|
await page.waitForTimeout(300)
|
||||||
|
|
||||||
// second screen shot should look almost identical, i.e. scale should be the same.
|
// second screen shot should look almost identical, i.e. scale should be the same.
|
||||||
await expect(page).toHaveScreenshot({
|
await expect(page).toHaveScreenshot({
|
||||||
@ -696,12 +699,15 @@ test.describe('Client side scene scale should match engine scale', () => {
|
|||||||
|
|
||||||
// exit sketch
|
// exit sketch
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
await u.doAndWaitForImageDiff(
|
||||||
|
() => page.getByRole('button', { name: 'Exit Sketch' }).click(),
|
||||||
|
200
|
||||||
|
)
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
await u.clearAndCloseDebugPanel()
|
await u.clearAndCloseDebugPanel()
|
||||||
await page.waitForTimeout(200)
|
await page.waitForTimeout(300)
|
||||||
|
|
||||||
// second screen shot should look almost identical, i.e. scale should be the same.
|
// second screen shot should look almost identical, i.e. scale should be the same.
|
||||||
await expect(page).toHaveScreenshot({
|
await expect(page).toHaveScreenshot({
|
||||||
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 45 KiB |
@ -22,11 +22,16 @@ export const TEST_SETTINGS = {
|
|||||||
},
|
},
|
||||||
} satisfies Partial<SaveSettingsPayload>
|
} satisfies Partial<SaveSettingsPayload>
|
||||||
|
|
||||||
export const TEST_SETTINGS_ONBOARDING = {
|
export const TEST_SETTINGS_ONBOARDING_EXPORT = {
|
||||||
...TEST_SETTINGS,
|
...TEST_SETTINGS,
|
||||||
app: { ...TEST_SETTINGS.app, onboardingStatus: '/export' },
|
app: { ...TEST_SETTINGS.app, onboardingStatus: '/export' },
|
||||||
} satisfies Partial<SaveSettingsPayload>
|
} satisfies Partial<SaveSettingsPayload>
|
||||||
|
|
||||||
|
export const TEST_SETTINGS_ONBOARDING_START = {
|
||||||
|
...TEST_SETTINGS,
|
||||||
|
app: { ...TEST_SETTINGS.app, onboardingStatus: '' },
|
||||||
|
} satisfies Partial<SaveSettingsPayload>
|
||||||
|
|
||||||
export const TEST_SETTINGS_CORRUPTED = {
|
export const TEST_SETTINGS_CORRUPTED = {
|
||||||
app: {
|
app: {
|
||||||
theme: Themes.Dark,
|
theme: Themes.Dark,
|
||||||
|
@ -182,3 +182,76 @@ export function getUtils(page: Page) {
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TemplateOptions = Array<number | Array<number>>
|
||||||
|
|
||||||
|
type makeTemplateReturn = {
|
||||||
|
regExp: RegExp
|
||||||
|
genNext: (
|
||||||
|
templateParts: TemplateStringsArray,
|
||||||
|
...options: TemplateOptions
|
||||||
|
) => makeTemplateReturn
|
||||||
|
}
|
||||||
|
|
||||||
|
const escapeRegExp = (string: string) => {
|
||||||
|
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string
|
||||||
|
}
|
||||||
|
|
||||||
|
const _makeTemplate = (
|
||||||
|
templateParts: TemplateStringsArray,
|
||||||
|
...options: TemplateOptions
|
||||||
|
) => {
|
||||||
|
const length = Math.max(...options.map((a) => (Array.isArray(a) ? a[0] : 0)))
|
||||||
|
let reExpTemplate = ''
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const currentStr = templateParts.map((str, index) => {
|
||||||
|
const currentOptions = options[index]
|
||||||
|
return (
|
||||||
|
escapeRegExp(str) +
|
||||||
|
String(
|
||||||
|
Array.isArray(currentOptions)
|
||||||
|
? currentOptions[i]
|
||||||
|
: typeof currentOptions === 'number'
|
||||||
|
? currentOptions
|
||||||
|
: ''
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
reExpTemplate += '|' + currentStr.join('')
|
||||||
|
}
|
||||||
|
return new RegExp(reExpTemplate)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tool for making templates to match code snippets in the editor with some fudge factor,
|
||||||
|
* as there's some level of non-determinism.
|
||||||
|
*
|
||||||
|
* Usage is as such:
|
||||||
|
* ```typescript
|
||||||
|
* const result = makeTemplate`const myVar = aFunc(${[1, 2, 3]})`
|
||||||
|
* await expect(page.locator('.cm-content')).toHaveText(result.regExp)
|
||||||
|
* ```
|
||||||
|
* Where the value `1`, `2` or `3` are all valid and should make the test pass.
|
||||||
|
*
|
||||||
|
* The function also has a `genNext` function that allows you to chain multiple templates
|
||||||
|
* together without having to repeat previous parts of the template.
|
||||||
|
* ```typescript
|
||||||
|
* const result2 = result.genNext`const myVar2 = aFunc(${[4, 5, 6]})`
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export const makeTemplate: (
|
||||||
|
templateParts: TemplateStringsArray,
|
||||||
|
...values: TemplateOptions
|
||||||
|
) => makeTemplateReturn = (templateParts, ...options) => {
|
||||||
|
return {
|
||||||
|
regExp: _makeTemplate(templateParts, ...options),
|
||||||
|
genNext: (
|
||||||
|
nextTemplateParts: TemplateStringsArray,
|
||||||
|
...nextOptions: TemplateOptions
|
||||||
|
) =>
|
||||||
|
makeTemplate(
|
||||||
|
[...templateParts, ...nextTemplateParts] as any as TemplateStringsArray,
|
||||||
|
[...options, ...nextOptions] as any
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { browser, $, expect } from '@wdio/globals'
|
|||||||
import fs from 'fs/promises'
|
import fs from 'fs/promises'
|
||||||
|
|
||||||
const documentsDir = `${process.env.HOME}/Documents`
|
const documentsDir = `${process.env.HOME}/Documents`
|
||||||
const userSettingsFile = `${process.env.HOME}/.config/dev.zoo.modeling-app/user.toml`
|
const userSettingsDir = `${process.env.HOME}/.config/dev.zoo.modeling-app`
|
||||||
const defaultProjectDir = `${documentsDir}/zoo-modeling-app-projects`
|
const defaultProjectDir = `${documentsDir}/zoo-modeling-app-projects`
|
||||||
const newProjectDir = `${documentsDir}/a-different-directory`
|
const newProjectDir = `${documentsDir}/a-different-directory`
|
||||||
const userCodeDir = '/tmp/kittycad_user_code'
|
const userCodeDir = '/tmp/kittycad_user_code'
|
||||||
@ -29,8 +29,10 @@ describe('ZMA (Tauri, Linux)', () => {
|
|||||||
// Clean up filesystem from previous tests
|
// Clean up filesystem from previous tests
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||||
await fs.rm(defaultProjectDir, { force: true, recursive: true })
|
await fs.rm(defaultProjectDir, { force: true, recursive: true })
|
||||||
|
await fs.rm(newProjectDir, { force: true, recursive: true })
|
||||||
await fs.rm(userCodeDir, { force: true })
|
await fs.rm(userCodeDir, { force: true })
|
||||||
await fs.rm(userSettingsFile, { force: true })
|
await fs.rm(userSettingsDir, { force: true, recursive: true })
|
||||||
|
await fs.mkdir(defaultProjectDir, { recursive: true })
|
||||||
await fs.mkdir(newProjectDir, { recursive: true })
|
await fs.mkdir(newProjectDir, { recursive: true })
|
||||||
|
|
||||||
const signInButton = await $('[data-testid="sign-in-button"]')
|
const signInButton = await $('[data-testid="sign-in-button"]')
|
||||||
@ -70,6 +72,7 @@ describe('ZMA (Tauri, Linux)', () => {
|
|||||||
console.log(cr.status)
|
console.log(cr.status)
|
||||||
|
|
||||||
// Now should be signed in
|
// Now should be signed in
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 10000))
|
||||||
const newFileButton = await $('[data-testid="home-new-file"]')
|
const newFileButton = await $('[data-testid="home-new-file"]')
|
||||||
expect(await newFileButton.getText()).toEqual('New project')
|
expect(await newFileButton.getText()).toEqual('New project')
|
||||||
})
|
})
|
||||||
@ -117,8 +120,8 @@ describe('ZMA (Tauri, Linux)', () => {
|
|||||||
it('opens the new file and expects a loading stream', async () => {
|
it('opens the new file and expects a loading stream', async () => {
|
||||||
const projectLink = await $('[data-testid="project-link"]')
|
const projectLink = await $('[data-testid="project-link"]')
|
||||||
await click(projectLink)
|
await click(projectLink)
|
||||||
const loadingText = await $('[data-testid="loading-stream"]')
|
const errorText = await $('[data-testid="unexpected-error"]')
|
||||||
expect(await loadingText.getText()).toContain('Loading stream...')
|
expect(await errorText.getText()).toContain('unexpected error')
|
||||||
await browser.execute('window.location.href = "tauri://localhost/home"')
|
await browser.execute('window.location.href = "tauri://localhost/home"')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<script
|
<script
|
||||||
defer
|
defer
|
||||||
data-domain="app.zoo.dev"
|
data-domain="app.zoo.dev"
|
||||||
src="https://plausible.corp.zoo.dev/js/script.js"
|
src="https://plausible.corp.zoo.dev/js/script.tagged-events.js"
|
||||||
></script>
|
></script>
|
||||||
<title>Zoo Modeling App</title>
|
<title>Zoo Modeling App</title>
|
||||||
</head>
|
</head>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "untitled-app",
|
"name": "untitled-app",
|
||||||
"version": "0.19.4",
|
"version": "0.21.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.16.0",
|
"@codemirror/autocomplete": "^6.16.0",
|
||||||
@ -10,7 +10,7 @@
|
|||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@headlessui/react": "^1.7.19",
|
"@headlessui/react": "^1.7.19",
|
||||||
"@headlessui/tailwindcss": "^0.2.0",
|
"@headlessui/tailwindcss": "^0.2.0",
|
||||||
"@kittycad/lib": "^0.0.58",
|
"@kittycad/lib": "^0.0.60",
|
||||||
"@lezer/javascript": "^1.4.9",
|
"@lezer/javascript": "^1.4.9",
|
||||||
"@open-rpc/client-js": "^1.8.1",
|
"@open-rpc/client-js": "^1.8.1",
|
||||||
"@react-hook/resize-observer": "^1.2.6",
|
"@react-hook/resize-observer": "^1.2.6",
|
||||||
@ -123,6 +123,7 @@
|
|||||||
"@tauri-apps/cli": "^2.0.0-beta.13",
|
"@tauri-apps/cli": "^2.0.0-beta.13",
|
||||||
"@types/crypto-js": "^4.2.2",
|
"@types/crypto-js": "^4.2.2",
|
||||||
"@types/debounce-promise": "^3.1.9",
|
"@types/debounce-promise": "^3.1.9",
|
||||||
|
"@types/mocha": "^10.0.6",
|
||||||
"@types/pixelmatch": "^5.2.6",
|
"@types/pixelmatch": "^5.2.6",
|
||||||
"@types/pngjs": "^6.0.4",
|
"@types/pngjs": "^6.0.4",
|
||||||
"@types/react-modal": "^3.16.3",
|
"@types/react-modal": "^3.16.3",
|
||||||
|
@ -27,7 +27,7 @@ export default defineConfig({
|
|||||||
baseURL: 'http://localhost:3000',
|
baseURL: 'http://localhost:3000',
|
||||||
|
|
||||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||||
trace: 'on-first-retry',
|
trace: 'retain-on-failure',
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Configure projects for major browsers */
|
/* Configure projects for major browsers */
|
||||||
|
BIN
public/onboarding-bracket-dimensions-dark.png
Normal file
After Width: | Height: | Size: 150 KiB |
BIN
public/onboarding-bracket-dimensions.png
Normal file
After Width: | Height: | Size: 152 KiB |
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 867 KiB |
444
src-tauri/Cargo.lock
generated
@ -17,6 +17,7 @@ tauri-build = { version = "2.0.0-beta.13", features = [] }
|
|||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
kcl-lib = { version = "0.1.53", path = "../src/wasm-lib/kcl" }
|
kcl-lib = { version = "0.1.53", path = "../src/wasm-lib/kcl" }
|
||||||
kittycad = "0.3.0"
|
kittycad = "0.3.0"
|
||||||
|
log = "0.4.21"
|
||||||
oauth2 = "4.4.2"
|
oauth2 = "4.4.2"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
tauri = { version = "2.0.0-beta.15", features = [ "devtools", "unstable"] }
|
tauri = { version = "2.0.0-beta.15", features = [ "devtools", "unstable"] }
|
||||||
@ -25,12 +26,14 @@ tauri-plugin-deep-link = { version = "2.0.0-beta.3" }
|
|||||||
tauri-plugin-dialog = { version = "2.0.0-beta.6" }
|
tauri-plugin-dialog = { version = "2.0.0-beta.6" }
|
||||||
tauri-plugin-fs = { version = "2.0.0-beta.6" }
|
tauri-plugin-fs = { version = "2.0.0-beta.6" }
|
||||||
tauri-plugin-http = { version = "2.0.0-beta.6" }
|
tauri-plugin-http = { version = "2.0.0-beta.6" }
|
||||||
|
tauri-plugin-log = { version = "2.0.0-beta.4" }
|
||||||
tauri-plugin-os = { version = "2.0.0-beta.2" }
|
tauri-plugin-os = { version = "2.0.0-beta.2" }
|
||||||
tauri-plugin-process = { version = "2.0.0-beta.2" }
|
tauri-plugin-process = { version = "2.0.0-beta.2" }
|
||||||
tauri-plugin-shell = { version = "2.0.0-beta.2" }
|
tauri-plugin-shell = { version = "2.0.0-beta.2" }
|
||||||
tauri-plugin-updater = { version = "2.0.0-beta.4" }
|
tauri-plugin-updater = { version = "2.0.0-beta.4" }
|
||||||
tokio = { version = "1.37.0", features = ["time", "fs", "process"] }
|
tokio = { version = "1.37.0", features = ["time", "fs", "process"] }
|
||||||
toml = "0.8.2"
|
toml = "0.8.2"
|
||||||
|
url = "2.5.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["updater"]
|
default = ["updater"]
|
||||||
|
@ -2,6 +2,24 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>NSPrincipalClass</key>
|
||||||
|
<string>NSApplication</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>NSDesktopFolderUsageDescription</key>
|
||||||
|
<string>Zoo Modeling App accesses the Desktop to load and save your project files and/or exported files here</string>
|
||||||
|
<key>NSDocumentsFolderUsageDescription</key>
|
||||||
|
<string>Zoo Modeling App accesses the Documents folder to load and save your project files and/or exported files here</string>
|
||||||
|
<key>NSDownloadsFolderUsageDescription</key>
|
||||||
|
<string>Zoo Modeling App accesses the Downloads folder to load and save your project files and/or exported files here</string>
|
||||||
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
|
<false/>
|
||||||
|
<key>DTXcode</key>
|
||||||
|
<string>1501</string>
|
||||||
|
<key>DTXcodeBuild</key>
|
||||||
|
<string>15A507</string>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
@ -14,14 +32,178 @@
|
|||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
|
<key>LSFileQuarantineEnabled</key>
|
||||||
|
<false/>
|
||||||
|
<key>CFBundleDocumentTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>dev.zoo.kcl</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>KCL</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>LSTypeIsPackage</key>
|
||||||
|
<false/>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Owner</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>dev.zoo.toml</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>TOML</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>LSTypeIsPackage</key>
|
||||||
|
<false/>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Default</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>dev.zoo.gltf</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>glTF</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>LSTypeIsPackage</key>
|
||||||
|
<false/>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Default</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>dev.zoo.glb</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>glb</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>LSTypeIsPackage</key>
|
||||||
|
<false/>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Default</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>dev.zoo.step</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>STEP</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>LSTypeIsPackage</key>
|
||||||
|
<false/>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Default</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>dev.zoo.fbx</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>FBX</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>LSTypeIsPackage</key>
|
||||||
|
<false/>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Default</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>dev.zoo.sldprt</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>Solidworks Part</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Viewer</string>
|
||||||
|
<key>LSTypeIsPackage</key>
|
||||||
|
<false/>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Default</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>public.geometry-definition-format</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>OBJ</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>LSTypeIsPackage</key>
|
||||||
|
<false/>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Default</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>public.polygon-file-format</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>PLY</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>LSTypeIsPackage</key>
|
||||||
|
<false/>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Default</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>public.standard-tesselated-geometry-format</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>STL</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>LSTypeIsPackage</key>
|
||||||
|
<false/>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Default</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>public.folder</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>Folders</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Viewer</string>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Alternate</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
<key>UTExportedTypeDeclarations</key>
|
<key>UTExportedTypeDeclarations</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
<key>UTTypeIdentifier</key>
|
<key>UTTypeIdentifier</key>
|
||||||
<string>dev.zoo.kcl</string>
|
<string>dev.zoo.kcl</string>
|
||||||
|
<key>UTTypeReferenceURL</key>
|
||||||
|
<string>https://zoo.dev/docs/kcl</string>
|
||||||
<key>UTTypeConformsTo</key>
|
<key>UTTypeConformsTo</key>
|
||||||
<array>
|
<array>
|
||||||
<string>public.source-code</string>
|
<string>public.source-code</string>
|
||||||
|
<string>public.data</string>
|
||||||
|
<string>public.text</string>
|
||||||
|
<string>public.plain-text</string>
|
||||||
|
<string>public.3d-content</string>
|
||||||
|
<string>public.script</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UTTypeDescription</key>
|
<key>UTTypeDescription</key>
|
||||||
<string>KCL (KittyCAD Language) document</string>
|
<string>KCL (KittyCAD Language) document</string>
|
||||||
@ -37,6 +219,158 @@
|
|||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>dev.zoo.gltf</string>
|
||||||
|
<key>UTTypeReferenceURL</key>
|
||||||
|
<string>https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html</string>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.data</string>
|
||||||
|
<string>public.text</string>
|
||||||
|
<string>public.plain-text</string>
|
||||||
|
<string>public.3d-content</string>
|
||||||
|
<string>public.json</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeDescription</key>
|
||||||
|
<string>Graphics Library Transmission Format (glTF)</string>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<array>
|
||||||
|
<string>gltf</string>
|
||||||
|
</array>
|
||||||
|
<key>public.mime-type</key>
|
||||||
|
<array>
|
||||||
|
<string>model/gltf+json</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>dev.zoo.glb</string>
|
||||||
|
<key>UTTypeReferenceURL</key>
|
||||||
|
<string>https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html</string>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.data</string>
|
||||||
|
<string>public.3d-content</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeDescription</key>
|
||||||
|
<string>Graphics Library Transmission Format (glTF) binary</string>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<array>
|
||||||
|
<string>glb</string>
|
||||||
|
</array>
|
||||||
|
<key>public.mime-type</key>
|
||||||
|
<array>
|
||||||
|
<string>model/gltf-binary</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>dev.zoo.step</string>
|
||||||
|
<key>UTTypeReferenceURL</key>
|
||||||
|
<string>https://www.loc.gov/preservation/digital/formats/fdd/fdd000448.shtml</string>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.data</string>
|
||||||
|
<string>public.3d-content</string>
|
||||||
|
<string>public.text</string>
|
||||||
|
<string>public.plain-text</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeDescription</key>
|
||||||
|
<string>STEP-file, ISO 10303-21</string>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<array>
|
||||||
|
<string>step</string>
|
||||||
|
<string>stp</string>
|
||||||
|
</array>
|
||||||
|
<key>public.mime-type</key>
|
||||||
|
<array>
|
||||||
|
<string>model/step</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>dev.zoo.sldprt</string>
|
||||||
|
<key>UTTypeReferenceURL</key>
|
||||||
|
<string>https://docs.fileformat.com/cad/sldprt/</string>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.data</string>
|
||||||
|
<string>public.3d-content</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeDescription</key>
|
||||||
|
<string>Solidworks Part</string>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<array>
|
||||||
|
<string>sldprt</string>
|
||||||
|
</array>
|
||||||
|
<key>public.mime-type</key>
|
||||||
|
<array>
|
||||||
|
<string>model/vnd.solidworks.sldprt</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>dev.zoo.fbx</string>
|
||||||
|
<key>UTTypeReferenceURL</key>
|
||||||
|
<string>https://en.wikipedia.org/wiki/FBX</string>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.data</string>
|
||||||
|
<string>public.3d-content</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeDescription</key>
|
||||||
|
<string>Autodesk Filmbox (FBX) format</string>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<array>
|
||||||
|
<string>fbx</string>
|
||||||
|
<string>fbxb</string>
|
||||||
|
</array>
|
||||||
|
<key>public.mime-type</key>
|
||||||
|
<array>
|
||||||
|
<string>model/vnd.autodesk.fbx</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>dev.zoo.toml</string>
|
||||||
|
<key>UTTypeReferenceURL</key>
|
||||||
|
<string>https://toml.io/en/</string>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.data</string>
|
||||||
|
<string>public.text</string>
|
||||||
|
<string>public.plain-text</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeDescription</key>
|
||||||
|
<string>Tom's Obvious Minimal Language</string>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<array>
|
||||||
|
<string>kcl</string>
|
||||||
|
</array>
|
||||||
|
<key>public.mime-type</key>
|
||||||
|
<array>
|
||||||
|
<string>text/toml</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
"permissions": [
|
"permissions": [
|
||||||
"cli:default",
|
"cli:default",
|
||||||
"deep-link:default",
|
"deep-link:default",
|
||||||
|
"log:default",
|
||||||
"path:default",
|
"path:default",
|
||||||
"event:default",
|
"event:default",
|
||||||
"window:default",
|
"window:default",
|
||||||
|