Compare commits

..

2 Commits

51 changed files with 2835 additions and 5661 deletions

View File

@ -1,6 +1,5 @@
VITE_KC_API_WS_MODELING_URL=wss://api.dev.zoo.dev/ws/modeling/commands
VITE_KC_API_BASE_URL=https://api.dev.zoo.dev
VITE_KC_SITE_BASE_URL=https://dev.zoo.dev
VITE_KC_WASM_OVERRIDE_URL=""
VITE_KC_SKIP_AUTH=false
VITE_KC_CONNECTION_TIMEOUT_MS=5000

View File

@ -1,6 +1,5 @@
VITE_KC_API_WS_MODELING_URL=wss://api.zoo.dev/ws/modeling/commands
VITE_KC_API_BASE_URL=https://api.zoo.dev
VITE_KC_SITE_BASE_URL=https://zoo.dev
VITE_KC_WASM_OVERRIDE_URL=""
VITE_KC_SKIP_AUTH=false
VITE_KC_CONNECTION_TIMEOUT_MS=15000

View File

@ -14,31 +14,9 @@ permissions:
pull-requests: write
jobs:
check-wasm-lib-changes:
runs-on: ubuntu-latest
outputs:
url: ${{ steps.set-output.outputs.url }}
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0 # Fetches all history for all branches and tags
- name: Check for changes in src/wasm-lib
id: set-output
run: |
if git diff --quiet origin/main...HEAD -- src/wasm-lib; then
echo "url=https://app.zoo.dev" >> $GITHUB_OUTPUT
echo "No changes detected in src/wasm-lib"
else
echo "Changes detected in src/wasm-lib"
echo "url=" >> $GITHUB_OUTPUT
fi
playwright-ubuntu:
timeout-minutes: 60
runs-on: ubuntu-latest-8-cores
needs: check-wasm-lib-changes
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
@ -50,19 +28,13 @@ jobs:
run: yarn
- name: Install Playwright Browsers
run: yarn playwright install --with-deps
- name: Print WASM Lib Changes URL
run: |
echo "WASM Lib Changes URL: ${{ needs.check-wasm-lib-changes.outputs.url }}"
- name: Setup Rust
if: ${{ needs.check-wasm-lib-changes.outputs.url }} == ''
uses: dtolnay/rust-toolchain@stable
- name: Cache wasm
if: ${{ needs.check-wasm-lib-changes.outputs.url }} == ''
uses: Swatinem/rust-cache@v2
with:
workspaces: './src/wasm-lib'
- name: build wasm
if: ${{ needs.check-wasm-lib-changes.outputs.url }} == ''
run: yarn build:wasm
- name: build web
run: yarn build:local
@ -72,7 +44,6 @@ jobs:
CI: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
snapshottoken: ${{ secrets.KITTYCAD_API_TOKEN }}
WASM_OVERRIDE: ${{ steps.check-wasm-lib-changes.outputs.url }}
- uses: actions/upload-artifact@v3
if: always()
with:
@ -108,7 +79,6 @@ jobs:
env:
CI: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
WASM_OVERRIDE: ${{ steps.check-wasm-lib-changes.outputs.url }}
- uses: actions/upload-artifact@v3
if: always()
with:
@ -119,7 +89,6 @@ jobs:
playwright-macos:
timeout-minutes: 60
runs-on: macos-14
needs: check-wasm-lib-changes
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
@ -131,15 +100,12 @@ jobs:
- name: Install Playwright Browsers
run: yarn playwright install --with-deps
- name: Setup Rust
if: needs.check-wasm-lib-changes.outputs.url == ''
uses: dtolnay/rust-toolchain@stable
- name: Cache wasm
if: needs.check-wasm-lib-changes.outputs.url == ''
uses: Swatinem/rust-cache@v2
with:
workspaces: './src/wasm-lib'
- name: build wasm
if: needs.check-wasm-lib-changes.outputs.url == ''
run: yarn build:wasm
- name: build web
run: yarn build:local
@ -150,7 +116,6 @@ jobs:
env:
CI: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
WASM_OVERRIDE: ${{ steps.check-wasm-lib-changes.outputs.url }}
- uses: actions/upload-artifact@v3
if: always()
with:

View File

@ -9,7 +9,7 @@ Extrudes by a given amount.
```js
extrude(length: number, sketch_group_set: SketchGroupSet) -> ExtrudeGroupSet
extrude(length: number, sketch_group: SketchGroup) -> ExtrudeGroup
```
### Examples
@ -29,7 +29,7 @@ startSketchOn('XY')
### Arguments
* `length`: `number` (REQUIRED)
* `sketch_group_set`: `SketchGroupSet` - A sketch group or a group of sketch groups. (REQUIRED)
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths. (REQUIRED)
```js
{
// The plane id or face id of the sketch group.
@ -110,7 +110,6 @@ startSketchOn('XY')
// The to point.
to: [number, number],
},
type: "sketchGroup",
// The paths in the sketch group.
value: [{
// The from point.
@ -194,15 +193,12 @@ startSketchOn('XY')
y: number,
z: number,
},
} |
{
type: "sketchGroups",
}
```
### Returns
`ExtrudeGroupSet` - A extrude group or a group of extrude groups.
`ExtrudeGroup` - An extrude group is a collection of extrude surfaces.
```js
{
// The id of the extrusion end cap
@ -282,7 +278,6 @@ startSketchOn('XY')
}],
// The id of the extrusion start cap
startCapId: uuid,
type: "extrudeGroup",
// The extrude surfaces.
value: [{
// The face id for the extrude plane.
@ -332,9 +327,6 @@ startSketchOn('XY')
y: number,
z: number,
},
} |
{
type: "extrudeGroups",
}
```

View File

