Compare commits
5 Commits
zoom-to-fi
...
franknoiro
| Author | SHA1 | Date | |
|---|---|---|---|
| e1da72a0ae | |||
| ec2d1999a7 | |||
| 95683f1cc1 | |||
| f48f1c21c1 | |||
| 5cdf2de89a |
12
.github/workflows/ci.yml
vendored
@ -89,20 +89,16 @@ jobs:
|
||||
- run: yarn build:wasm
|
||||
|
||||
- run: yarn simpleserver:ci
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
|
||||
- name: Install Chromium Browser
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
run: yarn playwright install chromium --with-deps
|
||||
|
||||
- name: run unit tests
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
run: yarn test:nowatch
|
||||
env:
|
||||
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
|
||||
- name: check for changes
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
id: git-check
|
||||
run: |
|
||||
git add src/lang/std/artifactMapGraphs
|
||||
@ -111,7 +107,7 @@ jobs:
|
||||
else echo "modified=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- name: Commit changes, if any
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' && steps.git-check.outputs.modified == 'true' }}
|
||||
if: steps.git-check.outputs.modified == 'true'
|
||||
run: |
|
||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
@ -535,7 +531,7 @@ jobs:
|
||||
project_id: kittycadapi
|
||||
|
||||
- name: Upload release files to public bucket
|
||||
uses: google-github-actions/upload-cloud-storage@v2.1.1
|
||||
uses: google-github-actions/upload-cloud-storage@v2.1.0
|
||||
with:
|
||||
path: artifact
|
||||
glob: '*/Zoo*'
|
||||
@ -543,13 +539,13 @@ jobs:
|
||||
destination: ${{ env.BUCKET_DIR }}/${{ env.VERSION }}
|
||||
|
||||
- name: Upload update endpoint to public bucket
|
||||
uses: google-github-actions/upload-cloud-storage@v2.1.1
|
||||
uses: google-github-actions/upload-cloud-storage@v2.1.0
|
||||
with:
|
||||
path: last_update.json
|
||||
destination: ${{ env.BUCKET_DIR }}
|
||||
|
||||
- name: Upload download endpoint to public bucket
|
||||
uses: google-github-actions/upload-cloud-storage@v2.1.1
|
||||
uses: google-github-actions/upload-cloud-storage@v2.1.0
|
||||
with:
|
||||
path: last_download.json
|
||||
destination: ${{ env.BUCKET_DIR }}
|
||||
|
||||
11
README.md
@ -110,17 +110,6 @@ Note that these became separate apps on Macos, so make sure you open the right o
|
||||
|
||||
<img width="1232" alt="image (1)" src="https://user-images.githubusercontent.com/29681384/211947073-e76b4933-bef5-4636-bc4d-e930ac8e290f.png">
|
||||
|
||||
## Checking out commits / Bisecting
|
||||
|
||||
Which commands from setup are one off vs need to be run every time?
|
||||
|
||||
The following will need to be run when checking out a new commit and guarantees the build is not stale:
|
||||
```bash
|
||||
yarn install
|
||||
yarn build:wasm-dev # or yarn build:wasm for slower but more production-like build
|
||||
yarn start # or yarn build:local && yarn serve for slower but more production-like build
|
||||
```
|
||||
|
||||
## Before submitting a PR
|
||||
|
||||
Before you submit a contribution PR to this repo, please ensure that:
|
||||
|
||||
@ -21,7 +21,6 @@ layout: manual
|
||||
* [`arc`](kcl/arc)
|
||||
* [`asin`](kcl/asin)
|
||||
* [`assert`](kcl/assert)
|
||||
* [`assertEqual`](kcl/assertEqual)
|
||||
* [`assertGreaterThan`](kcl/assertGreaterThan)
|
||||
* [`assertGreaterThanOrEq`](kcl/assertGreaterThanOrEq)
|
||||
* [`assertLessThan`](kcl/assertLessThan)
|
||||
|
||||
@ -54791,62 +54791,6 @@
|
||||
"const myVar = true\nassert(myVar, \"should always be true\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "assertEqual",
|
||||
"summary": "Check that a numerical value equals another at runtime,",
|
||||
"description": "otherwise raise an error.",
|
||||
"tags": [],
|
||||
"args": [
|
||||
{
|
||||
"name": "left",
|
||||
"type": "number",
|
||||
"schema": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "right",
|
||||
"type": "number",
|
||||
"schema": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "epsilon",
|
||||
"type": "number",
|
||||
"schema": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"type": "string",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"returnValue": {
|
||||
"name": "",
|
||||
"type": "()",
|
||||
"schema": {
|
||||
"type": "null"
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"unpublished": false,
|
||||
"deprecated": false,
|
||||
"examples": [
|
||||
"let n = 1.0285\nlet m = 1.0286\nassertEqual(n, m, 0.01, \"n is within the given tolerance for m\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "assertGreaterThan",
|
||||
"summary": "Check that a numerical value is greater than another at runtime,",
|
||||
@ -223491,7 +223435,7 @@
|
||||
},
|
||||
{
|
||||
"name": "xLine",
|
||||
"summary": "Draw a line parallel to the X-axis, with the given length.",
|
||||
"summary": "Draw a line on the x-axis.",
|
||||
"description": "",
|
||||
"tags": [],
|
||||
"args": [
|
||||
@ -230125,8 +230069,8 @@
|
||||
},
|
||||
{
|
||||
"name": "xLineTo",
|
||||
"summary": "Draw a line parallel to the X axis, that ends at the given X.",
|
||||
"description": "E.g. if the previous line ended at (1, 1), then xLineTo(4) draws a line from (1, 1) to (4, 1)",
|
||||
"summary": "Draw a line to a point on the x-axis.",
|
||||
"description": "",
|
||||
"tags": [],
|
||||
"args": [
|
||||
{
|
||||
@ -236759,7 +236703,7 @@
|
||||
},
|
||||
{
|
||||
"name": "yLine",
|
||||
"summary": "Draw a line parallel to the Y-axis, with the given length.",
|
||||
"summary": "Draw a line on the y-axis.",
|
||||
"description": "",
|
||||
"tags": [],
|
||||
"args": [
|
||||
@ -243393,8 +243337,8 @@
|
||||
},
|
||||
{
|
||||
"name": "yLineTo",
|
||||
"summary": "Draw a line parallel to the Y axis, that ends at the given Y.",
|
||||
"description": "E.g. if the previous line ended at (1, 1), then yLineTo(4) draws a line from (1, 1) to (1, 4)",
|
||||
"summary": "Draw a line to a point on the y-axis.",
|
||||
"description": "",
|
||||
"tags": [],
|
||||
"args": [
|
||||
{
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "xLine"
|
||||
excerpt: "Draw a line parallel to the X-axis, with the given length."
|
||||
excerpt: "Draw a line on the x-axis."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Draw a line parallel to the X-axis, with the given length.
|
||||
Draw a line on the x-axis.
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "xLineTo"
|
||||
excerpt: "Draw a line parallel to the X axis, that ends at the given X."
|
||||
excerpt: "Draw a line to a point on the x-axis."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Draw a line parallel to the X axis, that ends at the given X.
|
||||
Draw a line to a point on the x-axis.
|
||||
|
||||
|
||||
E.g. if the previous line ended at (1, 1), then xLineTo(4) draws a line from (1, 1) to (4, 1)
|
||||
|
||||
```js
|
||||
xLineTo(to: number, sketch_group: SketchGroup, tag?: TagDeclarator) -> SketchGroup
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "yLine"
|
||||
excerpt: "Draw a line parallel to the Y-axis, with the given length."
|
||||
excerpt: "Draw a line on the y-axis."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Draw a line parallel to the Y-axis, with the given length.
|
||||
Draw a line on the y-axis.
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "yLineTo"
|
||||
excerpt: "Draw a line parallel to the Y axis, that ends at the given Y."
|
||||
excerpt: "Draw a line to a point on the y-axis."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Draw a line parallel to the Y axis, that ends at the given Y.
|
||||
Draw a line to a point on the y-axis.
|
||||
|
||||
|
||||
E.g. if the previous line ended at (1, 1), then yLineTo(4) draws a line from (1, 1) to (1, 4)
|
||||
|
||||
```js
|
||||
yLineTo(to: number, sketch_group: SketchGroup, tag?: TagDeclarator) -> SketchGroup
|
||||
|
||||
@ -46,9 +46,9 @@ document.addEventListener('mousemove', (e) =>
|
||||
const deg = (Math.PI * 2) / 360
|
||||
|
||||
const commonPoints = {
|
||||
startAt: '[7.19, -9.7]',
|
||||
num1: 7.25,
|
||||
num2: 14.44,
|
||||
startAt: '[0.75, -1.01]',
|
||||
num1: 0.75,
|
||||
num2: 1.5,
|
||||
}
|
||||
|
||||
test.afterEach(async ({ context, page }, testInfo) => {
|
||||
@ -7603,7 +7603,7 @@ test.describe('Testing Gizmo', () => {
|
||||
})
|
||||
}
|
||||
|
||||
test('Context menu and popover menu', async ({ page }) => {
|
||||
test('Context menu', async ({ page }) => {
|
||||
const testCase = {
|
||||
testDescription: 'Right view',
|
||||
expectedCameraPosition: { x: 5660.02, y: -152, z: 26 },
|
||||
@ -7698,16 +7698,6 @@ test.describe('Testing Gizmo', () => {
|
||||
testCase.expectedCameraTarget.z.toString()
|
||||
),
|
||||
])
|
||||
|
||||
// Now test the popover menu.
|
||||
// It has the same click handlers, so we can just
|
||||
// test that it opens and contains the same content.
|
||||
const gizmoPopoverButton = page.getByRole('button', {
|
||||
name: 'view settings',
|
||||
})
|
||||
await expect(gizmoPopoverButton).toBeVisible()
|
||||
await gizmoPopoverButton.click()
|
||||
await expect(buttonToTest).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 29 KiB |
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "untitled-app",
|
||||
"version": "0.24.9",
|
||||
"version": "0.24.8",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.17.0",
|
||||
|
||||
@ -80,5 +80,5 @@
|
||||
}
|
||||
},
|
||||
"productName": "Zoo Modeling App",
|
||||
"version": "0.24.9"
|
||||
"version": "0.24.8"
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { SceneInfra } from 'clientSideScene/sceneInfra'
|
||||
import { sceneInfra } from 'lib/singletons'
|
||||
import { MutableRefObject, useEffect, useMemo, useRef } from 'react'
|
||||
import { MutableRefObject, useEffect, useRef } from 'react'
|
||||
import {
|
||||
WebGLRenderer,
|
||||
Scene,
|
||||
@ -25,8 +25,6 @@ import {
|
||||
ContextMenuItem,
|
||||
ContextMenuItemRefresh,
|
||||
} from './ContextMenu'
|
||||
import { Popover } from '@headlessui/react'
|
||||
import { CustomIcon } from './CustomIcon'
|
||||
|
||||
const CANVAS_SIZE = 80
|
||||
const FRUSTUM_SIZE = 0.5
|
||||
@ -61,30 +59,6 @@ export default function Gizmo() {
|
||||
const raycasterIntersect = useRef<Intersection<Object3D> | null>(null)
|
||||
const cameraPassiveUpdateTimer = useRef(0)
|
||||
const raycasterPassiveUpdateTimer = useRef(0)
|
||||
const menuItems = useMemo(
|
||||
() => [
|
||||
...Object.entries(axisNamesSemantic).map(([axisName, axisSemantic]) => (
|
||||
<ContextMenuItem
|
||||
key={axisName}
|
||||
onClick={() => {
|
||||
sceneInfra.camControls.updateCameraToAxis(axisName as AxisNames)
|
||||
}}
|
||||
>
|
||||
{axisSemantic} view
|
||||
</ContextMenuItem>
|
||||
)),
|
||||
<ContextMenuItem
|
||||
onClick={() => {
|
||||
sceneInfra.camControls.resetCameraPosition()
|
||||
}}
|
||||
>
|
||||
Reset view
|
||||
</ContextMenuItem>,
|
||||
<ContextMenuDivider />,
|
||||
<ContextMenuItemRefresh />,
|
||||
],
|
||||
[axisNamesSemantic]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!canvasRef.current) return
|
||||
@ -141,48 +115,43 @@ export default function Gizmo() {
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<>
|
||||
<div
|
||||
ref={wrapperRef}
|
||||
aria-label="View orientation gizmo"
|
||||
className="grid place-content-center rounded-full overflow-hidden border border-solid border-primary/50 pointer-events-auto bg-chalkboard-10/70 dark:bg-chalkboard-100/80 backdrop-blur-sm"
|
||||
>
|
||||
<canvas ref={canvasRef} />
|
||||
<ContextMenu menuTargetElement={wrapperRef} items={menuItems} />
|
||||
<ContextMenu
|
||||
menuTargetElement={wrapperRef}
|
||||
items={[
|
||||
...Object.entries(axisNamesSemantic).map(
|
||||
([axisName, axisSemantic]) => (
|
||||
<ContextMenuItem
|
||||
key={axisName}
|
||||
onClick={() => {
|
||||
sceneInfra.camControls.updateCameraToAxis(
|
||||
axisName as AxisNames
|
||||
)
|
||||
}}
|
||||
>
|
||||
{axisSemantic} view
|
||||
</ContextMenuItem>
|
||||
)
|
||||
),
|
||||
<ContextMenuItem
|
||||
onClick={() => {
|
||||
sceneInfra.camControls.resetCameraPosition()
|
||||
}}
|
||||
>
|
||||
Reset view
|
||||
</ContextMenuItem>,
|
||||
<ContextMenuDivider />,
|
||||
<ContextMenuItemRefresh />,
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<GizmoDropdown items={menuItems} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function GizmoDropdown({ items }: { items: React.ReactNode[] }) {
|
||||
return (
|
||||
<Popover className="absolute top-0 right-0 pointer-events-auto">
|
||||
{({ close }) => (
|
||||
<>
|
||||
<Popover.Button className="border-none p-0 m-0 -translate-y-1/4 translate-x-1/4">
|
||||
<CustomIcon
|
||||
name="caretDown"
|
||||
className="w-4 h-4 ui-open:rotate-180"
|
||||
/>
|
||||
<span className="sr-only">View settings</span>
|
||||
</Popover.Button>
|
||||
<Popover.Panel
|
||||
className={`absolute bottom-full right-0 mb-2 w-48 bg-chalkboard-10 dark:bg-chalkboard-90
|
||||
border border-solid border-chalkboard-10 dark:border-chalkboard-90 rounded
|
||||
shadow-lg`}
|
||||
>
|
||||
<ul className="relative flex flex-col items-stretch content-stretch p-0.5">
|
||||
{items.map((item, index) => (
|
||||
<li key={index} className="contents" onClick={() => close()}>
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Popover.Panel>
|
||||
</>
|
||||
)}
|
||||
</Popover>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -295,7 +295,7 @@ function ModelingPaneButton({
|
||||
<p
|
||||
id={`${paneConfig.id}-badge`}
|
||||
className={
|
||||
'absolute m-0 p-0 top-1 right-0 w-3 h-3 flex items-center justify-center text-[10px] font-semibold text-white bg-primary hue-rotate-90 rounded-full border border-chalkboard-10 dark:border-chalkboard-80 z-50 hover:cursor-pointer hover:scale-[2] transition-transform duration-200'
|
||||
'absolute m-0 p-0 top-1 right-0 w-3 h-3 flex items-center justify-center text-[10px] font-semibold text-white bg-primary hue-rotate-90 rounded-full border border-chalkboard-10 dark:border-chalkboard-80 z-50 hover:cursor-pointer'
|
||||
}
|
||||
onClick={showBadge.onClick}
|
||||
title={`Click to view ${showBadge.value} notification${
|
||||
|
||||
@ -1,63 +0,0 @@
|
||||
import { Project } from 'wasm-lib/kcl/bindings/Project'
|
||||
import { CustomIcon } from './CustomIcon'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import Fuse from 'fuse.js'
|
||||
|
||||
export function useProjectSearch(projects: Project[]) {
|
||||
const [query, setQuery] = useState('')
|
||||
const [searchResults, setSearchResults] = useState(projects)
|
||||
|
||||
const fuse = new Fuse(projects, {
|
||||
keys: [{ name: 'name', weight: 0.7 }],
|
||||
includeScore: true,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const results = fuse.search(query).map((result) => result.item)
|
||||
setSearchResults(query.length > 0 ? results : projects)
|
||||
}, [query, projects])
|
||||
|
||||
return {
|
||||
searchResults,
|
||||
query,
|
||||
setQuery,
|
||||
}
|
||||
}
|
||||
|
||||
export function ProjectSearchBar({
|
||||
setQuery,
|
||||
}: {
|
||||
setQuery: (query: string) => void
|
||||
}) {
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
useHotkeys(
|
||||
'Ctrl+.',
|
||||
(event) => {
|
||||
event.preventDefault()
|
||||
inputRef.current?.focus()
|
||||
},
|
||||
{ enableOnFormTags: true }
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="relative group">
|
||||
<div className="flex items-center gap-2 py-0.5 pl-0.5 pr-2 rounded border-solid border border-primary/10 dark:border-chalkboard-80 focus-within:border-primary dark:focus-within:border-chalkboard-30">
|
||||
<CustomIcon
|
||||
name="search"
|
||||
className="w-5 h-5 rounded-sm bg-primary/10 dark:bg-transparent text-primary dark:text-chalkboard-10 group-focus-within:bg-primary group-focus-within:text-chalkboard-10"
|
||||
/>
|
||||
<input
|
||||
ref={inputRef}
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
className="w-full text-sm bg-transparent focus:outline-none selection:bg-primary/20 dark:selection:bg-primary/40 dark:focus:outline-none"
|
||||
placeholder="Search projects (^.)"
|
||||
autoCapitalize="off"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
spellCheck="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -791,7 +791,6 @@ export function isSingleCursorInPipe(
|
||||
const pathToNode = getNodePathFromSourceRange(ast, selection.range)
|
||||
const nodeTypes = pathToNode.map(([, type]) => type)
|
||||
if (nodeTypes.includes('FunctionExpression')) return false
|
||||
if (!nodeTypes.includes('VariableDeclaration')) return false
|
||||
if (nodeTypes.includes('PipeExpression')) return true
|
||||
return false
|
||||
}
|
||||
|
||||
@ -465,45 +465,30 @@ async function GraphTheGraph(
|
||||
|
||||
await browser.close()
|
||||
|
||||
const originalImgPath = path.resolve(
|
||||
`./src/lang/std/artifactMapGraphs/${imageName}`
|
||||
)
|
||||
// chop the top 30 pixels off the image
|
||||
const originalImg = PNG.sync.read(fs.readFileSync(originalImgPath))
|
||||
// const img1Data = new Uint8Array(img1.data)
|
||||
// const img1DataChopped = img1Data.slice(30 * img1.width * 4)
|
||||
// img1.data = Buffer.from(img1DataChopped)
|
||||
const img1Path = path.resolve(`./src/lang/std/artifactMapGraphs/${imageName}`)
|
||||
const img2Path = path.resolve('./e2e/playwright/temp3.png')
|
||||
|
||||
const newImagePath = path.resolve('./e2e/playwright/temp3.png')
|
||||
const newImage = PNG.sync.read(fs.readFileSync(newImagePath))
|
||||
const newImageData = new Uint8Array(newImage.data)
|
||||
const newImageDataChopped = newImageData.slice(30 * newImage.width * 4)
|
||||
newImage.data = Buffer.from(newImageDataChopped)
|
||||
const img1 = PNG.sync.read(fs.readFileSync(img1Path))
|
||||
const img2 = PNG.sync.read(fs.readFileSync(img2Path))
|
||||
|
||||
const { width, height } = originalImg
|
||||
const { width, height } = img1
|
||||
const diff = new PNG({ width, height })
|
||||
|
||||
const imageSizeDifferent = originalImg.data.length !== newImage.data.length
|
||||
let numDiffPixels = 0
|
||||
if (!imageSizeDifferent) {
|
||||
numDiffPixels = pixelmatch(
|
||||
originalImg.data,
|
||||
newImage.data,
|
||||
diff.data,
|
||||
width,
|
||||
height,
|
||||
{
|
||||
threshold: 0.1,
|
||||
}
|
||||
)
|
||||
}
|
||||
const numDiffPixels = pixelmatch(
|
||||
img1.data,
|
||||
img2.data,
|
||||
diff.data,
|
||||
width,
|
||||
height,
|
||||
{ threshold: 0.1 }
|
||||
)
|
||||
|
||||
if (numDiffPixels > 10 || imageSizeDifferent) {
|
||||
if (numDiffPixels > 10) {
|
||||
console.warn('numDiffPixels', numDiffPixels)
|
||||
// write file out to final place
|
||||
fs.writeFileSync(
|
||||
`src/lang/std/artifactMapGraphs/${imageName}`,
|
||||
PNG.sync.write(newImage)
|
||||
PNG.sync.write(img2)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 180 KiB After Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 371 KiB After Width: | Height: | Size: 374 KiB |
@ -358,7 +358,7 @@ export const modelingMachine = createMachine(
|
||||
'Artifact graph populated': 'showPlanes',
|
||||
},
|
||||
|
||||
entry: ['hide default planes', 'zoom to fit'],
|
||||
entry: 'hide default planes',
|
||||
},
|
||||
|
||||
showPlanes: {
|
||||
@ -1063,27 +1063,8 @@ export const modelingMachine = createMachine(
|
||||
sketchEnginePathId: '',
|
||||
sketchPlaneId: '',
|
||||
}),
|
||||
'zoom to fit': () =>
|
||||
engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'zoom_to_fit',
|
||||
padding: 0.1,
|
||||
object_ids: [],
|
||||
},
|
||||
}),
|
||||
'reset camera position': () =>
|
||||
engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_look_at',
|
||||
center: { x: 0, y: 0, z: 0 },
|
||||
vantage: { x: 0, y: -1250, z: 580 },
|
||||
up: { x: 0, y: 0, z: 1 },
|
||||
},
|
||||
}),
|
||||
sceneInfra.camControls.resetCameraPosition(),
|
||||
'set new sketch metadata': assign((_, { data }) => ({
|
||||
sketchDetails: data,
|
||||
})),
|
||||
|
||||
@ -39,7 +39,6 @@ import {
|
||||
listProjects,
|
||||
renameProjectDirectory,
|
||||
} from 'lib/tauri'
|
||||
import { ProjectSearchBar, useProjectSearch } from 'components/ProjectSearchBar'
|
||||
|
||||
// This route only opens in the Tauri desktop context for now,
|
||||
// as defined in Router.tsx, so we can use the Tauri APIs and types.
|
||||
@ -155,7 +154,6 @@ const Home = () => {
|
||||
})
|
||||
const { projects } = state.context
|
||||
const [searchParams, setSearchParams] = useSearchParams()
|
||||
const { searchResults, query, setQuery } = useProjectSearch(projects)
|
||||
const sort = searchParams.get('sort_by') ?? 'modified:desc'
|
||||
|
||||
const isSortByModified = sort?.includes('modified') || !sort || sort === null
|
||||
@ -208,8 +206,8 @@ const Home = () => {
|
||||
<AppHeader showToolbar={false} />
|
||||
<div className="w-full flex flex-col overflow-hidden max-w-5xl px-4 mx-auto mt-24 lg:px-2">
|
||||
<section>
|
||||
<div className="flex justify-between items-center select-none">
|
||||
<div className="flex gap-8 items-center">
|
||||
<div className="flex justify-between items-baseline select-none">
|
||||
<div className="flex gap-8 items-baseline">
|
||||
<h1 className="text-3xl font-bold">Your Projects</h1>
|
||||
<ActionButton
|
||||
Element="button"
|
||||
@ -227,7 +225,6 @@ const Home = () => {
|
||||
</ActionButton>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<ProjectSearchBar setQuery={setQuery} />
|
||||
<small>Sort by</small>
|
||||
<ActionButton
|
||||
Element="button"
|
||||
@ -292,9 +289,9 @@ const Home = () => {
|
||||
<Loading>Loading your Projects...</Loading>
|
||||
) : (
|
||||
<>
|
||||
{searchResults.length > 0 ? (
|
||||
{projects.length > 0 ? (
|
||||
<ul className="grid w-full grid-cols-4 gap-4">
|
||||
{searchResults.sort(getSortFunction(sort)).map((project) => (
|
||||
{projects.sort(getSortFunction(sort)).map((project) => (
|
||||
<ProjectCard
|
||||
key={project.name}
|
||||
project={project}
|
||||
@ -305,10 +302,7 @@ const Home = () => {
|
||||
</ul>
|
||||
) : (
|
||||
<p className="p-4 my-8 border border-dashed rounded border-chalkboard-30 dark:border-chalkboard-70">
|
||||
No Projects found
|
||||
{projects.length === 0
|
||||
? ', ready to make your first one?'
|
||||
: ` with the search term "${query}"`}
|
||||
No Projects found, ready to make your first one?
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
|
||||
@ -2862,7 +2862,7 @@ impl MemberExpression {
|
||||
// Actually evaluate memory to compute the property.
|
||||
let prop = memory.get(&name, property_src)?;
|
||||
let MemoryItem::UserVal(prop) = prop else {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
return Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: property_sr,
|
||||
message: format!(
|
||||
"{name} is not a valid property/index, you can only use a string or int (>= 0) here",
|
||||
@ -2876,17 +2876,17 @@ impl MemberExpression {
|
||||
.and_then(|x| usize::try_from(x).ok())
|
||||
.map(Property::Number)
|
||||
.ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: property_sr,
|
||||
message: format!(
|
||||
"{name}'s value is not a valid property/index, you can only use a string or int (>= 0) here",
|
||||
"{name} is not a valid property/index, you can only use a string or int (>= 0) here",
|
||||
),
|
||||
})
|
||||
})?
|
||||
}
|
||||
JValue::String(ref x) => Property::String(x.to_owned()),
|
||||
_ => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
return Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: property_sr,
|
||||
message: format!(
|
||||
"{name} is not a valid property/index, you can only use a string to get the property of an object, or an int (>= 0) to get an item in an array",
|
||||
@ -2903,7 +2903,7 @@ impl MemberExpression {
|
||||
if let Ok(x) = u64::try_from(x) {
|
||||
Property::Number(x.try_into().unwrap())
|
||||
} else {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
return Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: property_sr,
|
||||
message: format!("{x} is not a valid index, indices must be whole numbers >= 0"),
|
||||
}));
|
||||
@ -2911,7 +2911,7 @@ impl MemberExpression {
|
||||
}
|
||||
LiteralValue::String(s) => Property::String(s),
|
||||
_ => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
return Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![self.into()],
|
||||
message: "Only strings or ints (>= 0) can be properties/indexes".to_owned(),
|
||||
}));
|
||||
@ -2943,7 +2943,7 @@ impl MemberExpression {
|
||||
}))
|
||||
} else {
|
||||
Err(KclError::UndefinedValue(KclErrorDetails {
|
||||
message: format!("Property '{property}' not found in object"),
|
||||
message: format!("Property {property} not found in object"),
|
||||
source_ranges: vec![self.clone().into()],
|
||||
}))
|
||||
}
|
||||
@ -2978,13 +2978,10 @@ impl MemberExpression {
|
||||
),
|
||||
source_ranges: vec![self.clone().into()],
|
||||
})),
|
||||
(being_indexed, _) => {
|
||||
let t = human_friendly_type(being_indexed);
|
||||
Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!("Only arrays and objects can be indexed, but you're trying to index a {t}"),
|
||||
source_ranges: vec![self.clone().into()],
|
||||
}))
|
||||
}
|
||||
(_, _) => Err(KclError::Semantic(KclErrorDetails {
|
||||
message: "Only arrays and objects can be indexed".to_owned(),
|
||||
source_ranges: vec![self.clone().into()],
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
@ -4073,17 +4070,6 @@ impl ConstraintLevels {
|
||||
}
|
||||
}
|
||||
|
||||
fn human_friendly_type(j: JValue) -> &'static str {
|
||||
match j {
|
||||
JValue::Null => "null",
|
||||
JValue::Bool(_) => "boolean (true/false value)",
|
||||
JValue::Number(_) => "number",
|
||||
JValue::String(_) => "string (text)",
|
||||
JValue::Array(_) => "array (list)",
|
||||
JValue::Object(_) => "object",
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
@ -67,27 +67,6 @@ pub async fn assert_gt(args: Args) -> Result<MemoryItem, KclError> {
|
||||
args.make_null_user_val()
|
||||
}
|
||||
|
||||
/// Check that a numerical value equals another at runtime,
|
||||
/// otherwise raise an error.
|
||||
///
|
||||
/// ```no_run
|
||||
/// let n = 1.0285
|
||||
/// let m = 1.0286
|
||||
/// assertEqual(n, m, 0.01, "n is within the given tolerance for m")
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "assertEqual",
|
||||
}]
|
||||
async fn inner_assert_equal(left: f64, right: f64, epsilon: f64, message: &str, args: &Args) -> Result<(), KclError> {
|
||||
_assert((right - left).abs() < epsilon, message, args).await
|
||||
}
|
||||
|
||||
pub async fn assert_equal(args: Args) -> Result<MemoryItem, KclError> {
|
||||
let (left, right, epsilon, description): (f64, f64, f64, String) = args.get_data()?;
|
||||
inner_assert_equal(left, right, epsilon, &description, &args).await?;
|
||||
args.make_null_user_val()
|
||||
}
|
||||
|
||||
/// Check that a numerical value is greater than another at runtime,
|
||||
/// otherwise raise an error.
|
||||
///
|
||||
|
||||
@ -120,7 +120,6 @@ lazy_static! {
|
||||
Box::new(crate::std::math::ToRadians),
|
||||
Box::new(crate::std::polar::Polar),
|
||||
Box::new(crate::std::assert::Assert),
|
||||
Box::new(crate::std::assert::AssertEqual),
|
||||
Box::new(crate::std::assert::AssertLessThan),
|
||||
Box::new(crate::std::assert::AssertGreaterThan),
|
||||
Box::new(crate::std::assert::AssertLessThanOrEq),
|
||||
|
||||
@ -168,9 +168,7 @@ pub async fn x_line_to(args: Args) -> Result<MemoryItem, KclError> {
|
||||
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
||||
}
|
||||
|
||||
/// Draw a line parallel to the X axis, that ends at the given X.
|
||||
/// E.g. if the previous line ended at (1, 1),
|
||||
/// then xLineTo(4) draws a line from (1, 1) to (4, 1)
|
||||
/// Draw a line to a point on the x-axis.
|
||||
///
|
||||
/// ```no_run
|
||||
/// const exampleSketch = startSketchOn('XZ')
|
||||
@ -216,9 +214,7 @@ pub async fn y_line_to(args: Args) -> Result<MemoryItem, KclError> {
|
||||
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
||||
}
|
||||
|
||||
/// Draw a line parallel to the Y axis, that ends at the given Y.
|
||||
/// E.g. if the previous line ended at (1, 1),
|
||||
/// then yLineTo(4) draws a line from (1, 1) to (1, 4)
|
||||
/// Draw a line to a point on the y-axis.
|
||||
///
|
||||
/// ```no_run
|
||||
/// const exampleSketch = startSketchOn("XZ")
|
||||
@ -340,7 +336,7 @@ pub async fn x_line(args: Args) -> Result<MemoryItem, KclError> {
|
||||
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
||||
}
|
||||
|
||||
/// Draw a line parallel to the X-axis, with the given length.
|
||||
/// Draw a line on the x-axis.
|
||||
///
|
||||
/// ```no_run
|
||||
/// const exampleSketch = startSketchOn('XZ')
|
||||
@ -382,7 +378,7 @@ pub async fn y_line(args: Args) -> Result<MemoryItem, KclError> {
|
||||
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
||||
}
|
||||
|
||||
/// Draw a line parallel to the Y-axis, with the given length.
|
||||
/// Draw a line on the y-axis.
|
||||
///
|
||||
/// ```no_run
|
||||
/// const exampleSketch = startSketchOn('XZ')
|
||||
|
||||
|
Before Width: | Height: | Size: 70 KiB |
@ -4,7 +4,8 @@ const arr = [0, 0, 0, 10]
|
||||
const i = 3
|
||||
const ten = arr[i]
|
||||
|
||||
assertEqual(ten, 10, 0.000001, "oops")
|
||||
assertLessThanOrEq(ten, 10, "oops")
|
||||
assertGreaterThanOrEq(ten, 10, "oops2")
|
||||
|
||||
const p = "foo"
|
||||
const obj = {
|
||||
@ -13,4 +14,5 @@ const obj = {
|
||||
}
|
||||
const one = obj[p]
|
||||
|
||||
assertEqual(one, 1, 0.0000001, "oops")
|
||||
assertLessThanOrEq(one, 1, "oops")
|
||||
assertGreaterThanOrEq(one, 1, "oops2")
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
let arr = []
|
||||
let x = arr[0]
|
||||
@ -1,18 +0,0 @@
|
||||
// This tests indexing an array.
|
||||
|
||||
const array = [90, 91, 92]
|
||||
|
||||
// Test: literal index.
|
||||
|
||||
const result0 = array[1]
|
||||
|
||||
assertLessThanOrEq(result0, 91, "Literal property lookup")
|
||||
assertGreaterThanOrEq(result0, 91, "Literal property lookup")
|
||||
|
||||
// Test: computed index.
|
||||
|
||||
const i = int(1 + 0)
|
||||
const result1 = array[i]
|
||||
|
||||
assertLessThanOrEq(result1, 91, "Computed property lookup")
|
||||
assertGreaterThanOrEq(result1, 91, "Computed property lookup")
|
||||
@ -1,2 +0,0 @@
|
||||
let arr = [1, 2, 3]
|
||||
let x = arr[1.2]
|
||||
@ -1,3 +0,0 @@
|
||||
let arr = [1, 2, 3]
|
||||
let i = -1
|
||||
let x = arr[i]
|
||||
@ -1,2 +0,0 @@
|
||||
let arr = [1, 2, 3]
|
||||
let x = arr["s"]
|
||||
@ -1,2 +0,0 @@
|
||||
let num = 999
|
||||
let x = num[3]
|
||||
@ -1,2 +0,0 @@
|
||||
let b = true
|
||||
let x = b["property"]
|
||||
@ -1,2 +0,0 @@
|
||||
let obj = {key: 123}
|
||||
let num = obj[3]
|
||||
@ -1,2 +0,0 @@
|
||||
let obj = {}
|
||||
let k = obj["age"]
|
||||
@ -1,40 +0,0 @@
|
||||
// This tests evaluating properties of objects.
|
||||
|
||||
const obj = {
|
||||
foo: 1,
|
||||
bar: 0,
|
||||
}
|
||||
|
||||
// Test: the property is a literal.
|
||||
|
||||
const one_a = obj["foo"]
|
||||
|
||||
assertLessThanOrEq(one_a, 1, "Literal property lookup")
|
||||
assertGreaterThanOrEq(one_a, 1, "Literal property lookup")
|
||||
|
||||
// Test: the property is a variable,
|
||||
// which must be evaluated before looking it up.
|
||||
|
||||
const p = "foo"
|
||||
const one_b = obj[p]
|
||||
|
||||
assertLessThanOrEq(one_b, 1, "Computed property lookup")
|
||||
assertGreaterThanOrEq(one_b, 1, "Computed property lookup")
|
||||
|
||||
// Test: multiple literal properties.
|
||||
|
||||
const obj2 = {
|
||||
inner: obj,
|
||||
}
|
||||
|
||||
const one_c = obj2.inner["foo"]
|
||||
|
||||
assertLessThanOrEq(one_c, 1, "Literal property lookup")
|
||||
assertGreaterThanOrEq(one_c, 1, "Literal property lookup")
|
||||
|
||||
// Test: multiple properties, mix of literal and computed.
|
||||
|
||||
const one_d = obj2.inner[p]
|
||||
|
||||
assertLessThanOrEq(one_d, 1, "Computed property lookup")
|
||||
assertGreaterThanOrEq(one_d, 1, "Computed property lookup")
|
||||
@ -4,7 +4,7 @@ use kcl_lib::{settings::types::UnitLength, test_server::execute_and_snapshot};
|
||||
/// i.e. how different the current model snapshot can be from the previous saved one.
|
||||
const MIN_DIFF: f64 = 0.99;
|
||||
|
||||
mod no_visuals;
|
||||
// mod server;
|
||||
|
||||
macro_rules! kcl_input {
|
||||
($file:literal) => {
|
||||
|
||||
@ -1,87 +0,0 @@
|
||||
use kcl_lib::{ast::types::Program, errors::KclError, executor::ExecutorContext};
|
||||
|
||||
macro_rules! gen_test {
|
||||
($file:ident) => {
|
||||
#[tokio::test]
|
||||
async fn $file() {
|
||||
let code = include_str!(concat!("inputs/no_visuals/", stringify!($file), ".kcl"));
|
||||
run(&code).await;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! gen_test_fail {
|
||||
($file:ident, $expected:literal) => {
|
||||
#[tokio::test]
|
||||
async fn $file() {
|
||||
let code = include_str!(concat!("inputs/no_visuals/", stringify!($file), ".kcl"));
|
||||
let actual = run_fail(&code).await;
|
||||
assert_eq!(actual.get_message(), $expected);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async fn run(code: &str) {
|
||||
let (ctx, program) = setup(code).await;
|
||||
|
||||
ctx.run(&program, None).await.unwrap();
|
||||
}
|
||||
|
||||
async fn setup(program: &str) -> (ExecutorContext, Program) {
|
||||
let tokens = kcl_lib::token::lexer(program).unwrap();
|
||||
let parser = kcl_lib::parser::Parser::new(tokens);
|
||||
let program = parser.ast().unwrap();
|
||||
let ctx = kcl_lib::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
kcl_lib::engine::conn_mock::EngineConnection::new().await.unwrap(),
|
||||
)),
|
||||
fs: std::sync::Arc::new(kcl_lib::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(kcl_lib::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
is_mock: true,
|
||||
};
|
||||
(ctx, program)
|
||||
}
|
||||
|
||||
async fn run_fail(code: &str) -> KclError {
|
||||
let (ctx, program) = setup(code).await;
|
||||
let Err(e) = ctx.run(&program, None).await else {
|
||||
panic!("Expected this KCL program to fail, but it (incorrectly) never threw an error.");
|
||||
};
|
||||
e
|
||||
}
|
||||
|
||||
gen_test!(property_of_object);
|
||||
gen_test!(index_of_array);
|
||||
gen_test_fail!(
|
||||
invalid_index_str,
|
||||
"semantic: Only integers >= 0 can be used as the index of an array, but you're using a string"
|
||||
);
|
||||
gen_test_fail!(
|
||||
invalid_index_negative,
|
||||
"semantic: i's value is not a valid property/index, you can only use a string or int (>= 0) here"
|
||||
);
|
||||
gen_test_fail!(
|
||||
invalid_index_fractional,
|
||||
"semantic: Only strings or ints (>= 0) can be properties/indexes"
|
||||
);
|
||||
gen_test_fail!(
|
||||
invalid_member_object,
|
||||
"semantic: Only arrays and objects can be indexed, but you're trying to index a number"
|
||||
);
|
||||
gen_test_fail!(
|
||||
invalid_member_object_prop,
|
||||
"semantic: Only arrays and objects can be indexed, but you're trying to index a boolean (true/false value)"
|
||||
);
|
||||
gen_test_fail!(
|
||||
non_string_key_of_object,
|
||||
"semantic: Only strings can be used as the property of an object, but you're using a number"
|
||||
);
|
||||
gen_test_fail!(
|
||||
array_index_oob,
|
||||
"undefined value: The array doesn't have any item at index 0"
|
||||
);
|
||||
gen_test_fail!(
|
||||
object_prop_not_found,
|
||||
"undefined value: Property 'age' not found in object"
|
||||
);
|
||||