@ -9,7 +9,7 @@ A linear pattern on a 2D sketch.
```js
patternLinear2d(data: LinearPattern2dData, sketch_group_set: SketchGroupSet) -> [SketchGroup]
patternLinear2d(data: LinearPattern2dData, sketch_group: SketchGroup) -> [SketchGroup]
```
### Examples
@ -39,7 +39,7 @@ const part = startSketchOn('XY')
repetitions: number,
}
```
* `sketch_group_set`: `SketchGroupSet` - A sketch group or a group of sketch groups. (REQUIRED)
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths. (REQUIRED)
```js
{
// The plane id or face id of the sketch group.
@ -120,7 +120,6 @@ const part = startSketchOn('XY')
// The to point.
to: [number, number],
},
type: "sketchGroup",
// The paths in the sketch group.
value: [{
// The from point.
@ -204,9 +203,6 @@ const part = startSketchOn('XY')
y: number,
z: number,
},
} |
{
type: "sketchGroups",
}
```

File diff suppressed because it is too large Load Diff

View File

@ -1035,7 +1035,6 @@ const part001 = startSketchOn('-XZ')
})
test('Can add multiple sketches', async ({ page }) => {
test.skip(process.platform === 'darwin', 'Can add multiple sketches')
const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
const PUR = 400 / 37.5 //pixeltoUnitRatio

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -7,7 +7,6 @@ export const TEST_SETTINGS = {
theme: Themes.Dark,
onboardingStatus: 'dismissed',
projectDirectory: '',
enableSSAO: false,
},
modeling: {
defaultUnit: 'in',

View File

@ -6,7 +6,7 @@ import { PNG } from 'pngjs'
async function waitForPageLoad(page: Page) {
// wait for 'Loading stream...' spinner
await page.getByTestId('loading-stream').waitFor()
// await page.getByTestId('loading-stream').waitFor()
// wait for all spinners to be gone
await page.getByTestId('loading').waitFor({ state: 'detached' })

View File

@ -18,7 +18,7 @@ export default defineConfig({
/* Retry on CI only */
retries: process.env.CI ? 3 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 2 : 1,
workers: process.env.CI ? 1 : 1,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
@ -72,7 +72,7 @@ export default defineConfig({
/* Run your local dev server before starting the tests */
webServer: {
command: 'VITE_KC_WASM_OVERRIDE_URL=$WASM_OVERRIDE yarn serve',
command: 'yarn serve',
// url: 'http://127.0.0.1:3000',
reuseExistingServer: !process.env.CI,
},

36
src-tauri/Cargo.lock generated
View File

@ -1592,7 +1592,7 @@ version = "0.18.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc"
dependencies = [
"heck 0.4.1",
"heck",
"proc-macro-crate 2.0.2",
"proc-macro-error",
"proc-macro2",
@ -1735,12 +1735,6 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.2.6"
@ -4328,7 +4322,7 @@ version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
dependencies = [
"heck 0.4.1",
"heck",
"proc-macro2",
"quote",
"rustversion",
@ -4417,7 +4411,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5fa6fb9ee296c0dc2df41a656ca7948546d061958115ddb0bcaae43ad0d17d2"
dependencies = [
"cfg-expr",
"heck 0.4.1",
"heck",
"pkg-config",
"toml 0.7.8",
"version-compare",
@ -4514,7 +4508,7 @@ dependencies = [
"getrandom 0.2.14",
"glob",
"gtk",
"heck 0.4.1",
"heck",
"http 1.1.0",
"jni",
"libc",
@ -4549,15 +4543,15 @@ dependencies = [
[[package]]
name = "tauri-build"
version = "2.0.0-beta.13"
version = "2.0.0-beta.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abcf98a9b4527567c3e5ca9723431d121e001c2145651b3fa044d22b5e025a7e"
checksum = "33de24aabe2b9c340d67005800cb6dd40aac5283126a42896fc8eec0b87cbe45"
dependencies = [
"anyhow",
"cargo_toml",
"dirs-next",
"glob",
"heck 0.5.0",
"heck",
"json-patch",
"schemars",
"semver",
@ -4602,7 +4596,7 @@ version = "2.0.0-beta.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b096f63f2724a1280ae0f5a34d0731de18ca18305e2ef6e5e9a39bb2710e8a85"
dependencies = [
"heck 0.4.1",
"heck",
"proc-macro2",
"quote",
"syn 2.0.48",
@ -4629,9 +4623,9 @@ dependencies = [
[[package]]
name = "tauri-plugin-dialog"
version = "2.0.0-beta.6"
version = "2.0.0-beta.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87caf6f2b704b0d27b4c64ef1fdd1f6ef97e2f5293216e230ad1efe61de54131"
checksum = "db4476c824a1488a52f4672d2b419a71fbf3dc97249013ef3c2c08fae2a23b71"
dependencies = [
"dunce",
"log",
@ -4666,9 +4660,9 @@ dependencies = [
[[package]]
name = "tauri-plugin-http"
version = "2.0.0-beta.6"
version = "2.0.0-beta.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c32962a2e2141b3bc034e5c04f363635965e59435794b6bdcf97a027f0925a"
checksum = "f27b2c90ed5473e1c068f523927fa0024212bc3a3f3a47c2a9c0b10b4b2e3ee4"
dependencies = [
"data-url",
"http 1.1.0",
@ -4806,16 +4800,16 @@ dependencies = [
[[package]]
name = "tauri-utils"
version = "2.0.0-beta.13"
version = "2.0.0-beta.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4709765385f035338ecc330f3fba753b8ee283c659c235da9768949cdb25469"
checksum = "760ac613d7f0de95067bcbcbcea175fe1df88fc4ab59c7f0b2cc2d01dc16a199"
dependencies = [
"brotli",
"cargo_metadata",
"ctor",
"dunce",
"glob",
"heck 0.5.0",
"heck",
"html5ever",
"infer",
"json-patch",

View File

@ -12,7 +12,7 @@ rust-version = "1.70"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "2.0.0-beta.13", features = [] }
tauri-build = { version = "2.0.0-beta.12", features = [] }
[dependencies]
anyhow = "1"
@ -21,9 +21,9 @@ oauth2 = "4.4.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tauri = { version = "2.0.0-beta.15", features = [ "devtools", "unstable"] }
tauri-plugin-dialog = { version = "2.0.0-beta.6" }
tauri-plugin-dialog = { version = "2.0.0-beta.5" }
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.5" }
tauri-plugin-os = { version = "2.0.0-beta.2" }
tauri-plugin-process = { version = "2.0.0-beta.2" }
tauri-plugin-shell = { version = "2.0.0-beta.2" }

View File

@ -193,6 +193,35 @@ export const Toolbar = () => {
Rectangle
</ActionButton>
</li>
<li className="contents" key="circle-button">
<ActionButton
className={buttonClassName}
Element="button"
onClick={() =>
state.matches('Sketch.Circle tool')
? send('CancelSketch')
: send('Equip circle tool')
}
aria-pressed={state.matches('Sketch.Circle tool')}
icon={{
icon: 'circle',
iconClassName,
bgClassName,
}}
disabled={
(!state.can('Equip circle tool') &&
!state.matches('Sketch.Circle tool')) ||
disableAllButtons
}
title={
state.can('Equip circle tool')
? 'Circle'
: 'Can only be used when a sketch is empty currently'
}
>
Circle
</ActionButton>
</li>
</>
)}
{state.matches('Sketch.SketchIdle') &&

View File

@ -97,6 +97,7 @@ import {
getRectangleCallExpressions,
updateRectangleSketch,
} from 'lib/rectangleTool'
import { circleAsCallExpressions, updateCircleSketch } from 'lib/circleTool'
type DraftSegment = 'line' | 'tangentialArcTo'
@ -580,7 +581,7 @@ export class SceneEntities {
...this.mouseEnterLeaveCallbacks(),
})
}
setupRectangleOriginListener = () => {
setupOriginListener = (type: 'circle' | 'rectangle') => {
sceneInfra.setCallbacks({
onClick: (args) => {
const twoD = args.intersectionPoint?.twoD
@ -589,7 +590,7 @@ export class SceneEntities {
return
}
sceneInfra.modelingSend({
type: 'Add rectangle origin',
type: `Add ${type} origin`,
data: [twoD.x, twoD.y],
})
},
@ -747,6 +748,154 @@ export class SceneEntities {
},
})
}
setupDraftCircle = async (
sketchPathToNode: PathToNode,
forward: [number, number, number],
up: [number, number, number],
sketchOrigin: [number, number, number],
circleOrigin: [x: number, y: number]
) => {
let _ast = JSON.parse(JSON.stringify(kclManager.ast))
const variableDeclarationName =
getNodeFromPath<VariableDeclaration>(
_ast,
sketchPathToNode || [],
'VariableDeclaration'
)?.node?.declarations?.[0]?.id?.name || ''
const tags: [string] = [findUniqueName(_ast, 'circle')]
const startSketchOn = getNodeFromPath<VariableDeclaration>(
_ast,
sketchPathToNode || [],
'VariableDeclaration'
)?.node?.declarations
const startSketchOnInit = startSketchOn?.[0]?.init
startSketchOn[0].init = createPipeExpression([
startSketchOnInit,
...circleAsCallExpressions(circleOrigin, tags),
])
_ast = parse(recast(_ast))
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
sketchPathToNode,
forward,
up,
position: sketchOrigin,
maybeModdedAst: _ast,
draftExpressionsIndices: { start: 0, end: 1 },
})
sceneInfra.setCallbacks({
onMove: async (args) => {
// Update the radius of the draft rectangle
const pathToNodeTwo = JSON.parse(JSON.stringify(sketchPathToNode))
pathToNodeTwo[1][0] = 0
const sketchInit = getNodeFromPath<VariableDeclaration>(
truncatedAst,
pathToNodeTwo || [],
'VariableDeclaration'
)?.node?.declarations?.[0]?.init
const x = (args.intersectionPoint.twoD.x || 0) - circleOrigin[0]
const y = (args.intersectionPoint.twoD.y || 0) - circleOrigin[1]
if (sketchInit.type === 'PipeExpression') {
updateCircleSketch(sketchInit, x, y, tags[0])
}
const { programMemory } = await executeAst({
ast: truncatedAst,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager,
programMemoryOverride,
})
this.sceneProgramMemory = programMemory
const sketchGroup = programMemory.root[
variableDeclarationName
] as SketchGroup
const sgPaths = sketchGroup.value
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
this.updateSegment(
sketchGroup.start,
0,
0,
_ast,
orthoFactor,
sketchGroup
)
sgPaths.forEach((seg, index) =>
this.updateSegment(seg, index, 0, _ast, orthoFactor, sketchGroup)
)
},
onClick: async (args) => {
// Commit the circle to the full AST/code and return to sketch.idle
const radiusPoint = args.intersectionPoint?.twoD
if (!radiusPoint || args.mouseEvent.button !== 0) return
const x = roundOff((radiusPoint.x || 0) - circleOrigin[0])
const y = roundOff((radiusPoint.y || 0) - circleOrigin[1])
const sketchInit = getNodeFromPath<VariableDeclaration>(
_ast,
sketchPathToNode || [],
'VariableDeclaration'
)?.node?.declarations?.[0]?.init
if (sketchInit.type === 'PipeExpression') {
updateCircleSketch(sketchInit, x, y, tags[0])
_ast = parse(recast(_ast))
console.log('onClick', {
sketchInit: sketchInit,
_ast,
x,
y,
truncatedAst,
})
// Update the primary AST and unequip the rectangle tool
await kclManager.executeAstMock(_ast)
sceneInfra.modelingSend({ type: 'CancelSketch' })
const { programMemory } = await executeAst({
ast: _ast,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager,
programMemoryOverride,
})
// Prepare to update the THREEjs scene
this.sceneProgramMemory = programMemory
const sketchGroup = programMemory.root[
variableDeclarationName
] as SketchGroup
const sgPaths = sketchGroup.value
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
// Update the starting segment of the THREEjs scene
this.updateSegment(
sketchGroup.start,
0,
0,
_ast,
orthoFactor,
sketchGroup
)
// Update the rest of the segments of the THREEjs scene
sgPaths.forEach((seg, index) =>
this.updateSegment(seg, index, 0, _ast, orthoFactor, sketchGroup)
)
}
},
})
}
setupSketchIdleCallbacks = ({
pathToNode,
up,

View File

@ -3,12 +3,13 @@ import { useCommandsContext } from 'hooks/useCommandsContext'
import { useKclContext } from 'lang/KclProvider'
import { CommandArgument } from 'lib/commandTypes'
import {
ResolvedSelectionType,
canSubmitSelectionArg,
getSelectionType,
getSelectionTypeDisplayText,
} from 'lib/selections'
import { modelingMachine } from 'machines/modelingMachine'
import { useCallback, useEffect, useRef, useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import { useHotkeys } from 'react-hotkeys-hook'
import { StateFrom } from 'xstate'
@ -29,13 +30,13 @@ function CommandBarSelectionInput({
const { commandBarState, commandBarSend } = useCommandsContext()
const [hasSubmitted, setHasSubmitted] = useState(false)
const selection = useSelector(arg.machineActor, selectionSelector)
const initSelectionsByType = useCallback(() => {
const selectionRangeEnd = selection.codeBasedSelections[0]?.range[1]
return !selectionRangeEnd || selectionRangeEnd === code.length
const [selectionsByType, setSelectionsByType] = useState<
'none' | ResolvedSelectionType[]
>(
selection.codeBasedSelections[0]?.range[1] === code.length
? 'none'
: getSelectionType(selection)
}, [selection, code])
const selectionsByType = initSelectionsByType()
)
const [canSubmitSelection, setCanSubmitSelection] = useState<boolean>(
canSubmitSelectionArg(selectionsByType, arg)
)
@ -50,14 +51,17 @@ function CommandBarSelectionInput({
inputRef.current?.focus()
}, [selection, inputRef])
useEffect(() => {
setSelectionsByType(
selection.codeBasedSelections[0]?.range[1] === code.length
? 'none'
: getSelectionType(selection)
)
}, [selection])
// Fast-forward through this arg if it's marked as skippable
// and we have a valid selection already
useEffect(() => {
console.log('selection input effect', {
selectionsByType,
canSubmitSelection,
arg,
})
setCanSubmitSelection(canSubmitSelectionArg(selectionsByType, arg))
const argValue = commandBarState.context.argumentsToSubmit[arg.name]
if (canSubmitSelection && arg.skip && argValue === undefined) {

View File

@ -61,6 +61,16 @@ const CustomIconMap = {
/>
</svg>
),
circle: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10 2.5C9.01509 2.5 8.03982 2.69399 7.12988 3.0709C6.21994 3.44781 5.39314 4.00026 4.6967 4.6967C4.00026 5.39314 3.44782 6.21993 3.07091 7.12987C2.694 8.03981 2.5 9.01508 2.5 10C2.5 10.9849 2.69399 11.9602 3.0709 12.8701C3.44781 13.7801 4.00026 14.6069 4.6967 15.3033C5.39314 15.9997 6.21993 16.5522 7.12987 16.9291C8.03982 17.306 9.01509 17.5 10 17.5C10.9849 17.5 11.9602 17.306 12.8701 16.9291C13.7801 16.5522 14.6069 15.9997 15.3033 15.3033C15.9997 14.6069 16.5522 13.7801 16.9291 12.8701C17.306 11.9602 17.5 10.9849 17.5 10C17.5 9.01509 17.306 8.03982 16.9291 7.12988C16.5522 6.21993 15.9997 5.39314 15.3033 4.6967C14.6069 4.00026 13.7801 3.44781 12.8701 3.0709C11.9602 2.69399 10.9849 2.5 10 2.5ZM6.7472 2.14702C7.77847 1.71986 8.88377 1.5 10 1.5C11.1162 1.5 12.2215 1.71986 13.2528 2.14702C14.2841 2.57419 15.2211 3.20029 16.0104 3.98959C16.7997 4.77889 17.4258 5.71592 17.853 6.74719C18.2801 7.77846 18.5 8.88377 18.5 10C18.5 11.1162 18.2801 12.2215 17.853 13.2528C17.4258 14.2841 16.7997 15.2211 16.0104 16.0104C15.2211 16.7997 14.2841 17.4258 13.2528 17.853C12.2215 18.2801 11.1162 18.5 10 18.5C8.88376 18.5 7.77846 18.2801 6.74719 17.853C5.71592 17.4258 4.77889 16.7997 3.98959 16.0104C3.20029 15.2211 2.57419 14.2841 2.14702 13.2528C1.71986 12.2215 1.5 11.1162 1.5 10C1.5 8.88376 1.71986 7.77845 2.14703 6.74719C2.57419 5.71592 3.2003 4.77889 3.9896 3.98959C4.7789 3.20029 5.71593 2.57419 6.7472 2.14702Z"
fill="currentColor"
/>
</svg>
),
clipboardCheckmark: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path

View File

@ -77,7 +77,7 @@ export const ModelingMachineProvider = ({
auth,
settings: {
context: {
app: { theme, enableSSAO },
app: { theme },
modeling: { defaultUnit, highlightEdges },
},
},
@ -87,7 +87,6 @@ export const ModelingMachineProvider = ({
useSetupEngineManager(streamRef, token, {
theme: theme.current,
highlightEdges: highlightEdges.current,
enableSSAO: enableSSAO.current,
})
const { htmlRef } = useStore((s) => ({
htmlRef: s.htmlRef,
@ -268,12 +267,10 @@ export const ModelingMachineProvider = ({
'has valid extrude selection': ({ selectionRanges }) => {
// A user can begin extruding if they either have 1+ faces selected or nothing selected
// TODO: I believe this guard only allows for extruding a single face at a time
if (selectionRanges.codeBasedSelections.length < 1) return false
const isPipe = isSketchPipe(selectionRanges)
if (
selectionRanges.codeBasedSelections.length === 0 ||
isSelectionLastLine(selectionRanges, codeManager.code)
)
if (isSelectionLastLine(selectionRanges, codeManager.code))
return true
if (!isPipe) return false

View File

@ -7,12 +7,7 @@ import React, { createContext, useEffect } from 'react'
import useStateMachineCommands from '../hooks/useStateMachineCommands'
import { settingsMachine } from 'machines/settingsMachine'
import { toast } from 'react-hot-toast'
import {
getThemeColorForEngine,
getOppositeTheme,
setThemeClass,
Themes,
} from 'lib/theme'
import { getThemeColorForEngine, setThemeClass, Themes } from 'lib/theme'
import decamelize from 'decamelize'
import {
AnyStateMachine,
@ -104,9 +99,6 @@ export const SettingsAuthProviderBase = ({
{
context: loadedSettings,
actions: {
//TODO: batch all these and if that's difficult to do from tsx,
// make it easy to do
setClientSideSceneUnits: (context, event) => {
const newBaseUnit =
event.type === 'set.modeling.defaultUnit'
@ -123,16 +115,6 @@ export const SettingsAuthProviderBase = ({
color: getThemeColorForEngine(context.app.theme.current),
},
})
const opposingTheme = getOppositeTheme(context.app.theme.current)
engineCommandManager.sendSceneCommand({
cmd_id: uuidv4(),
type: 'modeling_cmd_req',
cmd: {
type: 'set_default_system_properties',
color: getThemeColorForEngine(opposingTheme),
},
})
},
setEngineEdges: (context) => {
engineCommandManager.sendSceneCommand({

View File

@ -7,7 +7,5 @@ export const VITE_KC_API_BASE_URL = import.meta.env.VITE_KC_API_BASE_URL
export const VITE_KC_SITE_BASE_URL = import.meta.env.VITE_KC_SITE_BASE_URL
export const VITE_KC_CONNECTION_TIMEOUT_MS = import.meta.env
.VITE_KC_CONNECTION_TIMEOUT_MS
export const VITE_KC_WASM_OVERRIDE_URL = import.meta.env
.VITE_KC_WASM_OVERRIDE_URL
export const TEST = import.meta.env.TEST
export const DEV = import.meta.env.DEV

View File

@ -11,11 +11,9 @@ export function useSetupEngineManager(
settings = {
theme: Themes.System,
highlightEdges: true,
enableSSAO: true,
} as {
theme: Themes
highlightEdges: boolean
enableSSAO: boolean
}
) {
const {

View File

@ -4,7 +4,7 @@ import { Models } from '@kittycad/lib'
import { exportSave } from 'lib/exportSave'
import { uuidv4 } from 'lib/utils'
import { getNodePathFromSourceRange } from 'lang/queryAst'
import { Themes, getThemeColorForEngine, getOppositeTheme } from 'lib/theme'
import { Themes, getThemeColorForEngine } from 'lib/theme'
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
let lastMessage = ''
@ -941,7 +941,6 @@ export class EngineCommandManager {
settings = {
theme: Themes.Dark,
highlightEdges: true,
enableSSAO: true,
},
}: {
setMediaStream: (stream: MediaStream) => void
@ -954,7 +953,6 @@ export class EngineCommandManager {
settings?: {
theme: Themes
highlightEdges: boolean
enableSSAO: boolean
}
}) {
this.makeDefaultPlanes = makeDefaultPlanes
@ -971,8 +969,7 @@ export class EngineCommandManager {
return
}
const additionalSettings = settings.enableSSAO ? '&post_effect=ssao' : ''
const url = `${VITE_KC_API_WS_MODELING_URL}?video_res_width=${width}&video_res_height=${height}${additionalSettings}`
const url = `${VITE_KC_API_WS_MODELING_URL}?video_res_width=${width}&video_res_height=${height}`
this.engineConnection = new EngineConnection({
engineCommandManager: this,
url,
@ -992,18 +989,6 @@ export class EngineCommandManager {
color: getThemeColorForEngine(settings.theme),
},
})
// Sets the default line colors
const opposingTheme = getOppositeTheme(settings.theme)
this.sendSceneCommand({
cmd_id: uuidv4(),
type: 'modeling_cmd_req',
cmd: {
type: 'set_default_system_properties',
color: getThemeColorForEngine(opposingTheme),
},
})
// Set the edge lines visibility
this.sendSceneCommand({
type: 'modeling_cmd_req',
@ -1341,17 +1326,6 @@ export class EngineCommandManager {
this.lastArtifactMap = this.artifactMap
this.artifactMap = {}
await this.initPlanes()
await this.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'make_axes_gizmo',
clobber: false,
// If true, axes gizmo will be placed in the corner of the screen.
// If false, it will be placed at the origin of the scene.
gizmo_mode: true,
},
})
}
subscribeTo<T extends ModelTypes>({
event,

View File

@ -25,7 +25,7 @@ import { AppInfo } from 'wasm-lib/kcl/bindings/AppInfo'
import { CoreDumpManager } from 'lib/coredump'
import openWindow from 'lib/openWindow'
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
import { TEST, VITE_KC_WASM_OVERRIDE_URL } from 'env'
import { TEST } from 'env'
export type { Program } from '../wasm-lib/kcl/bindings/Program'
export type { Value } from '../wasm-lib/kcl/bindings/Value'
@ -76,19 +76,18 @@ export type { MemoryItem } from '../wasm-lib/kcl/bindings/MemoryItem'
export type { ExtrudeSurface } from '../wasm-lib/kcl/bindings/ExtrudeSurface'
export const wasmUrl = () => {
const baseUrl = VITE_KC_WASM_OVERRIDE_URL
? VITE_KC_WASM_OVERRIDE_URL
: typeof window === 'undefined'
? 'http://127.0.0.1:3000'
: window.location.origin.includes('tauri://localhost')
? 'tauri://localhost' // custom protocol for macOS
: window.location.origin.includes('tauri.localhost')
? 'http://tauri.localhost' // fallback for Windows
: window.location.origin.includes('localhost')
? 'http://localhost:3000'
: window.location.origin && window.location.origin !== 'null'
? window.location.origin
: 'http://localhost:3000'
const baseUrl =
typeof window === 'undefined'
? 'http://127.0.0.1:3000'
: window.location.origin.includes('tauri://localhost')
? 'tauri://localhost' // custom protocol for macOS
: window.location.origin.includes('tauri.localhost')
? 'http://tauri.localhost' // fallback for Windows
: window.location.origin.includes('localhost')
? 'http://localhost:3000'
: window.location.origin && window.location.origin !== 'null'
? window.location.origin
: 'http://localhost:3000'
const fullUrl = baseUrl + '/wasm_lib_bg.wasm'
console.log(`Full URL for WASM: ${fullUrl}`)

59
src/lib/circleTool.ts Normal file
View File

@ -0,0 +1,59 @@
import {
createArrayExpression,
createBinaryExpression,
createCallExpressionStdLib,
createLiteral,
createPipeSubstitution,
} from 'lang/modifyAst'
import { roundOff } from './utils'
import {
ArrayExpression,
CallExpression,
Literal,
PipeExpression,
} from 'lang/wasm'
/**
* Returns AST expressions for this KCL code:
* const yo = startSketchOn('XY')
* |> circle([0, 0], 0, %)
*/
export const circleAsCallExpressions = (
circleOrigin: [number, number],
tags: [string]
) => [
createCallExpressionStdLib('circle', [
createArrayExpression([
createLiteral(roundOff(circleOrigin[0])),
createLiteral(roundOff(circleOrigin[1])),
]),
createLiteral(10),
createPipeSubstitution(),
createLiteral(tags[0]),
]),
]
/**
* Mutates the pipeExpression to update the circle sketch
* @param pipeExpression
* @param x
* @param y
* @param tag
*/
export function updateCircleSketch(
pipeExpression: PipeExpression,
x: number,
y: number,
tag: string
) {
const circle = pipeExpression.body[1] as CallExpression
const origin = circle.arguments[0] as ArrayExpression
const originX = (origin.elements[0] as Literal).value
const originY = (origin.elements[1] as Literal).value
const radius = roundOff(
Math.sqrt((x - Number(originX)) ** 2 + (y - Number(originY)) ** 2)
)
;(circle.arguments[1] as Literal) = createLiteral(radius)
}

View File

@ -420,13 +420,7 @@ export function getSelectionTypeDisplayText(
const selectionsByType = getSelectionType(selection)
return (selectionsByType as Exclude<typeof selectionsByType, 'none'>)
.map(
// Hack for showing "face" instead of "extrude-wall" in command bar text
([type, count]) =>
`${count} ${type.replace('extrude-wall', 'face')}${
count > 1 ? 's' : ''
}`
)
.map(([type, count]) => `${count} ${type}${count > 1 ? 's' : ''}`)
.join(', ')
}

View File

@ -156,13 +156,6 @@ export function createSettings() {
</div>
),
}),
enableSSAO: new Setting<boolean>({
defaultValue: true,
description:
'Whether or not Screen Space Ambient Occlusion (SSAO) is enabled',
validate: (v) => typeof v === 'boolean',
hideOnPlatform: 'both', //for now
}),
onboardingStatus: new Setting<string>({
defaultValue: '',
validate: (v) => typeof v === 'string',

View File

@ -23,17 +23,6 @@ export function setThemeClass(theme: Themes) {
}
}
// Returns the resolved theme in use (Dark || Light)
export function getResolvedTheme(theme: Themes) {
return theme === Themes.System ? getSystemTheme() : theme
}
// Returns the opposing theme
export function getOppositeTheme(theme: Themes) {
const resolvedTheme = getResolvedTheme(theme)
return resolvedTheme === Themes.Dark ? Themes.Light : Themes.Dark
}
/**
* The engine takes RGBA values from 0-1
* So we convert from the conventional 0-255 found in Figma
@ -41,7 +30,7 @@ export function getOppositeTheme(theme: Themes) {
* @returns { r: number, g: number, b: number, a: number }
*/
export function getThemeColorForEngine(theme: Themes) {
const resolvedTheme = getResolvedTheme(theme)
const resolvedTheme = theme === Themes.System ? getSystemTheme() : theme
const dark = 28 / 255
const light = 249 / 255
return resolvedTheme === Themes.Dark

View File

@ -31,11 +31,9 @@ export function useCalculateKclExpression({
newVariableInsertIndex: number
setNewVariableName: (a: string) => void
} {
const { programMemory, code } = useKclContext()
const { programMemory } = useKclContext()
const { context } = useModelingContext()
const selectionRange:
| (typeof context.selectionRanges.codeBasedSelections)[number]['range']
| undefined = context.selectionRanges.codeBasedSelections[0]?.range
const selectionRange = context.selectionRanges.codeBasedSelections[0].range
const inputRef = useRef<HTMLInputElement>(null)
const [availableVarInfo, setAvailableVarInfo] = useState<
ReturnType<typeof findAllPreviousVariables>
@ -69,7 +67,7 @@ export function useCalculateKclExpression({
} else {
setIsNewVariableNameUnique(true)
}
}, [programMemory, newVariableName])
}, [newVariableName])
useEffect(() => {
if (!programMemory || !selectionRange) return
@ -83,8 +81,8 @@ export function useCalculateKclExpression({
useEffect(() => {
const execAstAndSetResult = async () => {
const _code = `const __result__ = ${value}`
const ast = parse(_code)
const code = `const __result__ = ${value}`
const ast = parse(code)
const _programMem: any = { root: {}, return: null }
availableVarInfo.variables.forEach(({ key, value }) => {
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
@ -113,7 +111,7 @@ export function useCalculateKclExpression({
setCalcResult('NAN')
setValueNode(null)
})
}, [value, availableVarInfo, code, kclManager.programMemory])
}, [value, availableVarInfo])
return {
valueNode,

File diff suppressed because one or more lines are too long

View File

@ -6,10 +6,10 @@
serial-integration = { max-threads = 4 }
[profile.default]
slow-timeout = { period = "30s", terminate-after = 1 }
slow-timeout = { period = "10s", terminate-after = 1 }
[profile.ci]
slow-timeout = { period = "50s", terminate-after = 5 }
slow-timeout = { period = "30s", terminate-after = 5 }
[[profile.default.overrides]]
filter = "test(serial_test_)"

View File

@ -1854,7 +1854,7 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.1.50"
version = "0.1.49"
dependencies = [
"anyhow",
"approx 0.5.1",
@ -2004,9 +2004,9 @@ dependencies = [
[[package]]
name = "kittycad-modeling-cmds"
version = "0.2.21"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e326955e8f315590a1926c17ff6a6082d3013f472c881aba56d73bfa170cf5b3"
checksum = "f93f7904109e445ab3dcfbaa4f0f4396d1df22c701075cdce4a7e491701796af"
dependencies = [
"anyhow",
"chrono",

View File

@ -65,7 +65,7 @@ kittycad = { version = "0.3.0", default-features = false, features = ["js", "req
kittycad-execution-plan = "0.1.5"
kittycad-execution-plan-macros = "0.1.9"
kittycad-execution-plan-traits = "0.1.14"
kittycad-modeling-cmds = "0.2.21"
kittycad-modeling-cmds = "0.2.20"
kittycad-modeling-session = "0.1.4"
[[test]]

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-lib"
description = "KittyCAD Language implementation and tools"
version = "0.1.50"
version = "0.1.49"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -62,11 +62,7 @@ impl StdLibFnArg {
}
pub fn get_autocomplete_snippet(&self, index: usize) -> Result<Option<(usize, String)>> {
if self.type_ == "SketchGroup"
|| self.type_ == "ExtrudeGroup"
|| self.type_ == "SketchSurface"
|| self.type_ == "SketchGroupSet"
{
if self.type_ == "SketchGroup" || self.type_ == "ExtrudeGroup" || self.type_ == "SketchSurface" {
return Ok(Some((index, format!("${{{}:{}}}", index, "%"))));
}
get_autocomplete_snippet_from_schema(&self.schema.clone(), index)
@ -323,12 +319,7 @@ pub fn get_type_string_from_schema(schema: &schemars::schema::Schema) -> Result<
if let Some(format) = &o.format {
if format == "uuid" {
return Ok((Primitive::Uuid.to_string(), false));
} else if format == "double"
|| format == "uint"
|| format == "int64"
|| format == "uint32"
|| format == "uint64"
{
} else if format == "double" || format == "uint" || format == "int64" || format == "uint32" {
return Ok((Primitive::Number.to_string(), false));
} else {
anyhow::bail!("unknown format: {}", format);
@ -465,12 +456,7 @@ pub fn get_autocomplete_snippet_from_schema(
if let Some(format) = &o.format {
if format == "uuid" {
return Ok(Some((index, format!(r#"${{{}:"tag_or_edge_fn"}}"#, index))));
} else if format == "double"
|| format == "uint"
|| format == "int64"
|| format == "uint32"
|| format == "uint64"
{
} else if format == "double" || format == "uint" || format == "int64" || format == "uint32" {
return Ok(Some((index, format!(r#"${{{}:3.14}}"#, index))));
} else {
anyhow::bail!("unknown format: {}", format);
@ -624,12 +610,7 @@ pub fn get_autocomplete_string_from_schema(schema: &schemars::schema::Schema) ->
if let Some(format) = &o.format {
if format == "uuid" {
return Ok(Primitive::Uuid.to_string());
} else if format == "double"
|| format == "uint"
|| format == "int64"
|| format == "uint32"
|| format == "uint64"
{
} else if format == "double" || format == "uint" || format == "int64" || format == "uint32" {
return Ok(Primitive::Number.to_string());
} else {
anyhow::bail!("unknown format: {}", format);

View File

@ -191,15 +191,6 @@ pub enum SketchGroupSet {
SketchGroups(Vec<Box<SketchGroup>>),
}
/// A extrude group or a group of extrude groups.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum ExtrudeGroupSet {
ExtrudeGroup(Box<ExtrudeGroup>),
ExtrudeGroups(Vec<Box<ExtrudeGroup>>),
}
/// Data for an imported geometry.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]

View File

@ -7,23 +7,17 @@ use uuid::Uuid;
use crate::{
errors::{KclError, KclErrorDetails},
executor::{
ExtrudeGroup, ExtrudeGroupSet, ExtrudeSurface, ExtrudeTransform, GeoMeta, MemoryItem, Path, SketchGroup,
SketchGroupSet, SketchSurface,
},
executor::{ExtrudeGroup, ExtrudeSurface, ExtrudeTransform, GeoMeta, MemoryItem, Path, SketchGroup, SketchSurface},
std::Args,
};
/// Extrudes by a given amount.
pub async fn extrude(args: Args) -> Result<MemoryItem, KclError> {
let (length, sketch_group_set) = args.get_number_sketch_group_set()?;
let (length, sketch_group) = args.get_number_sketch_group()?;
let result = inner_extrude(length, sketch_group_set, args).await?;
let result = inner_extrude(length, sketch_group, args).await?;
match result {
ExtrudeGroupSet::ExtrudeGroup(extrude_group) => Ok(MemoryItem::ExtrudeGroup(extrude_group)),
ExtrudeGroupSet::ExtrudeGroups(extrude_groups) => Ok(MemoryItem::ExtrudeGroups { value: extrude_groups }),
}
Ok(MemoryItem::ExtrudeGroup(result))
}
/// Extrudes by a given amount.
@ -40,33 +34,21 @@ pub async fn extrude(args: Args) -> Result<MemoryItem, KclError> {
#[stdlib {
name = "extrude"
}]
async fn inner_extrude(length: f64, sketch_group_set: SketchGroupSet, args: Args) -> Result<ExtrudeGroupSet, KclError> {
async fn inner_extrude(length: f64, sketch_group: Box<SketchGroup>, args: Args) -> Result<Box<ExtrudeGroup>, KclError> {
let id = uuid::Uuid::new_v4();
// Extrude the element(s).
let sketch_groups = match sketch_group_set {
SketchGroupSet::SketchGroup(sketch_group) => vec![sketch_group],
SketchGroupSet::SketchGroups(sketch_groups) => sketch_groups,
};
let mut extrude_groups = Vec::new();
for sketch_group in sketch_groups.iter() {
args.send_modeling_cmd(
id,
kittycad::types::ModelingCmd::Extrude {
target: sketch_group.id,
distance: length,
cap: true,
},
)
.await?;
extrude_groups.push(do_post_extrude(sketch_group.clone(), length, id, args.clone()).await?);
}
// Extrude the element.
args.send_modeling_cmd(
id,
kittycad::types::ModelingCmd::Extrude {
target: sketch_group.id,
distance: length,
cap: true,
},
)
.await?;
if extrude_groups.len() == 1 {
Ok(ExtrudeGroupSet::ExtrudeGroup(extrude_groups.pop().unwrap()))
} else {
Ok(ExtrudeGroupSet::ExtrudeGroups(extrude_groups))
}
do_post_extrude(sketch_group, length, id, args).await
}
pub(crate) async fn do_post_extrude(

View File

@ -11,7 +11,6 @@ pub mod revolve;
pub mod segment;
pub mod shapes;
pub mod sketch;
pub mod types;
pub mod utils;
use std::collections::HashMap;
@ -629,49 +628,6 @@ impl Args {
Ok((data, sketch_group))
}
fn get_data_and_sketch_group_set<T: serde::de::DeserializeOwned>(&self) -> Result<(T, SketchGroupSet), KclError> {
let first_value = self
.args
.first()
.ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!("Expected a struct as the first argument, found `{:?}`", self.args),
source_ranges: vec![self.source_range],
})
})?
.get_json_value()?;
let data: T = serde_json::from_value(first_value).map_err(|e| {
KclError::Type(KclErrorDetails {
message: format!("Failed to deserialize struct from JSON: {}", e),
source_ranges: vec![self.source_range],
})
})?;
let second_value = self.args.get(1).ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!("Expected a SketchGroup as the second argument, found `{:?}`", self.args),
source_ranges: vec![self.source_range],
})
})?;
let sketch_set = if let MemoryItem::SketchGroup(sg) = second_value {
SketchGroupSet::SketchGroup(sg.clone())
} else if let MemoryItem::SketchGroups { value } = second_value {
SketchGroupSet::SketchGroups(value.clone())
} else {
return Err(KclError::Type(KclErrorDetails {
message: format!(
"Expected a SketchGroup or Vector of SketchGroups as the second argument, found `{:?}`",
self.args
),
source_ranges: vec![self.source_range],
}));
};
Ok((data, sketch_set))
}
fn get_data_and_sketch_group_and_tag<T: serde::de::DeserializeOwned>(
&self,
) -> Result<(T, Box<SketchGroup>, Option<String>), KclError> {
@ -867,7 +823,7 @@ impl Args {
Ok((segment_name, to_number, sketch_group))
}
fn get_number_sketch_group_set(&self) -> Result<(f64, SketchGroupSet), KclError> {
fn get_number_sketch_group(&self) -> Result<(f64, Box<SketchGroup>), KclError> {
// Iterate over our args, the first argument should be a number.
// The second argument should be a SketchGroup.
let first_value = self
@ -890,21 +846,16 @@ impl Args {
})
})?;
let sketch_set = if let MemoryItem::SketchGroup(sg) = second_value {
SketchGroupSet::SketchGroup(sg.clone())
} else if let MemoryItem::SketchGroups { value } = second_value {
SketchGroupSet::SketchGroups(value.clone())
let sketch_group = if let MemoryItem::SketchGroup(sg) = second_value {
sg.clone()
} else {
return Err(KclError::Type(KclErrorDetails {
message: format!(
"Expected a SketchGroup or Vector of SketchGroups as the second argument, found `{:?}`",
self.args
),
message: format!("Expected a SketchGroup as the second argument, found `{:?}`", self.args),
source_ranges: vec![self.source_range],
}));
};
Ok((number, sketch_set))
Ok((number, sketch_group))
}
fn get_path_name_extrude_group(&self) -> Result<(String, Box<ExtrudeGroup>), KclError> {

View File

@ -8,8 +8,8 @@ use serde::{Deserialize, Serialize};
use crate::{
errors::{KclError, KclErrorDetails},
executor::{ExtrudeGroup, Geometries, Geometry, MemoryItem, SketchGroup, SketchGroupSet},
std::{types::Uint, Args},
executor::{ExtrudeGroup, Geometries, Geometry, MemoryItem, SketchGroup},
std::Args,
};
/// Data for a linear pattern on a 2D sketch.
@ -20,7 +20,7 @@ pub struct LinearPattern2dData {
/// The number of repetitions. Must be greater than 0.
/// This excludes the original entity. For example, if `repetitions` is 1,
/// the original entity will be copied once.
pub repetitions: Uint,
pub repetitions: u32,
/// The distance between each repetition. This can also be referred to as spacing.
pub distance: f64,
/// The axis of the pattern. This is a 2D vector.
@ -35,7 +35,7 @@ pub struct LinearPattern3dData {
/// The number of repetitions. Must be greater than 0.
/// This excludes the original entity. For example, if `repetitions` is 1,
/// the original entity will be copied once.
pub repetitions: Uint,
pub repetitions: u32,
/// The distance between each repetition. This can also be referred to as spacing.
pub distance: f64,
/// The axis of the pattern.
@ -57,8 +57,8 @@ impl LinearPattern {
pub fn repetitions(&self) -> u32 {
match self {
LinearPattern::TwoD(lp) => lp.repetitions.u32(),
LinearPattern::ThreeD(lp) => lp.repetitions.u32(),
LinearPattern::TwoD(lp) => lp.repetitions,
LinearPattern::ThreeD(lp) => lp.repetitions,
}
}
@ -72,7 +72,7 @@ impl LinearPattern {
/// A linear pattern on a 2D sketch.
pub async fn pattern_linear_2d(args: Args) -> Result<MemoryItem, KclError> {
let (data, sketch_group_set): (LinearPattern2dData, SketchGroupSet) = args.get_data_and_sketch_group_set()?;
let (data, sketch_group): (LinearPattern2dData, Box<SketchGroup>) = args.get_data_and_sketch_group()?;
if data.axis == [0.0, 0.0] {
return Err(KclError::Semantic(KclErrorDetails {
@ -83,7 +83,7 @@ pub async fn pattern_linear_2d(args: Args) -> Result<MemoryItem, KclError> {
}));
}
let sketch_groups = inner_pattern_linear_2d(data, sketch_group_set, args).await?;
let sketch_groups = inner_pattern_linear_2d(data, sketch_group, args).await?;
Ok(MemoryItem::SketchGroups { value: sketch_groups })
}
@ -99,33 +99,23 @@ pub async fn pattern_linear_2d(args: Args) -> Result<MemoryItem, KclError> {
}]
async fn inner_pattern_linear_2d(
data: LinearPattern2dData,
sketch_group_set: SketchGroupSet,
sketch_group: Box<SketchGroup>,
args: Args,
) -> Result<Vec<Box<SketchGroup>>, KclError> {
let starting_sketch_groups = match sketch_group_set {
SketchGroupSet::SketchGroup(sketch_group) => vec![sketch_group],
SketchGroupSet::SketchGroups(sketch_groups) => sketch_groups,
let geometries = pattern_linear(
LinearPattern::TwoD(data),
Geometry::SketchGroup(sketch_group),
args.clone(),
)
.await?;
let Geometries::SketchGroups(sketch_groups) = geometries else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Expected a vec of sketch groups".to_string(),
source_ranges: vec![args.source_range],
}));
};
let mut sketch_groups = Vec::new();
for sketch_group in starting_sketch_groups.iter() {
let geometries = pattern_linear(
LinearPattern::TwoD(data.clone()),
Geometry::SketchGroup(sketch_group.clone()),
args.clone(),
)
.await?;
let Geometries::SketchGroups(new_sketch_groups) = geometries else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Expected a vec of sketch groups".to_string(),
source_ranges: vec![args.source_range],
}));
};
sketch_groups.extend(new_sketch_groups);
}
Ok(sketch_groups)
}
@ -185,19 +175,6 @@ async fn inner_pattern_linear_3d(
async fn pattern_linear(data: LinearPattern, geometry: Geometry, args: Args) -> Result<Geometries, KclError> {
let id = uuid::Uuid::new_v4();
println!(
"id: {:#?}",
ModelingCmd::EntityLinearPattern {
axis: kittycad::types::Point3D {
x: data.axis()[0],
y: data.axis()[1],
z: data.axis()[2],
},
entity_id: geometry.id(),
num_repetitions: data.repetitions(),
spacing: data.distance(),
}
);
let resp = args
.send_modeling_cmd(
@ -257,7 +234,7 @@ pub struct CircularPattern2dData {
/// The number of repetitions. Must be greater than 0.
/// This excludes the original entity. For example, if `repetitions` is 1,
/// the original entity will be copied once.
pub repetitions: Uint,
pub repetitions: u32,
/// The center about which to make the pattern. This is a 2D vector.
pub center: [f64; 2],
/// The arc angle (in degrees) to place the repetitions. Must be greater than 0.
@ -274,7 +251,7 @@ pub struct CircularPattern3dData {
/// The number of repetitions. Must be greater than 0.
/// This excludes the original entity. For example, if `repetitions` is 1,
/// the original entity will be copied once.
pub repetitions: Uint,
pub repetitions: u32,
/// The axis around which to make the pattern. This is a 3D vector.
pub axis: [f64; 3],
/// The center about which to make the pattern. This is a 3D vector.
@ -307,8 +284,8 @@ impl CircularPattern {
pub fn repetitions(&self) -> u32 {
match self {
CircularPattern::TwoD(lp) => lp.repetitions.u32(),
CircularPattern::ThreeD(lp) => lp.repetitions.u32(),
CircularPattern::TwoD(lp) => lp.repetitions,
CircularPattern::ThreeD(lp) => lp.repetitions,
}
}

View File

@ -1,40 +0,0 @@
//! Custom types for various standard library types.
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
/// A Uint that allows us to do math but rounds to a uint.
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize, ts_rs::TS)]
#[ts(export)]
pub struct Uint(f64);
impl Uint {
pub fn new(value: f64) -> Self {
if value < 0.0 {
panic!("Uint cannot be negative");
}
Self(value)
}
pub fn value(&self) -> f64 {
self.0
}
pub fn u32(&self) -> u32 {
self.0.round() as u32
}
pub fn u64(&self) -> u64 {
self.0.round() as u64
}
}
impl JsonSchema for Uint {
fn schema_name() -> String {
"Uint".to_string()
}
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
gen.subschema_for::<u32>()
}
}

View File

@ -1,56 +0,0 @@
// Lego Brick
const lbumps = 10 // number of bumps long
const wbumps = 6 // number of bumps wide
const pitch = 8.0
const clearance = 0.1
const bumpDiam = 4.8
const bumpHeight = 1.8
const height = 3.2
const t = (pitch - (2 * clearance) - bumpDiam) / 2.0
const postDiam = pitch - t // works out to 6.5
const total_length = lbumps * pitch - (2.0 * clearance)
const total_width = wbumps * pitch - (2.0 * clearance)
const lSegments = total_length / (lbumps + 1)
const wSegments = total_width / (wbumps + 1)
// make the base
const s = startSketchOn('XY')
|> startProfileAt([-total_width / 2, -total_length / 2], %)
|> line([total_width, 0], %)
|> line([0, total_length], %)
|> line([-total_width, 0], %)
|> close(%)
|> extrude(height, %)
const shellExtrude = startSketchOn(s, "start")
|> startProfileAt([
-(total_width / 2 - t),
-(total_length / 2 - t)
], %)
|> line([total_width - (2 * t), 0], %)
|> line([0, total_length - (2 * t)], %)
|> line([-(total_width - (2 * t)), 0], %)
|> close(%)
|> extrude(-(height - t), %)
const peg = startSketchOn(s, "end")
|> circle([
-(total_width / 2 - wSegments),
-(total_length / 2 - lSegments)
], bumpDiam / 2, %)
|> patternLinear2d({
axis: [1, 0],
repetitions: 5,
distance: 7
}, %)
|> patternLinear2d({
axis: [0, 1],
repetitions: 9,
distance: 7
}, %)
|> extrude(bumpHeight, %)

View File

@ -115,15 +115,6 @@ async fn serial_test_riddle_small() {
twenty_twenty::assert_image("tests/executor/outputs/riddle_small.png", &result, 0.999);
}
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_lego() {
let code = include_str!("inputs/lego.kcl");
let result = execute_and_snapshot(code, kittycad::types::UnitLength::Mm)
.await
.unwrap();
twenty_twenty::assert_image("tests/executor/outputs/lego.png", &result, 0.999);
}
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_pentagon_fillet_desugar() {
let code = include_str!("inputs/pentagon_fillet_desugar.kcl");
@ -939,32 +930,11 @@ async fn serial_test_top_level_expression() {
twenty_twenty::assert_image("tests/executor/outputs/top_level_expression.png", &result, 0.999);
}
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_patterns_linear_basic_with_math() {
let code = r#"const num = 12
const distance = 5
const part = startSketchOn('XY')
|> circle([0,0], 2, %)
|> patternLinear2d({axis: [0,1], repetitions: num -1, distance: distance - 1}, %)
|> extrude(1, %)
"#;
let result = execute_and_snapshot(code, kittycad::types::UnitLength::Mm)
.await
.unwrap();
twenty_twenty::assert_image(
"tests/executor/outputs/patterns_linear_basic_with_math.png",
&result,
0.999,
);
}
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_patterns_linear_basic() {
let code = r#"const part = startSketchOn('XY')
|> circle([0,0], 2, %)
|> patternLinear2d({axis: [0,1], repetitions: 12, distance: 4}, %)
|> extrude(1, %)
|> patternLinear2d({axis: [0,1], repetitions: 12, distance: 2}, %)
"#;
let result = execute_and_snapshot(code, kittycad::types::UnitLength::Mm)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB