Compare commits
10 Commits
kcl-0.2.18
...
kurt-refac
Author | SHA1 | Date | |
---|---|---|---|
dc9000f6c7 | |||
70694d9dd3 | |||
6595fca000 | |||
8b0b5a0215 | |||
2263958fd0 | |||
66e60f2ddb | |||
5f51a0f569 | |||
aee1d66e56 | |||
1d1bb8cee0 | |||
c7dd89e720 |
File diff suppressed because one or more lines are too long
195
e2e/playwright/testing-samples-loading.spec.ts
Normal file
195
e2e/playwright/testing-samples-loading.spec.ts
Normal file
@ -0,0 +1,195 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { getUtils, setup, setupElectron, tearDown } from './test-utils'
|
||||
import { bracket } from 'lib/exampleKcl'
|
||||
import * as fsp from 'fs/promises'
|
||||
import { join } from 'path'
|
||||
import { FILE_EXT } from 'lib/constants'
|
||||
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test.describe('Testing in-app sample loading', () => {
|
||||
/**
|
||||
* Note this test implicitly depends on the KCL sample "flange-with-patterns.kcl"
|
||||
* and its title. https://github.com/KittyCAD/kcl-samples/blob/main/flange-with-patterns/flange-with-patterns.kcl
|
||||
*/
|
||||
test('Web: should overwrite current code, cannot create new file', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
await test.step(`Test setup`, async () => {
|
||||
await page.addInitScript((code) => {
|
||||
window.localStorage.setItem('persistCode', code)
|
||||
}, bracket)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
})
|
||||
|
||||
// Locators and constants
|
||||
const newSample = {
|
||||
file: 'flange-with-patterns' + FILE_EXT,
|
||||
title: 'Flange',
|
||||
}
|
||||
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
||||
const samplesCommandOption = page.getByRole('option', {
|
||||
name: 'Open Sample',
|
||||
})
|
||||
const commandSampleOption = page.getByRole('option', {
|
||||
name: newSample.title,
|
||||
exact: true,
|
||||
})
|
||||
const commandMethodArgButton = page.getByRole('button', {
|
||||
name: 'Method',
|
||||
})
|
||||
const commandMethodOption = (name: 'Overwrite' | 'Create new file') =>
|
||||
page.getByRole('option', {
|
||||
name,
|
||||
})
|
||||
const warningText = page.getByText('Overwrite current file?')
|
||||
const confirmButton = page.getByRole('button', { name: 'Submit command' })
|
||||
const codeLocator = page.locator('.cm-content')
|
||||
|
||||
await test.step(`Precondition: check the initial code`, async () => {
|
||||
await u.openKclCodePanel()
|
||||
await expect(codeLocator).toContainText(bracket.split('\n')[0])
|
||||
})
|
||||
|
||||
await test.step(`Load a KCL sample with the command palette`, async () => {
|
||||
await commandBarButton.click()
|
||||
await samplesCommandOption.click()
|
||||
await commandSampleOption.click()
|
||||
await commandMethodArgButton.click()
|
||||
await expect(commandMethodOption('Create new file')).not.toBeVisible()
|
||||
await commandMethodOption('Overwrite').click()
|
||||
await expect(warningText).toBeVisible()
|
||||
await confirmButton.click()
|
||||
|
||||
await expect(codeLocator).toContainText('// ' + newSample.title)
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Note this test implicitly depends on the KCL samples:
|
||||
* "flange-with-patterns.kcl": https://github.com/KittyCAD/kcl-samples/blob/main/flange-with-patterns/flange-with-patterns.kcl
|
||||
* "gear-rack.kcl": https://github.com/KittyCAD/kcl-samples/blob/main/gear-rack/gear-rack.kcl
|
||||
*/
|
||||
test(
|
||||
'Desktop: should create new file by default, optionally overwrite',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName: _ }, testInfo) => {
|
||||
const { electronApp, page, dir } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.writeFile(join(bracketDir, 'main.kcl'), bracket, {
|
||||
encoding: 'utf-8',
|
||||
})
|
||||
},
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Locators and constants
|
||||
const sampleOne = {
|
||||
file: 'flange-with-patterns' + FILE_EXT,
|
||||
title: 'Flange',
|
||||
}
|
||||
const sampleTwo = {
|
||||
file: 'gear-rack' + FILE_EXT,
|
||||
title: '100mm Gear Rack',
|
||||
}
|
||||
const projectCard = page.getByRole('link', { name: 'bracket' })
|
||||
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
||||
const commandOption = page.getByRole('option', { name: 'Open Sample' })
|
||||
const commandSampleOption = (name: string) =>
|
||||
page.getByRole('option', {
|
||||
name,
|
||||
exact: true,
|
||||
})
|
||||
const commandMethodArgButton = page.getByRole('button', {
|
||||
name: 'Method',
|
||||
})
|
||||
const commandMethodOption = page.getByRole('option', {
|
||||
name: 'Overwrite',
|
||||
})
|
||||
const newFileWarning = page.getByText(
|
||||
'Create a new file with the example code?'
|
||||
)
|
||||
const overwriteWarning = page.getByText('Overwrite current file?')
|
||||
const confirmButton = page.getByRole('button', { name: 'Submit command' })
|
||||
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||
const newlyCreatedFile = (name: string) =>
|
||||
page.getByRole('listitem').filter({
|
||||
has: page.getByRole('button', { name }),
|
||||
})
|
||||
const codeLocator = page.locator('.cm-content')
|
||||
|
||||
await test.step(`Test setup`, async () => {
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await projectCard.click()
|
||||
await u.waitForPageLoad()
|
||||
})
|
||||
|
||||
await test.step(`Precondition: check the initial code`, async () => {
|
||||
await u.openKclCodePanel()
|
||||
await expect(codeLocator).toContainText(bracket.split('\n')[0])
|
||||
await u.openFilePanel()
|
||||
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
await expect(newlyCreatedFile(sampleOne.file)).not.toBeVisible()
|
||||
})
|
||||
|
||||
await test.step(`Load a KCL sample with the command palette`, async () => {
|
||||
await commandBarButton.click()
|
||||
await commandOption.click()
|
||||
await commandSampleOption(sampleOne.title).click()
|
||||
await expect(overwriteWarning).not.toBeVisible()
|
||||
await expect(newFileWarning).toBeVisible()
|
||||
await confirmButton.click()
|
||||
})
|
||||
|
||||
await test.step(`Ensure we made and opened a new file`, async () => {
|
||||
await expect(codeLocator).toContainText('// ' + sampleOne.title)
|
||||
await expect(newlyCreatedFile(sampleOne.file)).toBeVisible()
|
||||
await expect(projectMenuButton).toContainText(sampleOne.file)
|
||||
})
|
||||
|
||||
await test.step(`Now overwrite the current file`, async () => {
|
||||
await commandBarButton.click()
|
||||
await commandOption.click()
|
||||
await commandSampleOption(sampleTwo.title).click()
|
||||
await commandMethodArgButton.click()
|
||||
await commandMethodOption.click()
|
||||
await expect(commandMethodArgButton).toContainText('overwrite')
|
||||
await expect(newFileWarning).not.toBeVisible()
|
||||
await expect(overwriteWarning).toBeVisible()
|
||||
await confirmButton.click()
|
||||
})
|
||||
|
||||
await test.step(`Ensure we overwrote the current file without navigating`, async () => {
|
||||
await expect(codeLocator).toContainText('// ' + sampleTwo.title)
|
||||
await test.step(`Check actual file contents`, async () => {
|
||||
await expect
|
||||
.poll(async () => {
|
||||
return await fsp.readFile(
|
||||
join(dir, 'bracket', sampleOne.file),
|
||||
'utf-8'
|
||||
)
|
||||
})
|
||||
.toContain('// ' + sampleTwo.title)
|
||||
})
|
||||
await expect(newlyCreatedFile(sampleOne.file)).toBeVisible()
|
||||
await expect(newlyCreatedFile(sampleTwo.file)).not.toBeVisible()
|
||||
await expect(projectMenuButton).toContainText(sampleOne.file)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
})
|
@ -13,9 +13,6 @@ test.afterEach(async ({ page }, testInfo) => {
|
||||
test('Units menu', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/')
|
||||
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
const unitsMenuButton = page.getByRole('button', {
|
||||
|
4
interface.d.ts
vendored
4
interface.d.ts
vendored
@ -63,6 +63,10 @@ export interface IElectronAPI {
|
||||
kittycad: (access: string, args: any) => any
|
||||
listMachines: () => Promise<MachinesListing>
|
||||
getMachineApiIp: () => Promise<string | null>
|
||||
onUpdateDownloaded: (
|
||||
callback: (value: string) => void
|
||||
) => Electron.IpcRenderer
|
||||
appRestart: () => void
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "zoo-modeling-app",
|
||||
"version": "0.25.3",
|
||||
"version": "0.25.4",
|
||||
"private": true,
|
||||
"productName": "Zoo Modeling App",
|
||||
"author": {
|
||||
@ -83,6 +83,7 @@
|
||||
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages",
|
||||
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages",
|
||||
"fetch:wasm": "./get-latest-wasm-bundle.sh",
|
||||
"fetch:samples": "echo \"Fetching latest KCL samples...\" && curl -o public/kcl-samples-manifest-fallback.json https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/manifest.json",
|
||||
"isomorphic-copy-wasm": "(copy src/wasm-lib/pkg/wasm_lib_bg.wasm public || cp src/wasm-lib/pkg/wasm_lib_bg.wasm public)",
|
||||
"build:wasm-dev": "yarn wasm-prep && (cd src/wasm-lib && wasm-pack build --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && yarn isomorphic-copy-wasm && yarn fmt",
|
||||
"build:wasm": "yarn wasm-prep && cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings && cd ../.. && yarn isomorphic-copy-wasm && yarn fmt",
|
||||
@ -90,7 +91,7 @@
|
||||
"wasm-prep": "rimraf src/wasm-lib/pkg && mkdirp src/wasm-lib/pkg && rimraf src/wasm-lib/kcl/bindings",
|
||||
"lint": "eslint --fix src e2e packages/codemirror-lsp-client",
|
||||
"bump-jsons": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json",
|
||||
"postinstall": "yarn xstate:typegen && ./node_modules/.bin/electron-rebuild",
|
||||
"postinstall": "yarn fetch:samples && yarn xstate:typegen && ./node_modules/.bin/electron-rebuild",
|
||||
"xstate:typegen": "yarn xstate typegen \"src/**/*.ts?(x)\"",
|
||||
"make:dev": "make dev",
|
||||
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts",
|
||||
|
152
public/kcl-samples-manifest-fallback.json
Normal file
152
public/kcl-samples-manifest-fallback.json
Normal file
@ -0,0 +1,152 @@
|
||||
[
|
||||
{
|
||||
"file": "80-20-rail.kcl",
|
||||
"title": "80/20 Rail",
|
||||
"description": "An 80/20 extruded aluminum linear rail. T-slot profile adjustable by profile height, rail length, and origin position"
|
||||
},
|
||||
{
|
||||
"file": "a-parametric-bearing-pillow-block.kcl",
|
||||
"title": "A Parametric Bearing Pillow Block",
|
||||
"description": "A bearing pillow block, also known as a plummer block or pillow block bearing, is a pedestal used to provide support for a rotating shaft with the help of compatible bearings and various accessories. Housing a bearing, the pillow block provides a secure and stable foundation that allows the shaft to rotate smoothly within its machinery setup. These components are essential in a wide range of mechanical systems and machinery, playing a key role in reducing friction and supporting radial and axial loads."
|
||||
},
|
||||
{
|
||||
"file": "ball-bearing.kcl",
|
||||
"title": "Ball Bearing",
|
||||
"description": "A ball bearing is a type of rolling-element bearing that uses balls to maintain the separation between the bearing races. The primary purpose of a ball bearing is to reduce rotational friction and support radial and axial loads."
|
||||
},
|
||||
{
|
||||
"file": "bracket.kcl",
|
||||
"title": "Shelf Bracket",
|
||||
"description": "This is a bracket that holds a shelf. It is made of aluminum and is designed to hold a force of 300 lbs. The bracket is 6 inches wide and the force is applied at the end of the shelf, 12 inches from the wall. The bracket has a factor of safety of 1.2. The legs of the bracket are 5 inches and 2 inches long. The thickness of the bracket is calculated from the constraints provided."
|
||||
},
|
||||
{
|
||||
"file": "brake-caliper.kcl",
|
||||
"title": "Brake Caliper",
|
||||
"description": "Brake calipers are used to squeeze the brake pads against the rotor, causing larger and larger amounts of friction depending on how hard the brakes are pressed."
|
||||
},
|
||||
{
|
||||
"file": "car-wheel.kcl",
|
||||
"title": "Car Wheel",
|
||||
"description": "A sports car wheel with a circular lug pattern and spokes."
|
||||
},
|
||||
{
|
||||
"file": "car-wheel-assembly.kcl",
|
||||
"title": "Car Wheel Assembly",
|
||||
"description": "A car wheel assembly with a rotor, tire, and lug nuts."
|
||||
},
|
||||
{
|
||||
"file": "enclosure.kcl",
|
||||
"title": "Enclosure",
|
||||
"description": "An enclosure body and sealing lid for storing items"
|
||||
},
|
||||
{
|
||||
"file": "flange-with-patterns.kcl",
|
||||
"title": "Flange",
|
||||
"description": "A flange is a flat rim, collar, or rib, typically forged or cast, that is used to strengthen an object, guide it, or attach it to another object. Flanges are known for their use in various applications, including piping, plumbing, and mechanical engineering, among others."
|
||||
},
|
||||
{
|
||||
"file": "flange-xy.kcl",
|
||||
"title": "Flange with XY coordinates",
|
||||
"description": "A flange is a flat rim, collar, or rib, typically forged or cast, that is used to strengthen an object, guide it, or attach it to another object. Flanges are known for their use in various applications, including piping, plumbing, and mechanical engineering, among others."
|
||||
},
|
||||
{
|
||||
"file": "focusrite-scarlett-mounting-bracket.kcl",
|
||||
"title": "A mounting bracket for the Focusrite Scarlett Solo audio interface",
|
||||
"description": "This is a bracket that holds an audio device underneath a desk or shelf. The audio device has dimensions of 144mm wide, 80mm length and 45mm depth with fillets of 6mm. This mounting bracket is designed to be 3D printed with PLA material"
|
||||
},
|
||||
{
|
||||
"file": "french-press.kcl",
|
||||
"title": "French Press",
|
||||
"description": "A french press immersion coffee maker"
|
||||
},
|
||||
{
|
||||
"file": "gear.kcl",
|
||||
"title": "Gear",
|
||||
"description": "A rotating machine part having cut teeth or, in the case of a cogwheel, inserted teeth (called cogs), which mesh with another toothed part to transmit torque. Geared devices can change the speed, torque, and direction of a power source. The two elements that define a gear are its circular shape and the teeth that are integrated into its outer edge, which are designed to fit into the teeth of another gear."
|
||||
},
|
||||
{
|
||||
"file": "gear-rack.kcl",
|
||||
"title": "100mm Gear Rack",
|
||||
"description": "A flat bar or rail that is engraved with teeth along its length. These teeth are designed to mesh with the teeth of a gear, known as a pinion. When the pinion, a small cylindrical gear, rotates, its teeth engage with the teeth on the rack, causing the rack to move linearly. Conversely, linear motion applied to the rack will cause the pinion to rotate."
|
||||
},
|
||||
{
|
||||
"file": "hex-nut.kcl",
|
||||
"title": "Hex nut",
|
||||
"description": "A hex nut is a type of fastener with a threaded hole and a hexagonal outer shape, used in a wide variety of applications to secure parts together. The hexagonal shape allows for a greater torque to be applied with wrenches or tools, making it one of the most common nut types in hardware."
|
||||
},
|
||||
{
|
||||
"file": "kitt.kcl",
|
||||
"title": "Kitt",
|
||||
"description": "The beloved KittyCAD mascot in a voxelized style."
|
||||
},
|
||||
{
|
||||
"file": "lego.kcl",
|
||||
"title": "Lego Brick",
|
||||
"description": "A standard Lego brick. This is a small, plastic construction block toy that can be interlocked with other blocks to build various structures, models, and figures. There are a lot of hacks used in this code."
|
||||
},
|
||||
{
|
||||
"file": "lug-nut.kcl",
|
||||
"title": "Lug Nut",
|
||||
"description": "lug Nuts are essential components used to create secure connections, whether for electrical purposes, like terminating wires or grounding, or for mechanical purposes, such as providing mounting points or reinforcing structural joints."
|
||||
},
|
||||
{
|
||||
"file": "mounting-plate.kcl",
|
||||
"title": "Mounting Plate",
|
||||
"description": "A flat piece of material, often metal or plastic, that serves as a support or base for attaching, securing, or mounting various types of equipment, devices, or components."
|
||||
},
|
||||
{
|
||||
"file": "multi-axis-robot.kcl",
|
||||
"title": "Robot Arm",
|
||||
"description": "A 4 axis robotic arm for industrial use. These machines can be used for assembly, packaging, organization of goods, and quality inspection processes"
|
||||
},
|
||||
{
|
||||
"file": "pipe.kcl",
|
||||
"title": "Pipe",
|
||||
"description": "A tubular section or hollow cylinder, usually but not necessarily of circular cross-section, used mainly to convey substances that can flow."
|
||||
},
|
||||
{
|
||||
"file": "pipe-flange-assembly.kcl",
|
||||
"title": "Pipe and Flange Assembly",
|
||||
"description": "A crucial component in various piping systems, designed to facilitate the connection, disconnection, and access to piping for inspection, cleaning, and modifications. This assembly combines pipes (long cylindrical conduits) with flanges (plate-like fittings) to create a secure yet detachable joint."
|
||||
},
|
||||
{
|
||||
"file": "poopy-shoe.kcl",
|
||||
"title": "Poopy Shoe",
|
||||
"description": "poop shute for bambu labs printer - optimized for printing."
|
||||
},
|
||||
{
|
||||
"file": "router-template-cross-bar.kcl",
|
||||
"title": "Router template for a cross bar",
|
||||
"description": "A guide for routing a notch into a cross bar."
|
||||
},
|
||||
{
|
||||
"file": "router-template-slate.kcl",
|
||||
"title": "Router template for a slate",
|
||||
"description": "A guide for routing a slate for a cross bar."
|
||||
},
|
||||
{
|
||||
"file": "sheet-metal-bracket.kcl",
|
||||
"title": "Sheet Metal Bracket",
|
||||
"description": "A component typically made from flat sheet metal through various manufacturing processes such as bending, punching, cutting, and forming. These brackets are used to support, attach, or mount other hardware components, often providing a structural or functional base for assembly."
|
||||
},
|
||||
{
|
||||
"file": "socket-head-cap-screw.kcl",
|
||||
"title": "Socket Head Cap Screw",
|
||||
"description": "This is for a #10-24 screw that is 1.00 inches long. A socket head cap screw is a type of fastener that is widely used in a variety of applications requiring a high strength fastening solution. It is characterized by its cylindrical head and internal hexagonal drive, which allows for tightening with an Allen wrench or hex key."
|
||||
},
|
||||
{
|
||||
"file": "tire.kcl",
|
||||
"title": "Tire",
|
||||
"description": "A tire is a critical component of a vehicle that provides the necessary traction and grip between the car and the road. It supports the vehicle's weight and absorbs shocks from road irregularities."
|
||||
},
|
||||
{
|
||||
"file": "washer.kcl",
|
||||
"title": "Washer",
|
||||
"description": "A small, typically disk-shaped component with a hole in the middle, used in a wide range of applications, primarily in conjunction with fasteners like bolts and screws. Washers distribute the load of a fastener across a broader area. This is especially important when the fastening surface is soft or uneven, as it helps to prevent damage to the surface and ensures the load is evenly distributed, reducing the risk of the fastener becoming loose over time."
|
||||
},
|
||||
{
|
||||
"file": "wheel-rotor.kcl",
|
||||
"title": "Wheel rotor",
|
||||
"description": "A component of a disc brake system. It provides a surface for brake pads to press against, generating the friction needed to slow or stop the vehicle."
|
||||
}
|
||||
]
|
@ -22,6 +22,7 @@ import {
|
||||
} from 'lib/toolbar'
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
||||
import { convertSelectionsToOld } from 'lib/selections'
|
||||
|
||||
export function Toolbar({
|
||||
className = '',
|
||||
@ -38,12 +39,17 @@ export function Toolbar({
|
||||
'!border-transparent hover:!border-chalkboard-20 dark:enabled:hover:!border-primary pressed:!border-primary ui-open:!border-primary'
|
||||
|
||||
const sketchPathId = useMemo(() => {
|
||||
if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast)) {
|
||||
if (
|
||||
!isSingleCursorInPipe(
|
||||
convertSelectionsToOld(context.selectionRanges),
|
||||
kclManager.ast
|
||||
)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
return isCursorInSketchCommandRange(
|
||||
engineCommandManager.artifactGraph,
|
||||
context.selectionRanges
|
||||
convertSelectionsToOld(context.selectionRanges)
|
||||
)
|
||||
}, [engineCommandManager.artifactGraph, context.selectionRanges])
|
||||
|
||||
|
@ -77,7 +77,7 @@ import {
|
||||
createPipeSubstitution,
|
||||
findUniqueName,
|
||||
} from 'lang/modifyAst'
|
||||
import { Selections, getEventForSegmentSelection } from 'lib/selections'
|
||||
import { Selections__old, getEventForSegmentSelection } from 'lib/selections'
|
||||
import { createGridHelper, orthoScale, perspScale } from './helpers'
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
@ -374,7 +374,7 @@ export class SceneEntities {
|
||||
forward: [number, number, number]
|
||||
up: [number, number, number]
|
||||
position?: [number, number, number]
|
||||
selectionRanges?: Selections
|
||||
selectionRanges?: Selections__old
|
||||
}): Promise<{
|
||||
truncatedAst: Program
|
||||
programMemoryOverride: ProgramMemory
|
||||
@ -1171,6 +1171,7 @@ export class SceneEntities {
|
||||
},
|
||||
onMove: () => {},
|
||||
onClick: (args) => {
|
||||
console.log('onClick', args)
|
||||
if (args?.mouseEvent.which !== 1) return
|
||||
if (!args || !args.selected) {
|
||||
sceneInfra.modelingSend({
|
||||
@ -1183,6 +1184,7 @@ export class SceneEntities {
|
||||
}
|
||||
const { selected } = args
|
||||
const event = getEventForSegmentSelection(selected)
|
||||
console.log('event', event)
|
||||
if (!event) return
|
||||
sceneInfra.modelingSend(event)
|
||||
},
|
||||
|
42
src/components/ActionButton.test.tsx
Normal file
42
src/components/ActionButton.test.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { ActionButton } from './ActionButton'
|
||||
|
||||
describe('ActionButton tests', () => {
|
||||
it('ActionButton with no iconStart or iconEnd should have even left and right padding', () => {
|
||||
render(<ActionButton Element="button">No icons</ActionButton>)
|
||||
expect(screen.getByRole('button')).toHaveClass('px-2')
|
||||
})
|
||||
|
||||
it('ActionButton with iconStart should have no padding on the left', () => {
|
||||
render(
|
||||
<ActionButton Element="button" iconStart={{ icon: 'trash' }}>
|
||||
Start icon only
|
||||
</ActionButton>
|
||||
)
|
||||
expect(screen.getByRole('button')).toHaveClass('pr-2')
|
||||
})
|
||||
|
||||
it('ActionButton with iconEnd should have no padding on the right', () => {
|
||||
render(
|
||||
<ActionButton Element="button" iconEnd={{ icon: 'trash' }}>
|
||||
End icon only
|
||||
</ActionButton>
|
||||
)
|
||||
expect(screen.getByRole('button')).toHaveClass('pl-2')
|
||||
})
|
||||
|
||||
it('ActionButton with both icons should have no padding on either side', () => {
|
||||
render(
|
||||
<ActionButton
|
||||
Element="button"
|
||||
iconStart={{ icon: 'trash' }}
|
||||
iconEnd={{ icon: 'trash' }}
|
||||
>
|
||||
Both icons
|
||||
</ActionButton>
|
||||
)
|
||||
expect(screen.getByRole('button')).not.toHaveClass('px-2')
|
||||
expect(screen.getByRole('button')).toHaveClass('px-0')
|
||||
})
|
||||
})
|
@ -44,11 +44,11 @@ export const ActionButton = forwardRef((props: ActionButtonProps, ref) => {
|
||||
const classNames = `action-button p-0 m-0 group mono text-xs leading-none flex items-center gap-2 rounded-sm border-solid border border-chalkboard-30 hover:border-chalkboard-40 enabled:dark:border-chalkboard-70 dark:hover:border-chalkboard-60 dark:bg-chalkboard-90/50 text-chalkboard-100 dark:text-chalkboard-10 ${
|
||||
props.iconStart
|
||||
? props.iconEnd
|
||||
? 'px-0'
|
||||
: 'pr-2'
|
||||
? 'px-0' // No padding if both icons are present
|
||||
: 'pr-2' // Padding on the right if only the start icon is present
|
||||
: props.iconEnd
|
||||
? 'px-2'
|
||||
: 'pl-2'
|
||||
? 'pl-2' // Padding on the left if only the end icon is present
|
||||
: 'px-2' // Padding on both sides if no icons are present
|
||||
} ${props.className ? props.className : ''}`
|
||||
|
||||
switch (props.Element) {
|
||||
|
@ -12,6 +12,7 @@ import { useKclContext } from 'lang/KclProvider'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { executeAst } from 'lang/langHelpers'
|
||||
import { trap } from 'lib/trap'
|
||||
import { convertSelectionsToOld } from 'lib/selections'
|
||||
|
||||
export const AvailableVars = ({
|
||||
onVarClick,
|
||||
@ -96,7 +97,8 @@ export function useCalc({
|
||||
} {
|
||||
const { programMemory } = useKclContext()
|
||||
const { context } = useModelingContext()
|
||||
const selectionRange = context.selectionRanges.codeBasedSelections[0].range
|
||||
const selectionRange = convertSelectionsToOld(context.selectionRanges)
|
||||
.codeBasedSelections[0].range
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const [availableVarInfo, setAvailableVarInfo] = useState<
|
||||
ReturnType<typeof findAllPreviousVariables>
|
||||
|
@ -6,8 +6,8 @@ import { CommandArgument, CommandArgumentOption } from 'lib/commandTypes'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { AnyStateMachine, StateFrom } from 'xstate'
|
||||
|
||||
const contextSelector = (snapshot: StateFrom<AnyStateMachine>) =>
|
||||
snapshot.context
|
||||
const contextSelector = (snapshot: StateFrom<AnyStateMachine> | undefined) =>
|
||||
snapshot?.context
|
||||
|
||||
function CommandArgOptionInput({
|
||||
arg,
|
||||
|
@ -2,7 +2,7 @@ import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { CustomIcon } from '../CustomIcon'
|
||||
import React, { useState } from 'react'
|
||||
import { ActionButton } from '../ActionButton'
|
||||
import { Selections, getSelectionTypeDisplayText } from 'lib/selections'
|
||||
import { Selections__old, getSelectionTypeDisplayText } from 'lib/selections'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { KclCommandValue, KclExpressionWithVariable } from 'lib/commandTypes'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
@ -125,7 +125,9 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
|
||||
<span data-testid="header-arg-value">
|
||||
{argValue ? (
|
||||
arg.inputType === 'selection' ? (
|
||||
getSelectionTypeDisplayText(argValue as Selections)
|
||||
getSelectionTypeDisplayText(
|
||||
argValue as Selections__old
|
||||
)
|
||||
) : arg.inputType === 'kcl' ? (
|
||||
roundOff(
|
||||
Number(
|
||||
@ -140,7 +142,11 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
|
||||
JSON.stringify(argValue)
|
||||
)
|
||||
) : (
|
||||
<em>{argValue}</em>
|
||||
<em>
|
||||
{arg.valueSummary
|
||||
? arg.valueSummary(argValue)
|
||||
: argValue}
|
||||
</em>
|
||||
)
|
||||
) : null}
|
||||
</span>
|
||||
|
@ -58,7 +58,17 @@ function CommandBarReview({ stepBack }: { stepBack: () => void }) {
|
||||
|
||||
return (
|
||||
<CommandBarHeader>
|
||||
<p className="px-4">Confirm {selectedCommand?.name}</p>
|
||||
<p className="px-4">
|
||||
{selectedCommand?.reviewMessage ? (
|
||||
selectedCommand.reviewMessage instanceof Function ? (
|
||||
selectedCommand.reviewMessage(commandBarState.context)
|
||||
) : (
|
||||
selectedCommand.reviewMessage
|
||||
)
|
||||
) : (
|
||||
<>Confirm {selectedCommand?.name}</>
|
||||
)}
|
||||
</p>
|
||||
<form
|
||||
id="review-form"
|
||||
className="absolute opacity-0 inset-0 pointer-events-none"
|
||||
|
@ -3,8 +3,10 @@ import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { CommandArgument } from 'lib/commandTypes'
|
||||
import {
|
||||
Selection,
|
||||
Selection__old,
|
||||
canSubmitSelectionArg,
|
||||
convertSelectionToOld,
|
||||
convertSelectionsToOld,
|
||||
getSelectionType,
|
||||
getSelectionTypeDisplayText,
|
||||
} from 'lib/selections'
|
||||
@ -12,13 +14,15 @@ import { modelingMachine } from 'machines/modelingMachine'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { StateFrom } from 'xstate'
|
||||
|
||||
const semanticEntityNames: { [key: string]: Array<Selection['type']> } = {
|
||||
const semanticEntityNames: { [key: string]: Array<Selection__old['type']> } = {
|
||||
face: ['extrude-wall', 'start-cap', 'end-cap'],
|
||||
edge: ['edge', 'line', 'arc'],
|
||||
point: ['point', 'line-end', 'line-mid'],
|
||||
}
|
||||
|
||||
function getSemanticSelectionType(selectionType: Array<Selection['type']>) {
|
||||
function getSemanticSelectionType(
|
||||
selectionType: Array<Selection__old['type']>
|
||||
) {
|
||||
const semanticSelectionType = new Set()
|
||||
selectionType.forEach((type) => {
|
||||
Object.entries(semanticEntityNames).forEach(([entity, entityTypes]) => {
|
||||
@ -31,8 +35,8 @@ function getSemanticSelectionType(selectionType: Array<Selection['type']>) {
|
||||
return Array.from(semanticSelectionType)
|
||||
}
|
||||
|
||||
const selectionSelector = (snapshot: StateFrom<typeof modelingMachine>) =>
|
||||
snapshot.context.selectionRanges
|
||||
const selectionSelector = (snapshot?: StateFrom<typeof modelingMachine>) =>
|
||||
snapshot?.context.selectionRanges
|
||||
|
||||
function CommandBarSelectionInput({
|
||||
arg,
|
||||
@ -49,10 +53,14 @@ function CommandBarSelectionInput({
|
||||
const [hasSubmitted, setHasSubmitted] = useState(false)
|
||||
const selection = useSelector(arg.machineActor, selectionSelector)
|
||||
const selectionsByType = useMemo(() => {
|
||||
const selectionRangeEnd = selection.codeBasedSelections[0]?.range[1]
|
||||
return !selectionRangeEnd || selectionRangeEnd === code.length
|
||||
const selectionRangeEnd = !selection
|
||||
? null
|
||||
: convertSelectionsToOld(selection)?.codeBasedSelections[0]?.range[1]
|
||||
return !selectionRangeEnd || selectionRangeEnd === code.length || !selection
|
||||
? 'none'
|
||||
: getSelectionType(selection)
|
||||
: !selection
|
||||
? 'none'
|
||||
: getSelectionType(convertSelectionsToOld(selection))
|
||||
}, [selection, code])
|
||||
const canSubmitSelection = useMemo<boolean>(
|
||||
() => canSubmitSelectionArg(selectionsByType, arg),
|
||||
@ -87,6 +95,8 @@ function CommandBarSelectionInput({
|
||||
onSubmit(selection)
|
||||
}
|
||||
|
||||
const selectionOld = selection && convertSelectionsToOld(selection)
|
||||
|
||||
return (
|
||||
<form id="arg-form" onSubmit={handleSubmit}>
|
||||
<label
|
||||
@ -96,7 +106,7 @@ function CommandBarSelectionInput({
|
||||
}
|
||||
>
|
||||
{canSubmitSelection
|
||||
? getSelectionTypeDisplayText(selection) + ' selected'
|
||||
? getSelectionTypeDisplayText(selectionOld) + ' selected'
|
||||
: `Please select ${
|
||||
arg.multiple ? 'one or more ' : 'one '
|
||||
}${getSemanticSelectionType(arg.selectionTypes).join(' or ')}`}
|
||||
|
16
src/components/CommandBarOverwriteWarning.tsx
Normal file
16
src/components/CommandBarOverwriteWarning.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
interface CommandBarOverwriteWarningProps {
|
||||
heading?: string
|
||||
message?: string
|
||||
}
|
||||
|
||||
export function CommandBarOverwriteWarning({
|
||||
heading = 'Overwrite current file?',
|
||||
message = 'This will permanently replace the current code in the editor.',
|
||||
}: CommandBarOverwriteWarningProps) {
|
||||
return (
|
||||
<>
|
||||
<p className="font-bold text-destroy-60">{heading}</p>
|
||||
<p>{message}</p>
|
||||
</>
|
||||
)
|
||||
}
|
@ -21,6 +21,7 @@ export const EngineCommands = () => {
|
||||
const [engineCommands, clearEngineCommands] = useEngineCommands()
|
||||
const [containsFilter, setContainsFilter] = useState('')
|
||||
const [customCmd, setCustomCmd] = useState('')
|
||||
console.log(JSON.stringify(engineCommands, null, 2))
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
@ -64,7 +65,10 @@ export const EngineCommands = () => {
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<button data-testid="clear-commands" onClick={clearEngineCommands}>
|
||||
<button
|
||||
data-testid="clear-commands"
|
||||
onClick={() => clearEngineCommands()}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
<br />
|
||||
|
@ -2,7 +2,7 @@ import { useMachine } from '@xstate/react'
|
||||
import { useNavigate, useRouteLoaderData } from 'react-router-dom'
|
||||
import { type IndexLoaderData } from 'lib/types'
|
||||
import { PATHS } from 'lib/paths'
|
||||
import React, { createContext } from 'react'
|
||||
import React, { createContext, useEffect, useMemo } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import {
|
||||
Actor,
|
||||
@ -22,6 +22,12 @@ import {
|
||||
} from 'lib/constants'
|
||||
import { getProjectInfo } from 'lib/desktop'
|
||||
import { getNextDirName, getNextFileName } from 'lib/desktopFS'
|
||||
import { kclCommands } from 'lib/kclCommands'
|
||||
import { codeManager, kclManager } from 'lib/singletons'
|
||||
import {
|
||||
getKclSamplesManifest,
|
||||
KclSamplesManifestItem,
|
||||
} from 'lib/getKclSamplesManifest'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -41,6 +47,16 @@ export const FileMachineProvider = ({
|
||||
const navigate = useNavigate()
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const { project, file } = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
|
||||
const [kclSamples, setKclSamples] = React.useState<KclSamplesManifestItem[]>(
|
||||
[]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchKclSamples() {
|
||||
setKclSamples(await getKclSamplesManifest())
|
||||
}
|
||||
fetchKclSamples().catch(reportError)
|
||||
}, [])
|
||||
|
||||
const [state, send] = useMachine(
|
||||
fileMachine.provide({
|
||||
@ -121,6 +137,7 @@ export const FileMachineProvider = ({
|
||||
return {
|
||||
message: `Successfully created "${createdName}"`,
|
||||
path: createdPath,
|
||||
shouldSetToRename: input.shouldSetToRename,
|
||||
}
|
||||
}),
|
||||
createFile: fromPromise(async ({ input }) => {
|
||||
@ -271,6 +288,46 @@ export const FileMachineProvider = ({
|
||||
}
|
||||
)
|
||||
|
||||
const kclCommandMemo = useMemo(
|
||||
() =>
|
||||
kclCommands(
|
||||
async (data) => {
|
||||
if (data.method === 'overwrite') {
|
||||
codeManager.updateCodeStateEditor(data.code)
|
||||
await kclManager.executeCode(true)
|
||||
await codeManager.writeToFile()
|
||||
} else if (data.method === 'newFile' && isDesktop()) {
|
||||
send({
|
||||
type: 'Create file',
|
||||
data: {
|
||||
name: data.sampleName,
|
||||
content: data.code,
|
||||
makeDir: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
kclSamples.map((sample) => ({
|
||||
value: sample.file,
|
||||
name: sample.title,
|
||||
}))
|
||||
).filter(
|
||||
(command) => kclSamples.length || command.name !== 'open-kcl-example'
|
||||
),
|
||||
[codeManager, kclManager, send, kclSamples]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
commandBarSend({ type: 'Add commands', data: { commands: kclCommandMemo } })
|
||||
|
||||
return () => {
|
||||
commandBarSend({
|
||||
type: 'Remove commands',
|
||||
data: { commands: kclCommandMemo },
|
||||
})
|
||||
}
|
||||
}, [commandBarSend, kclCommandMemo])
|
||||
|
||||
return (
|
||||
<FileContext.Provider
|
||||
value={{
|
||||
|
@ -393,14 +393,14 @@ export const FileTreeMenu = () => {
|
||||
function createFile() {
|
||||
send({
|
||||
type: 'Create file',
|
||||
data: { name: '', makeDir: false },
|
||||
data: { name: '', makeDir: false, shouldSetToRename: true },
|
||||
})
|
||||
}
|
||||
|
||||
function createFolder() {
|
||||
send({
|
||||
type: 'Create file',
|
||||
data: { name: '', makeDir: true },
|
||||
data: { name: '', makeDir: true, shouldSetToRename: true },
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -37,13 +37,17 @@ import {
|
||||
} from './Toolbar/SetAngleBetween'
|
||||
import { applyConstraintAngleLength } from './Toolbar/setAngleLength'
|
||||
import {
|
||||
Selections,
|
||||
Selections__old,
|
||||
canSweepSelection,
|
||||
handleSelectionBatch,
|
||||
isSelectionLastLine,
|
||||
isRangeBetweenCharacters,
|
||||
isSketchPipe,
|
||||
updateSelections,
|
||||
convertSelectionsToOld,
|
||||
convertSelectionToOld,
|
||||
Selections,
|
||||
updateSelections2,
|
||||
} from 'lib/selections'
|
||||
import { applyConstraintIntersect } from './Toolbar/Intersect'
|
||||
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
|
||||
@ -249,6 +253,7 @@ export const ModelingMachineProvider = ({
|
||||
'Set selection': assign(
|
||||
({ context: { selectionRanges, sketchDetails }, event }) => {
|
||||
// this was needed for ts after adding 'Set selection' action to on done modal events
|
||||
// const oldSelections = convertSelectionsToOld(selectionRanges)
|
||||
const setSelections =
|
||||
('data' in event &&
|
||||
event.data &&
|
||||
@ -275,8 +280,12 @@ export const ModelingMachineProvider = ({
|
||||
})
|
||||
})
|
||||
}
|
||||
// let selections: Selections__old = {
|
||||
// codeBasedSelections: [],
|
||||
// otherSelections: [],
|
||||
// }
|
||||
let selections: Selections = {
|
||||
codeBasedSelections: [],
|
||||
graphSelections: [],
|
||||
otherSelections: [],
|
||||
}
|
||||
if (setSelections.selectionType === 'singleCodeCursor') {
|
||||
@ -286,21 +295,28 @@ export const ModelingMachineProvider = ({
|
||||
!editorManager.isShiftDown
|
||||
) {
|
||||
selections = {
|
||||
codeBasedSelections: [],
|
||||
graphSelections: [],
|
||||
otherSelections: [],
|
||||
}
|
||||
} else if (
|
||||
setSelections.selection &&
|
||||
!editorManager.isShiftDown
|
||||
) {
|
||||
// const oldSelection = convertSelectionToOld(setSelections.selection)
|
||||
// if (oldSelection) {
|
||||
|
||||
// }
|
||||
selections = {
|
||||
codeBasedSelections: [setSelections.selection],
|
||||
graphSelections: [setSelections.selection],
|
||||
otherSelections: [],
|
||||
}
|
||||
} else if (setSelections.selection && editorManager.isShiftDown) {
|
||||
// const oldSelection = convertSelectionToOld(setSelections.selection)
|
||||
// if (oldSelection) {
|
||||
// }
|
||||
selections = {
|
||||
codeBasedSelections: [
|
||||
...selectionRanges.codeBasedSelections,
|
||||
graphSelections: [
|
||||
...selectionRanges.graphSelections,
|
||||
setSelections.selection,
|
||||
],
|
||||
otherSelections: selectionRanges.otherSelections,
|
||||
@ -312,7 +328,7 @@ export const ModelingMachineProvider = ({
|
||||
codeMirrorSelection,
|
||||
updateSceneObjectColors,
|
||||
} = handleSelectionBatch({
|
||||
selections,
|
||||
selections: convertSelectionsToOld(selections),
|
||||
})
|
||||
codeMirrorSelection && dispatchSelection(codeMirrorSelection)
|
||||
engineEvents &&
|
||||
@ -336,18 +352,18 @@ export const ModelingMachineProvider = ({
|
||||
if (setSelections.selectionType === 'otherSelection') {
|
||||
if (editorManager.isShiftDown) {
|
||||
selections = {
|
||||
codeBasedSelections: selectionRanges.codeBasedSelections,
|
||||
graphSelections: selectionRanges.graphSelections,
|
||||
otherSelections: [setSelections.selection],
|
||||
}
|
||||
} else {
|
||||
selections = {
|
||||
codeBasedSelections: [],
|
||||
graphSelections: [],
|
||||
otherSelections: [setSelections.selection],
|
||||
}
|
||||
}
|
||||
const { engineEvents, updateSceneObjectColors } =
|
||||
handleSelectionBatch({
|
||||
selections,
|
||||
selections: convertSelectionsToOld(selections),
|
||||
})
|
||||
engineEvents &&
|
||||
engineEvents.forEach((event) => {
|
||||
@ -360,7 +376,9 @@ export const ModelingMachineProvider = ({
|
||||
}
|
||||
}
|
||||
if (setSelections.selectionType === 'completeSelection') {
|
||||
editorManager.selectRange(setSelections.selection)
|
||||
editorManager.selectRange(
|
||||
convertSelectionsToOld(setSelections.selection)
|
||||
)
|
||||
if (!sketchDetails)
|
||||
return {
|
||||
selectionRanges: setSelections.selection,
|
||||
@ -494,10 +512,11 @@ export const ModelingMachineProvider = ({
|
||||
'has valid sweep selection': ({ context: { 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
|
||||
const _selections = convertSelectionsToOld(selectionRanges)
|
||||
const hasNoSelection =
|
||||
selectionRanges.codeBasedSelections.length === 0 ||
|
||||
isRangeBetweenCharacters(selectionRanges) ||
|
||||
isSelectionLastLine(selectionRanges, codeManager.code)
|
||||
_selections.codeBasedSelections.length === 0 ||
|
||||
isRangeBetweenCharacters(_selections) ||
|
||||
isSelectionLastLine(_selections, codeManager.code)
|
||||
|
||||
if (hasNoSelection) {
|
||||
// they have no selection, we should enable the button
|
||||
@ -505,31 +524,34 @@ export const ModelingMachineProvider = ({
|
||||
// BUT only if there's extrudable geometry
|
||||
return doesSceneHaveSweepableSketch(kclManager.ast)
|
||||
}
|
||||
if (!isSketchPipe(selectionRanges)) return false
|
||||
if (!isSketchPipe(_selections)) return false
|
||||
|
||||
return canSweepSelection(selectionRanges)
|
||||
return canSweepSelection(_selections)
|
||||
},
|
||||
'has valid selection for deletion': ({
|
||||
context: { selectionRanges },
|
||||
}) => {
|
||||
const _selections = convertSelectionsToOld(selectionRanges)
|
||||
if (!commandBarState.matches('Closed')) return false
|
||||
if (selectionRanges.codeBasedSelections.length <= 0) return false
|
||||
if (_selections.codeBasedSelections.length <= 0) return false
|
||||
return true
|
||||
},
|
||||
'has valid fillet selection': ({ context: { selectionRanges } }) =>
|
||||
hasValidFilletSelection({
|
||||
selectionRanges,
|
||||
'has valid fillet selection': ({ context: { selectionRanges } }) => {
|
||||
const _selections = convertSelectionsToOld(selectionRanges)
|
||||
return hasValidFilletSelection({
|
||||
selectionRanges: _selections,
|
||||
ast: kclManager.ast,
|
||||
code: codeManager.code,
|
||||
}),
|
||||
})
|
||||
},
|
||||
'Selection is on face': ({ context: { selectionRanges }, event }) => {
|
||||
if (event.type !== 'Enter sketch') return false
|
||||
if (event.data?.forceNewSketch) return false
|
||||
if (!isSingleCursorInPipe(selectionRanges, kclManager.ast))
|
||||
return false
|
||||
const _selections = convertSelectionsToOld(selectionRanges)
|
||||
if (!isSingleCursorInPipe(_selections, kclManager.ast)) return false
|
||||
return !!isCursorInSketchCommandRange(
|
||||
engineCommandManager.artifactGraph,
|
||||
selectionRanges
|
||||
_selections
|
||||
)
|
||||
},
|
||||
'Has exportable geometry': () => {
|
||||
@ -619,7 +641,8 @@ export const ModelingMachineProvider = ({
|
||||
}),
|
||||
'animate-to-sketch': fromPromise(
|
||||
async ({ input: { selectionRanges } }) => {
|
||||
const sourceRange = selectionRanges.codeBasedSelections[0].range
|
||||
const _selections = convertSelectionsToOld(selectionRanges)
|
||||
const sourceRange = _selections.codeBasedSelections[0].range
|
||||
const sketchPathToNode = getNodePathFromSourceRange(
|
||||
kclManager.ast,
|
||||
sourceRange
|
||||
@ -643,10 +666,11 @@ export const ModelingMachineProvider = ({
|
||||
),
|
||||
'Get horizontal info': fromPromise(
|
||||
async ({ input: { selectionRanges, sketchDetails } }) => {
|
||||
const _selections = convertSelectionsToOld(selectionRanges)
|
||||
const { modifiedAst, pathToNodeMap } =
|
||||
await applyConstraintHorzVertDistance({
|
||||
constraint: 'setHorzDistance',
|
||||
selectionRanges,
|
||||
selectionRanges: _selections,
|
||||
})
|
||||
const _modifiedAst = parse(recast(modifiedAst))
|
||||
if (!sketchDetails)
|
||||
@ -664,7 +688,8 @@ export const ModelingMachineProvider = ({
|
||||
sketchDetails.origin
|
||||
)
|
||||
if (err(updatedAst)) return Promise.reject(updatedAst)
|
||||
const selection = updateSelections(
|
||||
// const selection = updateSelections(
|
||||
const selection = updateSelections2(
|
||||
pathToNodeMap,
|
||||
selectionRanges,
|
||||
updatedAst.newAst
|
||||
@ -682,7 +707,7 @@ export const ModelingMachineProvider = ({
|
||||
const { modifiedAst, pathToNodeMap } =
|
||||
await applyConstraintHorzVertDistance({
|
||||
constraint: 'setVertDistance',
|
||||
selectionRanges,
|
||||
selectionRanges: convertSelectionsToOld(selectionRanges),
|
||||
})
|
||||
const _modifiedAst = parse(recast(modifiedAst))
|
||||
if (!sketchDetails)
|
||||
@ -702,7 +727,7 @@ export const ModelingMachineProvider = ({
|
||||
if (err(updatedAst)) return Promise.reject(updatedAst)
|
||||
const selection = updateSelections(
|
||||
pathToNodeMap,
|
||||
selectionRanges,
|
||||
convertSelectionsToOld(selectionRanges),
|
||||
updatedAst.newAst
|
||||
)
|
||||
if (err(selection)) return Promise.reject(selection)
|
||||
@ -716,15 +741,15 @@ export const ModelingMachineProvider = ({
|
||||
'Get angle info': fromPromise(
|
||||
async ({ input: { selectionRanges, sketchDetails } }) => {
|
||||
const info = angleBetweenInfo({
|
||||
selectionRanges,
|
||||
selectionRanges: convertSelectionsToOld(selectionRanges),
|
||||
})
|
||||
if (err(info)) return Promise.reject(info)
|
||||
const { modifiedAst, pathToNodeMap } = await (info.enabled
|
||||
? applyConstraintAngleBetween({
|
||||
selectionRanges,
|
||||
selectionRanges: convertSelectionsToOld(selectionRanges),
|
||||
})
|
||||
: applyConstraintAngleLength({
|
||||
selectionRanges,
|
||||
selectionRanges: convertSelectionsToOld(selectionRanges),
|
||||
angleOrLength: 'setAngle',
|
||||
}))
|
||||
const _modifiedAst = parse(recast(modifiedAst))
|
||||
@ -747,7 +772,7 @@ export const ModelingMachineProvider = ({
|
||||
if (err(updatedAst)) return Promise.reject(updatedAst)
|
||||
const selection = updateSelections(
|
||||
pathToNodeMap,
|
||||
selectionRanges,
|
||||
convertSelectionsToOld(selectionRanges),
|
||||
updatedAst.newAst
|
||||
)
|
||||
if (err(selection)) return Promise.reject(selection)
|
||||
|
@ -9,10 +9,12 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
|
||||
export const KclEditorMenu = ({ children }: PropsWithChildren) => {
|
||||
const { enable: convertToVarEnabled, handleClick: handleConvertToVarClick } =
|
||||
useConvertToVariable()
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
|
||||
return (
|
||||
<Menu>
|
||||
@ -77,6 +79,22 @@ export const KclEditorMenu = ({ children }: PropsWithChildren) => {
|
||||
</small>
|
||||
</a>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<button
|
||||
onClick={() => {
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: {
|
||||
groupId: 'code',
|
||||
name: 'open-kcl-example',
|
||||
},
|
||||
})
|
||||
}}
|
||||
className={styles.button}
|
||||
>
|
||||
<span>Load a sample model</span>
|
||||
</button>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<a
|
||||
className={styles.button}
|
||||
@ -85,7 +103,7 @@ export const KclEditorMenu = ({ children }: PropsWithChildren) => {
|
||||
rel="noopener noreferrer"
|
||||
onClick={openExternalBrowserIfDesktop()}
|
||||
>
|
||||
<span>KCL samples</span>
|
||||
<span>View all samples</span>
|
||||
<small>
|
||||
zoo.dev
|
||||
<FontAwesomeIcon
|
||||
|
64
src/components/ToastUpdate.tsx
Normal file
64
src/components/ToastUpdate.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import toast from 'react-hot-toast'
|
||||
import { ActionButton } from './ActionButton'
|
||||
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
||||
|
||||
export function ToastUpdate({
|
||||
version,
|
||||
onRestart,
|
||||
}: {
|
||||
version: string
|
||||
onRestart: () => void
|
||||
}) {
|
||||
return (
|
||||
<div className="inset-0 z-50 grid place-content-center rounded bg-chalkboard-110/50 shadow-md">
|
||||
<div className="max-w-3xl min-w-[35rem] p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90">
|
||||
<div className="my-4 flex items-baseline">
|
||||
<span
|
||||
className="px-3 py-1 text-xl rounded-full bg-primary text-chalkboard-10"
|
||||
data-testid="update-version"
|
||||
>
|
||||
v{version}
|
||||
</span>
|
||||
<span className="ml-4 text-md text-bold">
|
||||
A new update has downloaded and will be available next time you
|
||||
start the app. You can view the release notes{' '}
|
||||
<a
|
||||
onClick={openExternalBrowserIfDesktop(
|
||||
`https://github.com/KittyCAD/modeling-app/releases/tag/v${version}`
|
||||
)}
|
||||
href={`https://github.com/KittyCAD/modeling-app/releases/tag/v${version}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
here on GitHub.
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between gap-8">
|
||||
<ActionButton
|
||||
Element="button"
|
||||
iconStart={{
|
||||
icon: 'arrowRotateRight',
|
||||
}}
|
||||
name="Restart app now"
|
||||
onClick={onRestart}
|
||||
>
|
||||
Restart app now
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
Element="button"
|
||||
iconStart={{
|
||||
icon: 'checkmark',
|
||||
}}
|
||||
name="Got it"
|
||||
onClick={() => {
|
||||
toast.dismiss()
|
||||
}}
|
||||
>
|
||||
Got it
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import { toolTips } from 'lang/langHelpers'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Selections__old } from 'lib/selections'
|
||||
import { Program, Expr, VariableDeclarator } from '../../lang/wasm'
|
||||
import {
|
||||
getNodePathFromSourceRange,
|
||||
@ -18,7 +18,7 @@ import { TransformInfo } from 'lang/std/stdTypes'
|
||||
export function equalAngleInfo({
|
||||
selectionRanges,
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
selectionRanges: Selections__old
|
||||
}):
|
||||
| {
|
||||
transforms: TransformInfo[]
|
||||
@ -82,7 +82,7 @@ export function equalAngleInfo({
|
||||
export function applyConstraintEqualAngle({
|
||||
selectionRanges,
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
selectionRanges: Selections__old
|
||||
}):
|
||||
| {
|
||||
modifiedAst: Program
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { toolTips } from 'lang/langHelpers'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Selections__old } from 'lib/selections'
|
||||
import { Program, Expr, VariableDeclarator } from '../../lang/wasm'
|
||||
import {
|
||||
getNodePathFromSourceRange,
|
||||
@ -18,7 +18,7 @@ import { err } from 'lib/trap'
|
||||
export function setEqualLengthInfo({
|
||||
selectionRanges,
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
selectionRanges: Selections__old
|
||||
}):
|
||||
| {
|
||||
transforms: TransformInfo[]
|
||||
@ -83,7 +83,7 @@ export function setEqualLengthInfo({
|
||||
export function applyConstraintEqualLength({
|
||||
selectionRanges,
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
selectionRanges: Selections__old
|
||||
}):
|
||||
| {
|
||||
modifiedAst: Program
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { toolTips } from 'lang/langHelpers'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Selections__old } from 'lib/selections'
|
||||
import { Program, ProgramMemory, Expr } from '../../lang/wasm'
|
||||
import {
|
||||
getNodePathFromSourceRange,
|
||||
@ -15,7 +15,7 @@ import { kclManager } from 'lib/singletons'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
export function horzVertInfo(
|
||||
selectionRanges: Selections,
|
||||
selectionRanges: Selections__old,
|
||||
horOrVert: 'vertical' | 'horizontal'
|
||||
):
|
||||
| {
|
||||
@ -53,7 +53,7 @@ export function horzVertInfo(
|
||||
}
|
||||
|
||||
export function applyConstraintHorzVert(
|
||||
selectionRanges: Selections,
|
||||
selectionRanges: Selections__old,
|
||||
horOrVert: 'vertical' | 'horizontal',
|
||||
ast: Program,
|
||||
programMemory: ProgramMemory
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { toolTips } from 'lang/langHelpers'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Program, Expr, VariableDeclarator } from '../../lang/wasm'
|
||||
import { Selections__old } from 'lib/selections'
|
||||
import {
|
||||
getNodePathFromSourceRange,
|
||||
getNodeFromPath,
|
||||
@ -25,12 +25,12 @@ const getModalInfo = createInfoModal(GetInfoModal)
|
||||
export function intersectInfo({
|
||||
selectionRanges,
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
selectionRanges: Selections__old
|
||||
}):
|
||||
| {
|
||||
transforms: TransformInfo[]
|
||||
enabled: boolean
|
||||
forcedSelectionRanges: Selections
|
||||
forcedSelectionRanges: Selections__old
|
||||
}
|
||||
| Error {
|
||||
if (selectionRanges.codeBasedSelections.length < 2) {
|
||||
@ -134,7 +134,7 @@ export function intersectInfo({
|
||||
export async function applyConstraintIntersect({
|
||||
selectionRanges,
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
selectionRanges: Selections__old
|
||||
}): Promise<{
|
||||
modifiedAst: Program
|
||||
pathToNodeMap: PathToNodeMap
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { toolTips } from 'lang/langHelpers'
|
||||
import { Selection, Selections } from 'lib/selections'
|
||||
import { Selection__old, Selections__old } from 'lib/selections'
|
||||
import { PathToNode, Program, Expr } from '../../lang/wasm'
|
||||
import {
|
||||
getNodePathFromSourceRange,
|
||||
@ -18,13 +18,13 @@ export function removeConstrainingValuesInfo({
|
||||
selectionRanges,
|
||||
pathToNodes,
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
selectionRanges: Selections__old
|
||||
pathToNodes?: Array<PathToNode>
|
||||
}):
|
||||
| {
|
||||
transforms: TransformInfo[]
|
||||
enabled: boolean
|
||||
updatedSelectionRanges: Selections
|
||||
updatedSelectionRanges: Selections__old
|
||||
}
|
||||
| Error {
|
||||
const paths =
|
||||
@ -45,7 +45,7 @@ export function removeConstrainingValuesInfo({
|
||||
? {
|
||||
otherSelections: [],
|
||||
codeBasedSelections: nodes.map(
|
||||
(node): Selection => ({
|
||||
(node): Selection__old => ({
|
||||
range: [node.start, node.end],
|
||||
type: 'default',
|
||||
})
|
||||
@ -73,7 +73,7 @@ export function applyRemoveConstrainingValues({
|
||||
selectionRanges,
|
||||
pathToNodes,
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
selectionRanges: Selections__old
|
||||
pathToNodes?: Array<PathToNode>
|
||||
}):
|
||||
| {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { toolTips } from 'lang/langHelpers'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Program, Expr } from '../../lang/wasm'
|
||||
import { Selections__old } from 'lib/selections'
|
||||
import {
|
||||
getNodePathFromSourceRange,
|
||||
getNodeFromPath,
|
||||
@ -32,7 +32,7 @@ export function absDistanceInfo({
|
||||
selectionRanges,
|
||||
constraint,
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
selectionRanges: Selections__old
|
||||
constraint: Constraint
|
||||
}):
|
||||
| {
|
||||
@ -93,7 +93,7 @@ export async function applyConstraintAbsDistance({
|
||||
selectionRanges,
|
||||
constraint,
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
selectionRanges: Selections__old
|
||||
constraint: 'xAbs' | 'yAbs'
|
||||
}): Promise<{
|
||||
modifiedAst: Program
|
||||
@ -157,7 +157,7 @@ export function applyConstraintAxisAlign({
|
||||
selectionRanges,
|
||||
constraint,
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
selectionRanges: Selections__old
|
||||
constraint: 'snapToYAxis' | 'snapToXAxis'
|
||||
}):
|
||||
| {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { toolTips } from 'lang/langHelpers'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Program, Expr, VariableDeclarator } from '../../lang/wasm'
|
||||
import { Selections__old } from 'lib/selections'
|
||||
import {
|
||||
getNodePathFromSourceRange,
|
||||
getNodeFromPath,
|
||||
@ -24,7 +24,7 @@ const getModalInfo = createInfoModal(GetInfoModal)
|
||||
export function angleBetweenInfo({
|
||||
selectionRanges,
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
selectionRanges: Selections__old
|
||||
}):
|
||||
| {
|
||||
transforms: TransformInfo[]
|
||||
@ -90,7 +90,7 @@ export async function applyConstraintAngleBetween({
|
||||
selectionRanges,
|
||||
}: // constraint,
|
||||
{
|
||||
selectionRanges: Selections
|
||||
selectionRanges: Selections__old
|
||||
// constraint: 'setHorzDistance' | 'setVertDistance'
|
||||
}): Promise<{
|
||||
modifiedAst: Program
|
||||
|
@ -16,7 +16,7 @@ import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal'
|
||||
import { createLiteral, createVariableDeclaration } from '../../lang/modifyAst'
|
||||
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Selections__old } from 'lib/selections'
|
||||
import { cleanErrs, err } from 'lib/trap'
|
||||
|
||||
const getModalInfo = createInfoModal(GetInfoModal)
|
||||
@ -25,7 +25,7 @@ export function horzVertDistanceInfo({
|
||||
selectionRanges,
|
||||
constraint,
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
selectionRanges: Selections__old
|
||||
constraint: 'setHorzDistance' | 'setVertDistance'
|
||||
}):
|
||||
| {
|
||||
@ -95,7 +95,7 @@ export async function applyConstraintHorzVertDistance({
|
||||
// TODO align will always be false (covered by synconous applyConstraintHorzVertAlign), remove it
|
||||
isAlign = false,
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
selectionRanges: Selections__old
|
||||
constraint: 'setHorzDistance' | 'setVertDistance'
|
||||
isAlign?: false
|
||||
}): Promise<{
|
||||
@ -181,7 +181,7 @@ export function applyConstraintHorzVertAlign({
|
||||
selectionRanges,
|
||||
constraint,
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
selectionRanges: Selections__old
|
||||
constraint: 'setHorzDistance' | 'setVertDistance'
|
||||
}):
|
||||
| {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { toolTips } from 'lang/langHelpers'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Program, Expr } from '../../lang/wasm'
|
||||
import { Selections__old } from 'lib/selections'
|
||||
import {
|
||||
getNodePathFromSourceRange,
|
||||
getNodeFromPath,
|
||||
@ -32,7 +32,7 @@ export function angleLengthInfo({
|
||||
selectionRanges,
|
||||
angleOrLength = 'setLength',
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
selectionRanges: Selections__old
|
||||
angleOrLength?: 'setLength' | 'setAngle'
|
||||
}):
|
||||
| {
|
||||
@ -74,7 +74,7 @@ export async function applyConstraintAngleLength({
|
||||
selectionRanges,
|
||||
angleOrLength = 'setLength',
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
selectionRanges: Selections__old
|
||||
angleOrLength?: 'setLength' | 'setAngle'
|
||||
}): Promise<{
|
||||
modifiedAst: Program
|
||||
|
@ -2,7 +2,11 @@ import { EditorView, ViewUpdate } from '@codemirror/view'
|
||||
import { EditorSelection, Annotation, Transaction } from '@codemirror/state'
|
||||
import { engineCommandManager } from 'lib/singletons'
|
||||
import { modelingMachine, ModelingMachineEvent } from 'machines/modelingMachine'
|
||||
import { Selections, processCodeMirrorRanges, Selection } from 'lib/selections'
|
||||
import {
|
||||
Selections__old,
|
||||
Selection__old,
|
||||
processCodeMirrorRanges,
|
||||
} from 'lib/selections'
|
||||
import { undo, redo } from '@codemirror/commands'
|
||||
import { CommandBarMachineEvent } from 'machines/commandBarMachine'
|
||||
import { addLineHighlight, addLineHighlightEvent } from './highlightextension'
|
||||
@ -31,7 +35,7 @@ export default class EditorManager {
|
||||
private _copilotEnabled: boolean = true
|
||||
|
||||
private _isShiftDown: boolean = false
|
||||
private _selectionRanges: Selections = {
|
||||
private _selectionRanges: Selections__old = {
|
||||
otherSelections: [],
|
||||
codeBasedSelections: [],
|
||||
}
|
||||
@ -73,7 +77,7 @@ export default class EditorManager {
|
||||
this._isShiftDown = isShiftDown
|
||||
}
|
||||
|
||||
set selectionRanges(selectionRanges: Selections) {
|
||||
set selectionRanges(selectionRanges: Selections__old) {
|
||||
this._selectionRanges = selectionRanges
|
||||
}
|
||||
|
||||
@ -97,7 +101,7 @@ export default class EditorManager {
|
||||
return this._highlightRange
|
||||
}
|
||||
|
||||
setHighlightRange(selections: Array<Selection['range']>): void {
|
||||
setHighlightRange(selections: Array<Selection__old['range']>): void {
|
||||
this._highlightRange = selections
|
||||
|
||||
const selectionsWithSafeEnds = selections.map((s): [number, number] => {
|
||||
@ -203,7 +207,7 @@ export default class EditorManager {
|
||||
return false
|
||||
}
|
||||
|
||||
selectRange(selections: Selections) {
|
||||
selectRange(selections: Selections__old) {
|
||||
if (selections.codeBasedSelections.length === 0) {
|
||||
return
|
||||
}
|
||||
|
@ -9,10 +9,9 @@ import { useModelingContext } from './useModelingContext'
|
||||
import { getEventForSelectWithPoint } from 'lib/selections'
|
||||
import {
|
||||
getCapCodeRef,
|
||||
getSweepEdgeCodeRef,
|
||||
getSweepFromSuspectedSweepSurface,
|
||||
getSolid2dCodeRef,
|
||||
getWallCodeRef,
|
||||
getCodeRefsByArtifactId,
|
||||
} from 'lang/std/artifactGraph'
|
||||
import { err, reportRejection } from 'lib/trap'
|
||||
import { DefaultPlaneStr, getFaceDetails } from 'clientSideScene/sceneEntities'
|
||||
@ -29,52 +28,14 @@ export function useEngineConnectionSubscriptions() {
|
||||
event: 'highlight_set_entity',
|
||||
callback: ({ data }) => {
|
||||
if (data?.entity_id) {
|
||||
const artifact = engineCommandManager.artifactGraph.get(
|
||||
data.entity_id
|
||||
const codeRefs = getCodeRefsByArtifactId(
|
||||
data.entity_id,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (artifact?.type === 'solid2D') {
|
||||
const codeRef = getSolid2dCodeRef(
|
||||
artifact,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(codeRef)) return
|
||||
editorManager.setHighlightRange([codeRef.range])
|
||||
} else if (artifact?.type === 'cap') {
|
||||
const codeRef = getCapCodeRef(
|
||||
artifact,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(codeRef)) return
|
||||
editorManager.setHighlightRange([codeRef.range])
|
||||
} else if (artifact?.type === 'wall') {
|
||||
const extrusion = getSweepFromSuspectedSweepSurface(
|
||||
data.entity_id,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
const codeRef = getWallCodeRef(
|
||||
artifact,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(codeRef)) return
|
||||
editorManager.setHighlightRange(
|
||||
err(extrusion)
|
||||
? [codeRef.range]
|
||||
: [codeRef.range, extrusion.codeRef.range]
|
||||
)
|
||||
} else if (artifact?.type === 'sweepEdge') {
|
||||
const codeRef = getSweepEdgeCodeRef(
|
||||
artifact,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(codeRef)) return
|
||||
editorManager.setHighlightRange([codeRef.range])
|
||||
} else if (artifact?.type === 'segment') {
|
||||
editorManager.setHighlightRange([
|
||||
artifact?.codeRef?.range || [0, 0],
|
||||
])
|
||||
} else {
|
||||
editorManager.setHighlightRange([[0, 0]])
|
||||
if (codeRefs) {
|
||||
editorManager.setHighlightRange(codeRefs.map(({ range }) => range))
|
||||
}
|
||||
editorManager.setHighlightRange([[0, 0]])
|
||||
} else if (
|
||||
!editorManager.highlightRange ||
|
||||
(editorManager.highlightRange[0][0] !== 0 &&
|
||||
|
@ -11,6 +11,7 @@ import { useModelingContext } from './useModelingContext'
|
||||
import { PathToNode, SourceRange } from 'lang/wasm'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { toSync } from 'lib/utils'
|
||||
import { convertSelectionsToOld } from 'lib/selections'
|
||||
|
||||
export const getVarNameModal = createSetVarNameModal(SetVarNameModal)
|
||||
|
||||
@ -28,14 +29,19 @@ export function useConvertToVariable(range?: SourceRange) {
|
||||
|
||||
const meta = isNodeSafeToReplace(
|
||||
parsed,
|
||||
range || context.selectionRanges.codeBasedSelections?.[0]?.range || []
|
||||
range ||
|
||||
convertSelectionsToOld(context.selectionRanges).codeBasedSelections?.[0]
|
||||
?.range ||
|
||||
[]
|
||||
)
|
||||
if (trap(meta)) return
|
||||
|
||||
const { isSafe, value } = meta
|
||||
const canReplace = isSafe && value.type !== 'Identifier'
|
||||
const isOnlyOneSelection =
|
||||
!!range || context.selectionRanges.codeBasedSelections.length === 1
|
||||
!!range ||
|
||||
convertSelectionsToOld(context.selectionRanges).codeBasedSelections
|
||||
.length === 1
|
||||
|
||||
setEnabled(canReplace && isOnlyOneSelection)
|
||||
}, [context.selectionRanges])
|
||||
@ -52,7 +58,9 @@ export function useConvertToVariable(range?: SourceRange) {
|
||||
moveValueIntoNewVariable(
|
||||
ast,
|
||||
kclManager.programMemory,
|
||||
range || context.selectionRanges.codeBasedSelections[0].range,
|
||||
range ||
|
||||
convertSelectionsToOld(context.selectionRanges)
|
||||
.codeBasedSelections[0].range,
|
||||
variableName
|
||||
)
|
||||
|
||||
|
@ -1,12 +1,13 @@
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import './index.css'
|
||||
import reportWebVitals from './reportWebVitals'
|
||||
import { Toaster } from 'react-hot-toast'
|
||||
import toast, { Toaster } from 'react-hot-toast'
|
||||
import { Router } from './Router'
|
||||
import { HotkeysProvider } from 'react-hotkeys-hook'
|
||||
import ModalContainer from 'react-modal-promise'
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
import { AppStreamProvider } from 'AppState'
|
||||
import { ToastUpdate } from 'components/ToastUpdate'
|
||||
|
||||
// uncomment for xstate inspector
|
||||
// import { DEV } from 'env'
|
||||
@ -52,4 +53,17 @@ root.render(
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals()
|
||||
|
||||
isDesktop()
|
||||
isDesktop() &&
|
||||
window.electron.onUpdateDownloaded((version: string) => {
|
||||
const message = `A new update (${version}) was downloaded and will be available next time you open the app.`
|
||||
console.log(message)
|
||||
toast.custom(
|
||||
ToastUpdate({
|
||||
version,
|
||||
onRestart: () => {
|
||||
window.electron.appRestart()
|
||||
},
|
||||
}),
|
||||
{ duration: 30000 }
|
||||
)
|
||||
})
|
||||
|
@ -3,8 +3,6 @@ import { createContext, useContext, useEffect, useState } from 'react'
|
||||
import { type IndexLoaderData } from 'lib/types'
|
||||
import { useLoaderData } from 'react-router-dom'
|
||||
import { codeManager, kclManager } from 'lib/singletons'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { Command } from 'lib/commandTypes'
|
||||
|
||||
const KclContext = createContext({
|
||||
code: codeManager?.code || '',
|
||||
@ -37,7 +35,6 @@ export function KclContextProvider({
|
||||
const [errors, setErrors] = useState<KCLError[]>([])
|
||||
const [logs, setLogs] = useState<string[]>([])
|
||||
const [wasmInitFailed, setWasmInitFailed] = useState(false)
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
|
||||
useEffect(() => {
|
||||
codeManager.registerCallBacks({
|
||||
@ -53,28 +50,6 @@ export function KclContextProvider({
|
||||
})
|
||||
}, [])
|
||||
|
||||
// Add format code to command palette.
|
||||
useEffect(() => {
|
||||
const commands: Command[] = [
|
||||
{
|
||||
name: 'format-code',
|
||||
displayName: 'Format Code',
|
||||
description: 'Nicely formats the KCL code in the editor.',
|
||||
needsReview: false,
|
||||
groupId: 'code',
|
||||
icon: 'code',
|
||||
onSubmit: (data) => {
|
||||
kclManager.format()
|
||||
},
|
||||
},
|
||||
]
|
||||
commandBarSend({ type: 'Add commands', data: { commands } })
|
||||
|
||||
return () => {
|
||||
commandBarSend({ type: 'Remove commands', data: { commands } })
|
||||
}
|
||||
}, [kclManager, commandBarSend])
|
||||
|
||||
return (
|
||||
<KclContext.Provider
|
||||
value={{
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { executeAst, lintAst } from 'lang/langHelpers'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Selections__old } from 'lib/selections'
|
||||
import { KCLError, kclErrorsToDiagnostics } from './errors'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { EngineCommandManager } from './std/engineConnection'
|
||||
@ -425,14 +425,14 @@ export class KclManager {
|
||||
}
|
||||
): Promise<{
|
||||
newAst: Program
|
||||
selections?: Selections
|
||||
selections?: Selections__old
|
||||
}> {
|
||||
const newCode = recast(ast)
|
||||
if (err(newCode)) return Promise.reject(newCode)
|
||||
|
||||
const astWithUpdatedSource = this.safeParse(newCode)
|
||||
if (!astWithUpdatedSource) return Promise.reject(new Error('bad ast'))
|
||||
let returnVal: Selections | undefined = undefined
|
||||
let returnVal: Selections__old | undefined = undefined
|
||||
|
||||
if (optionalParams?.focusPath) {
|
||||
returnVal = {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Selection } from 'lib/selections'
|
||||
import { err, reportRejection, trap } from 'lib/trap'
|
||||
import { Selection__old } from 'lib/selections'
|
||||
import {
|
||||
Program,
|
||||
CallExpression,
|
||||
@ -762,7 +762,7 @@ export function createBinaryExpressionWithUnary([left, right]: [
|
||||
|
||||
export function giveSketchFnCallTag(
|
||||
ast: Program,
|
||||
range: Selection['range'],
|
||||
range: Selection__old['range'],
|
||||
tag?: string
|
||||
):
|
||||
| {
|
||||
@ -836,7 +836,7 @@ export function moveValueIntoNewVariablePath(
|
||||
export function moveValueIntoNewVariable(
|
||||
ast: Program,
|
||||
programMemory: ProgramMemory,
|
||||
sourceRange: Selection['range'],
|
||||
sourceRange: Selection__old['range'],
|
||||
variableName: string
|
||||
): {
|
||||
modifiedAst: Program
|
||||
@ -955,7 +955,7 @@ export function removeSingleConstraintInfo(
|
||||
|
||||
export async function deleteFromSelection(
|
||||
ast: Program,
|
||||
selection: Selection,
|
||||
selection: Selection__old,
|
||||
programMemory: ProgramMemory,
|
||||
getFaceDetails: (id: string) => Promise<Models['FaceIsPlanar_type']> = () =>
|
||||
({} as any)
|
||||
|
@ -19,7 +19,7 @@ import {
|
||||
import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst'
|
||||
import { createLiteral } from 'lang/modifyAst'
|
||||
import { err } from 'lib/trap'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Selections__old } from 'lib/selections'
|
||||
import { engineCommandManager, kclManager } from 'lib/singletons'
|
||||
import { VITE_KC_DEV_TOKEN } from 'env'
|
||||
import { KclCommandValue } from 'lib/commandTypes'
|
||||
@ -106,7 +106,7 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
|
||||
code.indexOf(selectedSegmentSnippet),
|
||||
code.indexOf(selectedSegmentSnippet) + selectedSegmentSnippet.length,
|
||||
]
|
||||
const selection: Selections = {
|
||||
const selection: Selections__old = {
|
||||
codeBasedSelections: [
|
||||
{
|
||||
range: segmentRange,
|
||||
@ -469,7 +469,7 @@ const runModifyAstWithFilletAndTagTest = async (
|
||||
code.indexOf(selectionSnippet) + selectionSnippet.length,
|
||||
]
|
||||
)
|
||||
const selection: Selections = {
|
||||
const selection: Selections__old = {
|
||||
codeBasedSelections: segmentRanges.map((segmentRange) => ({
|
||||
range: segmentRange,
|
||||
type: 'default',
|
||||
@ -730,7 +730,7 @@ describe('Testing button states', () => {
|
||||
]
|
||||
: [ast.end, ast.end] // empty line in the end of the code
|
||||
|
||||
const selectionRanges: Selections = {
|
||||
const selectionRanges: Selections__old = {
|
||||
codeBasedSelections: [
|
||||
{
|
||||
range,
|
||||
|
@ -31,7 +31,7 @@ import {
|
||||
sketchLineHelperMap,
|
||||
} from '../std/sketch'
|
||||
import { err, trap } from 'lib/trap'
|
||||
import { Selections, canFilletSelection } from 'lib/selections'
|
||||
import { Selections__old, canFilletSelection } from 'lib/selections'
|
||||
import { KclCommandValue } from 'lib/commandTypes'
|
||||
import {
|
||||
ArtifactGraph,
|
||||
@ -45,7 +45,7 @@ import { kclManager, engineCommandManager, editorManager } from 'lib/singletons'
|
||||
|
||||
export function applyFilletToSelection(
|
||||
ast: Program,
|
||||
selection: Selections,
|
||||
selection: Selections__old,
|
||||
radius: KclCommandValue
|
||||
): void | Error {
|
||||
// 1. clone ast
|
||||
@ -63,7 +63,7 @@ export function applyFilletToSelection(
|
||||
|
||||
export function modifyAstWithFilletAndTag(
|
||||
ast: Program,
|
||||
selection: Selections,
|
||||
selection: Selections__old,
|
||||
radius: KclCommandValue
|
||||
): { modifiedAst: Program; pathToFilletNode: Array<PathToNode> } | Error {
|
||||
const astResult = insertRadiusIntoAst(ast, radius)
|
||||
@ -130,7 +130,7 @@ function insertRadiusIntoAst(
|
||||
|
||||
export function getPathToExtrudeForSegmentSelection(
|
||||
ast: Program,
|
||||
selection: Selections,
|
||||
selection: Selections__old,
|
||||
programMemory: ProgramMemory,
|
||||
artifactGraph: ArtifactGraph
|
||||
): { pathToSegmentNode: PathToNode; pathToExtrudeNode: PathToNode } | Error {
|
||||
@ -447,7 +447,7 @@ export const hasValidFilletSelection = ({
|
||||
ast,
|
||||
code,
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
selectionRanges: Selections__old
|
||||
ast: Program
|
||||
code: string
|
||||
}) => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ToolTip } from 'lang/langHelpers'
|
||||
import { Selection, Selections } from 'lib/selections'
|
||||
import { Selection__old, Selections__old } from 'lib/selections'
|
||||
import {
|
||||
ArrayExpression,
|
||||
BinaryExpression,
|
||||
@ -120,7 +120,7 @@ export function getNodeFromPathCurry(
|
||||
|
||||
function moreNodePathFromSourceRange(
|
||||
node: Expr | ExpressionStatement | VariableDeclaration | ReturnStatement,
|
||||
sourceRange: Selection['range'],
|
||||
sourceRange: Selection__old['range'],
|
||||
previousPath: PathToNode = [['body', '']]
|
||||
): PathToNode {
|
||||
const [start, end] = sourceRange
|
||||
@ -315,7 +315,7 @@ function moreNodePathFromSourceRange(
|
||||
|
||||
export function getNodePathFromSourceRange(
|
||||
node: Program,
|
||||
sourceRange: Selection['range'],
|
||||
sourceRange: Selection__old['range'],
|
||||
previousPath: PathToNode = [['body', '']]
|
||||
): PathToNode {
|
||||
const [start, end] = sourceRange || []
|
||||
@ -493,7 +493,7 @@ export function findAllPreviousVariablesPath(
|
||||
export function findAllPreviousVariables(
|
||||
ast: Program,
|
||||
programMemory: ProgramMemory,
|
||||
sourceRange: Selection['range'],
|
||||
sourceRange: Selection__old['range'],
|
||||
type: 'number' | 'string' = 'number'
|
||||
): {
|
||||
variables: PrevVariable<typeof type extends 'number' ? number : string>[]
|
||||
@ -639,8 +639,8 @@ export function isValueZero(val?: Expr): boolean {
|
||||
export function isLinesParallelAndConstrained(
|
||||
ast: Program,
|
||||
programMemory: ProgramMemory,
|
||||
primaryLine: Selection,
|
||||
secondaryLine: Selection
|
||||
primaryLine: Selection__old,
|
||||
secondaryLine: Selection__old
|
||||
):
|
||||
| {
|
||||
isParallelAndConstrained: boolean
|
||||
@ -735,7 +735,7 @@ export function doesPipeHaveCallExp({
|
||||
}: {
|
||||
calleeName: string
|
||||
ast: Program
|
||||
selection: Selection
|
||||
selection: Selection__old
|
||||
}): boolean {
|
||||
const pathToNode = getNodePathFromSourceRange(ast, selection.range)
|
||||
const pipeExpressionMeta = getNodeFromPath<PipeExpression>(
|
||||
@ -762,7 +762,7 @@ export function hasExtrudeSketchGroup({
|
||||
programMemory,
|
||||
}: {
|
||||
ast: Program
|
||||
selection: Selection
|
||||
selection: Selection__old
|
||||
programMemory: ProgramMemory
|
||||
}): boolean {
|
||||
const pathToNode = getNodePathFromSourceRange(ast, selection.range)
|
||||
@ -786,7 +786,7 @@ export function hasExtrudeSketchGroup({
|
||||
}
|
||||
|
||||
export function isSingleCursorInPipe(
|
||||
selectionRanges: Selections,
|
||||
selectionRanges: Selections__old,
|
||||
ast: Program
|
||||
) {
|
||||
if (selectionRanges.codeBasedSelections.length !== 1) return false
|
||||
@ -860,7 +860,10 @@ export function findUsesOfTagInPipe(
|
||||
return dependentRanges
|
||||
}
|
||||
|
||||
export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) {
|
||||
export function hasSketchPipeBeenExtruded(
|
||||
selection: Selection__old,
|
||||
ast: Program
|
||||
) {
|
||||
const path = getNodePathFromSourceRange(ast, selection.range)
|
||||
const _node = getNodeFromPath<PipeExpression>(ast, path, 'PipeExpression')
|
||||
if (err(_node)) return false
|
||||
|
@ -5,86 +5,90 @@ import { err } from 'lib/trap'
|
||||
|
||||
export type ArtifactId = string
|
||||
|
||||
interface CommonCommandProperties {
|
||||
interface BaseArtifact {
|
||||
id: ArtifactId
|
||||
}
|
||||
|
||||
export interface CodeRef {
|
||||
range: SourceRange
|
||||
pathToNode: PathToNode
|
||||
}
|
||||
|
||||
export interface PlaneArtifact {
|
||||
export interface PlaneArtifact extends BaseArtifact {
|
||||
type: 'plane'
|
||||
pathIds: Array<ArtifactId>
|
||||
codeRef: CommonCommandProperties
|
||||
codeRef: CodeRef
|
||||
}
|
||||
export interface PlaneArtifactRich {
|
||||
export interface PlaneArtifactRich extends BaseArtifact {
|
||||
type: 'plane'
|
||||
paths: Array<PathArtifact>
|
||||
codeRef: CommonCommandProperties
|
||||
codeRef: CodeRef
|
||||
}
|
||||
|
||||
export interface PathArtifact {
|
||||
export interface PathArtifact extends BaseArtifact {
|
||||
type: 'path'
|
||||
planeId: ArtifactId
|
||||
segIds: Array<ArtifactId>
|
||||
sweepId: ArtifactId
|
||||
solid2dId?: ArtifactId
|
||||
codeRef: CommonCommandProperties
|
||||
codeRef: CodeRef
|
||||
}
|
||||
|
||||
interface solid2D {
|
||||
interface solid2D extends BaseArtifact {
|
||||
type: 'solid2D'
|
||||
pathId: ArtifactId
|
||||
}
|
||||
export interface PathArtifactRich {
|
||||
export interface PathArtifactRich extends BaseArtifact {
|
||||
type: 'path'
|
||||
plane: PlaneArtifact | WallArtifact
|
||||
segments: Array<SegmentArtifact>
|
||||
sweep: SweepArtifact
|
||||
codeRef: CommonCommandProperties
|
||||
codeRef: CodeRef
|
||||
}
|
||||
|
||||
interface SegmentArtifact {
|
||||
interface SegmentArtifact extends BaseArtifact {
|
||||
type: 'segment'
|
||||
pathId: ArtifactId
|
||||
surfaceId: ArtifactId
|
||||
edgeIds: Array<ArtifactId>
|
||||
edgeCutId?: ArtifactId
|
||||
codeRef: CommonCommandProperties
|
||||
codeRef: CodeRef
|
||||
}
|
||||
interface SegmentArtifactRich {
|
||||
interface SegmentArtifactRich extends BaseArtifact {
|
||||
type: 'segment'
|
||||
path: PathArtifact
|
||||
surf: WallArtifact
|
||||
edges: Array<SweepEdge>
|
||||
edgeCut?: EdgeCut
|
||||
codeRef: CommonCommandProperties
|
||||
codeRef: CodeRef
|
||||
}
|
||||
|
||||
/** A Sweep is a more generic term for extrude, revolve, loft and sweep*/
|
||||
interface SweepArtifact {
|
||||
interface SweepArtifact extends BaseArtifact {
|
||||
type: 'sweep'
|
||||
subType: 'extrusion' | 'revolve'
|
||||
pathId: string
|
||||
surfaceIds: Array<string>
|
||||
edgeIds: Array<string>
|
||||
codeRef: CommonCommandProperties
|
||||
codeRef: CodeRef
|
||||
}
|
||||
interface SweepArtifactRich {
|
||||
interface SweepArtifactRich extends BaseArtifact {
|
||||
type: 'sweep'
|
||||
subType: 'extrusion' | 'revolve'
|
||||
path: PathArtifact
|
||||
surfaces: Array<WallArtifact | CapArtifact>
|
||||
edges: Array<SweepEdge>
|
||||
codeRef: CommonCommandProperties
|
||||
codeRef: CodeRef
|
||||
}
|
||||
|
||||
interface WallArtifact {
|
||||
interface WallArtifact extends BaseArtifact {
|
||||
type: 'wall'
|
||||
segId: ArtifactId
|
||||
edgeCutEdgeIds: Array<ArtifactId>
|
||||
sweepId: ArtifactId
|
||||
pathIds: Array<ArtifactId>
|
||||
}
|
||||
interface CapArtifact {
|
||||
interface CapArtifact extends BaseArtifact {
|
||||
type: 'cap'
|
||||
subType: 'start' | 'end'
|
||||
edgeCutEdgeIds: Array<ArtifactId>
|
||||
@ -92,7 +96,7 @@ interface CapArtifact {
|
||||
pathIds: Array<ArtifactId>
|
||||
}
|
||||
|
||||
interface SweepEdge {
|
||||
interface SweepEdge extends BaseArtifact {
|
||||
type: 'sweepEdge'
|
||||
segId: ArtifactId
|
||||
sweepId: ArtifactId
|
||||
@ -100,16 +104,16 @@ interface SweepEdge {
|
||||
}
|
||||
|
||||
/** A edgeCut is a more generic term for both fillet or chamfer */
|
||||
interface EdgeCut {
|
||||
interface EdgeCut extends BaseArtifact {
|
||||
type: 'edgeCut'
|
||||
subType: 'fillet' | 'chamfer'
|
||||
consumedEdgeId: ArtifactId
|
||||
edgeIds: Array<ArtifactId>
|
||||
surfaceId: ArtifactId
|
||||
codeRef: CommonCommandProperties
|
||||
codeRef: CodeRef
|
||||
}
|
||||
|
||||
interface EdgeCutEdge {
|
||||
interface EdgeCutEdge extends BaseArtifact {
|
||||
type: 'edgeCutEdge'
|
||||
edgeCutId: ArtifactId
|
||||
surfaceId: ArtifactId
|
||||
@ -258,6 +262,7 @@ export function getArtifactsToUpdate({
|
||||
id: currentPlaneId,
|
||||
artifact: {
|
||||
type: 'wall',
|
||||
id,
|
||||
segId: existingPlane.segId,
|
||||
edgeCutEdgeIds: existingPlane.edgeCutEdgeIds,
|
||||
sweepId: existingPlane.sweepId,
|
||||
@ -267,7 +272,10 @@ export function getArtifactsToUpdate({
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
{ id: currentPlaneId, artifact: { type: 'plane', pathIds, codeRef } },
|
||||
{
|
||||
id: currentPlaneId,
|
||||
artifact: { type: 'plane', id: currentPlaneId, pathIds, codeRef },
|
||||
},
|
||||
]
|
||||
}
|
||||
} else if (cmd.type === 'start_path') {
|
||||
@ -275,6 +283,7 @@ export function getArtifactsToUpdate({
|
||||
id,
|
||||
artifact: {
|
||||
type: 'path',
|
||||
id,
|
||||
segIds: [],
|
||||
planeId: currentPlaneId,
|
||||
sweepId: '',
|
||||
@ -287,7 +296,7 @@ export function getArtifactsToUpdate({
|
||||
if (plane?.type === 'plane') {
|
||||
returnArr.push({
|
||||
id: currentPlaneId,
|
||||
artifact: { type: 'plane', pathIds: [id], codeRef },
|
||||
artifact: { type: 'plane', id: currentPlaneId, pathIds: [id], codeRef },
|
||||
})
|
||||
}
|
||||
if (plane?.type === 'wall') {
|
||||
@ -295,6 +304,7 @@ export function getArtifactsToUpdate({
|
||||
id: currentPlaneId,
|
||||
artifact: {
|
||||
type: 'wall',
|
||||
id,
|
||||
segId: plane.segId,
|
||||
edgeCutEdgeIds: plane.edgeCutEdgeIds,
|
||||
sweepId: plane.sweepId,
|
||||
@ -309,6 +319,7 @@ export function getArtifactsToUpdate({
|
||||
id,
|
||||
artifact: {
|
||||
type: 'segment',
|
||||
id,
|
||||
pathId,
|
||||
surfaceId: '',
|
||||
edgeIds: [],
|
||||
@ -327,7 +338,11 @@ export function getArtifactsToUpdate({
|
||||
) {
|
||||
returnArr.push({
|
||||
id: response.data.modeling_response.data.face_id,
|
||||
artifact: { type: 'solid2D', pathId },
|
||||
artifact: {
|
||||
type: 'solid2D',
|
||||
id: response.data.modeling_response.data.face_id,
|
||||
pathId,
|
||||
},
|
||||
})
|
||||
const path = getArtifact(pathId)
|
||||
if (path?.type === 'path')
|
||||
@ -347,6 +362,7 @@ export function getArtifactsToUpdate({
|
||||
artifact: {
|
||||
type: 'sweep',
|
||||
subType: subType,
|
||||
id,
|
||||
pathId: cmd.target,
|
||||
surfaceIds: [],
|
||||
edgeIds: [],
|
||||
@ -378,6 +394,7 @@ export function getArtifactsToUpdate({
|
||||
id: face_id,
|
||||
artifact: {
|
||||
type: 'wall',
|
||||
id: face_id,
|
||||
segId: curve_id,
|
||||
edgeCutEdgeIds: [],
|
||||
sweepId: path.sweepId,
|
||||
@ -410,6 +427,7 @@ export function getArtifactsToUpdate({
|
||||
id: face_id,
|
||||
artifact: {
|
||||
type: 'cap',
|
||||
id: face_id,
|
||||
subType: cap === 'bottom' ? 'start' : 'end',
|
||||
edgeCutEdgeIds: [],
|
||||
sweepId: path.sweepId,
|
||||
@ -456,6 +474,7 @@ export function getArtifactsToUpdate({
|
||||
id: response.data.modeling_response.data.edge,
|
||||
artifact: {
|
||||
type: 'sweepEdge',
|
||||
id: response.data.modeling_response.data.edge,
|
||||
subType:
|
||||
cmd.type === 'solid3d_get_prev_adjacent_edge'
|
||||
? 'adjacent'
|
||||
@ -484,6 +503,7 @@ export function getArtifactsToUpdate({
|
||||
id,
|
||||
artifact: {
|
||||
type: 'edgeCut',
|
||||
id,
|
||||
subType: cmd.cut_type,
|
||||
consumedEdgeId: cmd.edge_id,
|
||||
edgeIds: [],
|
||||
@ -574,6 +594,7 @@ export function expandPlane(
|
||||
)
|
||||
return {
|
||||
type: 'plane',
|
||||
id: plane.id,
|
||||
paths: Array.from(paths.values()),
|
||||
codeRef: plane.codeRef,
|
||||
}
|
||||
@ -602,6 +623,7 @@ export function expandPath(
|
||||
if (err(plane)) return plane
|
||||
return {
|
||||
type: 'path',
|
||||
id: path.id,
|
||||
segments: Array.from(segs.values()),
|
||||
sweep,
|
||||
plane,
|
||||
@ -629,6 +651,7 @@ export function expandSweep(
|
||||
return {
|
||||
type: 'sweep',
|
||||
subType: 'extrusion',
|
||||
id: sweep.id,
|
||||
surfaces: Array.from(surfs.values()),
|
||||
edges: Array.from(edges.values()),
|
||||
path,
|
||||
@ -664,6 +687,7 @@ export function expandSegment(
|
||||
|
||||
return {
|
||||
type: 'segment',
|
||||
id: segment.id,
|
||||
path,
|
||||
surf,
|
||||
edges: Array.from(edges.values()),
|
||||
@ -675,7 +699,7 @@ export function expandSegment(
|
||||
export function getCapCodeRef(
|
||||
cap: CapArtifact,
|
||||
artifactGraph: ArtifactGraph
|
||||
): CommonCommandProperties | Error {
|
||||
): CodeRef | Error {
|
||||
const sweep = getArtifactOfTypes(
|
||||
{ key: cap.sweepId, types: ['sweep'] },
|
||||
artifactGraph
|
||||
@ -692,7 +716,7 @@ export function getCapCodeRef(
|
||||
export function getSolid2dCodeRef(
|
||||
solid2D: solid2D,
|
||||
artifactGraph: ArtifactGraph
|
||||
): CommonCommandProperties | Error {
|
||||
): CodeRef | Error {
|
||||
const path = getArtifactOfTypes(
|
||||
{ key: solid2D.pathId, types: ['path'] },
|
||||
artifactGraph
|
||||
@ -704,7 +728,7 @@ export function getSolid2dCodeRef(
|
||||
export function getWallCodeRef(
|
||||
wall: WallArtifact,
|
||||
artifactGraph: ArtifactGraph
|
||||
): CommonCommandProperties | Error {
|
||||
): CodeRef | Error {
|
||||
const seg = getArtifactOfTypes(
|
||||
{ key: wall.segId, types: ['segment'] },
|
||||
artifactGraph
|
||||
@ -716,7 +740,7 @@ export function getWallCodeRef(
|
||||
export function getSweepEdgeCodeRef(
|
||||
edge: SweepEdge,
|
||||
artifactGraph: ArtifactGraph
|
||||
): CommonCommandProperties | Error {
|
||||
): CodeRef | Error {
|
||||
const seg = getArtifactOfTypes(
|
||||
{ key: edge.segId, types: ['segment'] },
|
||||
artifactGraph
|
||||
@ -751,3 +775,33 @@ export function getSweepFromSuspectedPath(
|
||||
artifactGraph
|
||||
)
|
||||
}
|
||||
|
||||
export function getCodeRefsByArtifactId(
|
||||
id: string,
|
||||
artifactGraph: ArtifactGraph
|
||||
): Array<CodeRef> | null {
|
||||
const artifact = artifactGraph.get(id)
|
||||
if (artifact?.type === 'solid2D') {
|
||||
const codeRef = getSolid2dCodeRef(artifact, artifactGraph)
|
||||
if (err(codeRef)) return null
|
||||
return [codeRef]
|
||||
// editorManager.setHighlightRange([codeRef.range])
|
||||
} else if (artifact?.type === 'cap') {
|
||||
const codeRef = getCapCodeRef(artifact, artifactGraph)
|
||||
if (err(codeRef)) return null
|
||||
return [codeRef]
|
||||
} else if (artifact?.type === 'wall') {
|
||||
const extrusion = getSweepFromSuspectedSweepSurface(id, artifactGraph)
|
||||
const codeRef = getWallCodeRef(artifact, artifactGraph)
|
||||
if (err(codeRef)) return null
|
||||
return err(extrusion) ? [codeRef] : [codeRef, extrusion.codeRef]
|
||||
} else if (artifact?.type === 'sweepEdge') {
|
||||
const codeRef = getSweepEdgeCodeRef(artifact, artifactGraph)
|
||||
if (err(codeRef)) return null
|
||||
return [codeRef]
|
||||
} else if (artifact?.type === 'segment') {
|
||||
return [artifact.codeRef]
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
@ -1912,11 +1912,13 @@ export function getConstraintInfo(
|
||||
): ConstrainInfo[] {
|
||||
const fnName = callExpression?.callee?.name || ''
|
||||
if (!(fnName in sketchLineHelperMap)) return []
|
||||
return sketchLineHelperMap[fnName].getConstraintInfo(
|
||||
const result = sketchLineHelperMap[fnName].getConstraintInfo(
|
||||
callExpression,
|
||||
code,
|
||||
pathToNode
|
||||
)
|
||||
// console.log('result path', result[0].pathToNode)
|
||||
return result
|
||||
}
|
||||
|
||||
export function compareVec2Epsilon(
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
transformAstSketchLines,
|
||||
} from './sketchcombos'
|
||||
import { getSketchSegmentFromSourceRange } from './sketchConstraints'
|
||||
import { Selection } from 'lib/selections'
|
||||
import { Selection__old } from 'lib/selections'
|
||||
import { enginelessExecutor } from '../../lib/testHelpers'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
@ -33,7 +33,7 @@ async function testingSwapSketchFnCall({
|
||||
originalRange: [number, number]
|
||||
}> {
|
||||
const startIndex = inputCode.indexOf(callToSwap)
|
||||
const range: Selection = {
|
||||
const range: Selection__old = {
|
||||
type: 'default',
|
||||
range: [startIndex, startIndex + callToSwap.length],
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
getConstraintLevelFromSourceRange,
|
||||
} from './sketchcombos'
|
||||
import { ToolTip } from 'lang/langHelpers'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Selections__old } from 'lib/selections'
|
||||
import { err } from 'lib/trap'
|
||||
import { enginelessExecutor } from '../../lib/testHelpers'
|
||||
|
||||
@ -87,8 +87,8 @@ function getConstraintTypeFromSourceHelper2(
|
||||
}
|
||||
|
||||
function makeSelections(
|
||||
codeBaseSelections: Selections['codeBasedSelections']
|
||||
): Selections {
|
||||
codeBaseSelections: Selections__old['codeBasedSelections']
|
||||
): Selections__old {
|
||||
return {
|
||||
codeBasedSelections: codeBaseSelections,
|
||||
otherSelections: [],
|
||||
@ -208,7 +208,7 @@ const part001 = startSketchOn('XY')
|
||||
const ast = parse(inputScript)
|
||||
if (err(ast)) return Promise.reject(ast)
|
||||
|
||||
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
||||
const selectionRanges: Selections__old['codeBasedSelections'] = inputScript
|
||||
.split('\n')
|
||||
.filter((ln) => ln.includes('//'))
|
||||
.map((ln) => {
|
||||
@ -299,7 +299,7 @@ const part001 = startSketchOn('XY')
|
||||
const ast = parse(inputScript)
|
||||
if (err(ast)) return Promise.reject(ast)
|
||||
|
||||
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
||||
const selectionRanges: Selections__old['codeBasedSelections'] = inputScript
|
||||
.split('\n')
|
||||
.filter((ln) => ln.includes('// select for horizontal constraint'))
|
||||
.map((ln) => {
|
||||
@ -361,7 +361,7 @@ const part001 = startSketchOn('XY')
|
||||
const ast = parse(inputScript)
|
||||
if (err(ast)) return Promise.reject(ast)
|
||||
|
||||
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
||||
const selectionRanges: Selections__old['codeBasedSelections'] = inputScript
|
||||
.split('\n')
|
||||
.filter((ln) => ln.includes('// select for vertical constraint'))
|
||||
.map((ln) => {
|
||||
@ -434,7 +434,7 @@ const part001 = startSketchOn('XY')
|
||||
segEndY(seg01) + 2.93
|
||||
], %) // xRelative`)
|
||||
})
|
||||
it('testing for yRelative to horizontal distance', async () => {
|
||||
it.only('testing for yRelative to horizontal distance', async () => {
|
||||
const expectedCode = await helperThing(
|
||||
inputScript,
|
||||
['// base selection', '// yRelative'],
|
||||
@ -456,7 +456,7 @@ async function helperThing(
|
||||
const ast = parse(inputScript)
|
||||
if (err(ast)) return Promise.reject(ast)
|
||||
|
||||
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
||||
const selectionRanges: Selections__old['codeBasedSelections'] = inputScript
|
||||
.split('\n')
|
||||
.filter((ln) =>
|
||||
linesOfInterest.some((lineOfInterest) => ln.includes(lineOfInterest))
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
TransformInfo,
|
||||
} from './stdTypes'
|
||||
import { ToolTip, toolTips } from 'lang/langHelpers'
|
||||
import { Selections, Selection } from 'lib/selections'
|
||||
import { Selections__old, Selection__old } from 'lib/selections'
|
||||
import { cleanErrs, err } from 'lib/trap'
|
||||
import {
|
||||
CallExpression,
|
||||
@ -1470,7 +1470,7 @@ export function getConstraintType(
|
||||
}
|
||||
|
||||
export function getTransformInfos(
|
||||
selectionRanges: Selections,
|
||||
selectionRanges: Selections__old,
|
||||
ast: Program,
|
||||
constraintType: ConstraintType
|
||||
): TransformInfo[] {
|
||||
@ -1502,7 +1502,7 @@ export function getTransformInfos(
|
||||
}
|
||||
|
||||
export function getRemoveConstraintsTransforms(
|
||||
selectionRanges: Selections,
|
||||
selectionRanges: Selections__old,
|
||||
ast: Program,
|
||||
constraintType: ConstraintType
|
||||
): TransformInfo[] | Error {
|
||||
@ -1542,7 +1542,7 @@ export function transformSecondarySketchLinesTagFirst({
|
||||
forceValueUsedInTransform,
|
||||
}: {
|
||||
ast: Program
|
||||
selectionRanges: Selections
|
||||
selectionRanges: Selections__old
|
||||
transformInfos: TransformInfo[]
|
||||
programMemory: ProgramMemory
|
||||
forceSegName?: string
|
||||
@ -1613,12 +1613,12 @@ export function transformAstSketchLines({
|
||||
referencedSegmentRange,
|
||||
}: {
|
||||
ast: Program
|
||||
selectionRanges: Selections | PathToNode[]
|
||||
selectionRanges: Selections__old | PathToNode[]
|
||||
transformInfos: TransformInfo[]
|
||||
programMemory: ProgramMemory
|
||||
referenceSegName: string
|
||||
referencedSegmentRange?: Selection__old['range']
|
||||
forceValueUsedInTransform?: BinaryPart
|
||||
referencedSegmentRange?: Selection['range']
|
||||
}):
|
||||
| {
|
||||
modifiedAst: Program
|
||||
@ -1658,6 +1658,7 @@ export function transformAstSketchLines({
|
||||
''
|
||||
const inputs: InputArgs = []
|
||||
|
||||
console.log('getConstraintInfo', callExp.node, _pathToNode)
|
||||
getConstraintInfo(callExp.node, '', _pathToNode).forEach((a) => {
|
||||
if (
|
||||
a.type === 'tangentialWithPrevious' ||
|
||||
@ -1822,7 +1823,7 @@ function getArgLiteralVal(arg: Literal): number | Error {
|
||||
export type ConstraintLevel = 'free' | 'partial' | 'full'
|
||||
|
||||
export function getConstraintLevelFromSourceRange(
|
||||
cursorRange: Selection['range'],
|
||||
cursorRange: Selection__old['range'],
|
||||
ast: Program | Error
|
||||
): Error | { range: [number, number]; level: ConstraintLevel } {
|
||||
if (err(ast)) return ast
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Selections__old } from 'lib/selections'
|
||||
import { Program, PathToNode } from './wasm'
|
||||
import { getNodeFromPath } from './queryAst'
|
||||
import { ArtifactGraph, filterArtifacts } from 'lang/std/artifactGraph'
|
||||
@ -7,10 +7,10 @@ import { err } from 'lib/trap'
|
||||
|
||||
export function pathMapToSelections(
|
||||
ast: Program,
|
||||
prevSelections: Selections,
|
||||
prevSelections: Selections__old,
|
||||
pathToNodeMap: { [key: number]: PathToNode }
|
||||
): Selections {
|
||||
const newSelections: Selections = {
|
||||
): Selections__old {
|
||||
const newSelections: Selections__old = {
|
||||
...prevSelections,
|
||||
codeBasedSelections: [],
|
||||
}
|
||||
@ -47,7 +47,7 @@ export function updatePathToNodeFromMap(
|
||||
|
||||
export function isCursorInSketchCommandRange(
|
||||
artifactGraph: ArtifactGraph,
|
||||
selectionRanges: Selections
|
||||
selectionRanges: Selections__old
|
||||
): string | false {
|
||||
const overlappingEntries = filterArtifacts(
|
||||
{
|
||||
|
@ -2,7 +2,7 @@ import { Models } from '@kittycad/lib'
|
||||
import { StateMachineCommandSetConfig, KclCommandValue } from 'lib/commandTypes'
|
||||
import { KCL_DEFAULT_LENGTH, KCL_DEFAULT_DEGREE } from 'lib/constants'
|
||||
import { components } from 'lib/machine-api'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Selections__old } from 'lib/selections'
|
||||
import { machineManager } from 'lib/machineManager'
|
||||
import { modelingMachine, SketchTool } from 'machines/modelingMachine'
|
||||
|
||||
@ -28,17 +28,17 @@ export type ModelingCommandSchema = {
|
||||
machine: components['schemas']['MachineInfoResponse']
|
||||
}
|
||||
Extrude: {
|
||||
selection: Selections // & { type: 'face' } would be cool to lock that down
|
||||
selection: Selections__old // & { type: 'face' } would be cool to lock that down
|
||||
// result: (typeof EXTRUSION_RESULTS)[number]
|
||||
distance: KclCommandValue
|
||||
}
|
||||
Revolve: {
|
||||
selection: Selections
|
||||
selection: Selections__old
|
||||
angle: KclCommandValue
|
||||
}
|
||||
Fillet: {
|
||||
// todo
|
||||
selection: Selections
|
||||
selection: Selections__old
|
||||
radius: KclCommandValue
|
||||
}
|
||||
'change tool': {
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { CustomIconName } from 'components/CustomIcon'
|
||||
import { AllMachines } from 'hooks/useStateMachineCommands'
|
||||
import { Actor, AnyStateMachine, ContextFrom, EventFrom } from 'xstate'
|
||||
import { Selection } from './selections'
|
||||
import { Selection__old } from './selections'
|
||||
import { Identifier, Expr, VariableDeclaration } from 'lang/wasm'
|
||||
import { commandBarMachine } from 'machines/commandBarMachine'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
type Icon = CustomIconName
|
||||
const PLATFORMS = ['both', 'web', 'desktop'] as const
|
||||
@ -67,6 +68,12 @@ export type Command<
|
||||
name: CommandName
|
||||
groupId: T['id']
|
||||
needsReview: boolean
|
||||
reviewMessage?:
|
||||
| string
|
||||
| ReactNode
|
||||
| ((
|
||||
commandBarContext: { argumentsToSubmit: Record<string, unknown> } // Should be the commandbarMachine's context, but it creates a circular dependency
|
||||
) => string | ReactNode)
|
||||
onSubmit: (data?: CommandSchema) => void
|
||||
onCancel?: () => void
|
||||
args?: {
|
||||
@ -133,7 +140,7 @@ export type CommandArgumentConfig<
|
||||
}
|
||||
| {
|
||||
inputType: 'selection'
|
||||
selectionTypes: Selection['type'][]
|
||||
selectionTypes: Selection__old['type'][]
|
||||
multiple: boolean
|
||||
}
|
||||
| { inputType: 'kcl'; defaultValue?: string } // KCL expression inputs have simple strings as default values
|
||||
@ -181,7 +188,7 @@ export type CommandArgument<
|
||||
machineContext?: ContextFrom<T>
|
||||
) => boolean)
|
||||
skip?: boolean
|
||||
machineActor: Actor<T>
|
||||
machineActor?: Actor<T>
|
||||
/** For showing a summary display of the current value, such as in
|
||||
* the command bar's header
|
||||
*/
|
||||
@ -206,7 +213,7 @@ export type CommandArgument<
|
||||
}
|
||||
| {
|
||||
inputType: 'selection'
|
||||
selectionTypes: Selection['type'][]
|
||||
selectionTypes: Selection__old['type'][]
|
||||
multiple: boolean
|
||||
}
|
||||
| { inputType: 'kcl'; defaultValue?: string } // KCL expression inputs have simple strings as default value
|
||||
|
@ -95,3 +95,10 @@ export const MAKE_TOAST_MESSAGES = {
|
||||
ERROR_STARTING_PRINT: 'Error while starting print',
|
||||
SUCCESS: 'Started print successfully',
|
||||
}
|
||||
|
||||
/** The URL for the KCL samples manifest files */
|
||||
export const KCL_SAMPLES_MANIFEST_URLS = {
|
||||
remote:
|
||||
'https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/manifst.json',
|
||||
localFallback: '/kcl-samples-manifest-fallback.json',
|
||||
} as const
|
||||
|
31
src/lib/getKclSamplesManifest.ts
Normal file
31
src/lib/getKclSamplesManifest.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { KCL_SAMPLES_MANIFEST_URLS } from './constants'
|
||||
import { isDesktop } from './isDesktop'
|
||||
|
||||
export type KclSamplesManifestItem = {
|
||||
file: string
|
||||
title: string
|
||||
description: string
|
||||
}
|
||||
|
||||
export async function getKclSamplesManifest() {
|
||||
let response = await fetch(KCL_SAMPLES_MANIFEST_URLS.remote)
|
||||
if (!response.ok) {
|
||||
console.warn(
|
||||
'Failed to fetch latest remote KCL samples manifest, falling back to local:',
|
||||
response.statusText
|
||||
)
|
||||
response = await fetch(
|
||||
(isDesktop() ? '.' : '') + KCL_SAMPLES_MANIFEST_URLS.localFallback
|
||||
)
|
||||
if (!response.ok) {
|
||||
console.error(
|
||||
'Failed to fetch fallback KCL samples manifest:',
|
||||
response.statusText
|
||||
)
|
||||
return []
|
||||
}
|
||||
}
|
||||
return response.json().then((manifest) => {
|
||||
return manifest as KclSamplesManifestItem[]
|
||||
})
|
||||
}
|
110
src/lib/kclCommands.ts
Normal file
110
src/lib/kclCommands.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import { CommandBarOverwriteWarning } from 'components/CommandBarOverwriteWarning'
|
||||
import { Command, CommandArgumentOption } from './commandTypes'
|
||||
import { kclManager } from './singletons'
|
||||
import { isDesktop } from './isDesktop'
|
||||
import { FILE_EXT } from './constants'
|
||||
|
||||
interface OnSubmitProps {
|
||||
sampleName: string
|
||||
code: string
|
||||
method: 'overwrite' | 'newFile'
|
||||
}
|
||||
|
||||
export function kclCommands(
|
||||
onSubmit: (p: OnSubmitProps) => Promise<void>,
|
||||
providedOptions: CommandArgumentOption<string>[]
|
||||
): Command[] {
|
||||
return [
|
||||
{
|
||||
name: 'format-code',
|
||||
displayName: 'Format Code',
|
||||
description: 'Nicely formats the KCL code in the editor.',
|
||||
needsReview: false,
|
||||
groupId: 'code',
|
||||
icon: 'code',
|
||||
onSubmit: () => {
|
||||
kclManager.format()
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'open-kcl-example',
|
||||
displayName: 'Open sample',
|
||||
description: 'Imports an example KCL program into the editor.',
|
||||
needsReview: true,
|
||||
icon: 'code',
|
||||
reviewMessage: ({ argumentsToSubmit }) =>
|
||||
argumentsToSubmit.method === 'newFile'
|
||||
? 'Create a new file with the example code?'
|
||||
: CommandBarOverwriteWarning({}),
|
||||
groupId: 'code',
|
||||
onSubmit(data) {
|
||||
if (!data?.sample) {
|
||||
return
|
||||
}
|
||||
const sampleCodeUrl = `https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/${encodeURIComponent(
|
||||
data.sample.replace(FILE_EXT, '')
|
||||
)}/${encodeURIComponent(data.sample)}`
|
||||
fetch(sampleCodeUrl)
|
||||
.then(async (response) => {
|
||||
if (!response.ok) {
|
||||
console.error('Failed to fetch sample code:', response.statusText)
|
||||
return
|
||||
}
|
||||
const code = await response.text()
|
||||
|
||||
return {
|
||||
sampleName: data.sample,
|
||||
code,
|
||||
method: data.method,
|
||||
}
|
||||
})
|
||||
.then((props) => {
|
||||
if (props?.code) {
|
||||
onSubmit(props).catch(reportError)
|
||||
}
|
||||
})
|
||||
.catch(reportError)
|
||||
},
|
||||
args: {
|
||||
method: {
|
||||
inputType: 'options',
|
||||
required: true,
|
||||
skip: true,
|
||||
defaultValue: isDesktop() ? 'newFile' : 'overwrite',
|
||||
options() {
|
||||
return [
|
||||
{
|
||||
value: 'overwrite',
|
||||
name: 'Overwrite current code',
|
||||
isCurrent: !isDesktop(),
|
||||
},
|
||||
...(isDesktop()
|
||||
? [
|
||||
{
|
||||
value: 'newFile',
|
||||
name: 'Create a new file',
|
||||
isCurrent: true,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]
|
||||
},
|
||||
},
|
||||
sample: {
|
||||
inputType: 'options',
|
||||
required: true,
|
||||
valueSummary(value) {
|
||||
const MAX_LENGTH = 12
|
||||
if (typeof value === 'string') {
|
||||
return value.length > MAX_LENGTH
|
||||
? value.substring(0, MAX_LENGTH) + '...'
|
||||
: value
|
||||
}
|
||||
return value
|
||||
},
|
||||
options: providedOptions,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
34
src/lib/kclSamplesArray.json
Normal file
34
src/lib/kclSamplesArray.json
Normal file
@ -0,0 +1,34 @@
|
||||
[
|
||||
"80-20-rail",
|
||||
"a-parametric-bearing-pillow-block",
|
||||
"ball-bearing",
|
||||
"bracket",
|
||||
"brake-caliper",
|
||||
"car-wheel-assembly",
|
||||
"car-wheel",
|
||||
"enclosure",
|
||||
"flange-with-patterns",
|
||||
"flange-xy",
|
||||
"focusrite-scarlett-mounting-bracket",
|
||||
"french-press",
|
||||
"gear-rack",
|
||||
"gear",
|
||||
"hex-nut",
|
||||
"kitt",
|
||||
"lego",
|
||||
"lug-nut",
|
||||
"mounting-plate",
|
||||
"multi-axis-robot",
|
||||
"pipe-flange-assembly",
|
||||
"pipe",
|
||||
"poopy-shoe",
|
||||
"router-template-cross-bar",
|
||||
"router-template-slate",
|
||||
"screenshots",
|
||||
"sheet-metal-bracket",
|
||||
"socket-head-cap-screw",
|
||||
"step",
|
||||
"tire",
|
||||
"washer",
|
||||
"wheel-rotor"
|
||||
]
|
@ -28,12 +28,15 @@ import { AXIS_GROUP, X_AXIS } from 'clientSideScene/sceneInfra'
|
||||
import { PathToNodeMap } from 'lang/std/sketchcombos'
|
||||
import { err } from 'lib/trap'
|
||||
import {
|
||||
Artifact,
|
||||
getArtifactOfTypes,
|
||||
getArtifactsOfTypes,
|
||||
getCapCodeRef,
|
||||
getSweepEdgeCodeRef,
|
||||
getSolid2dCodeRef,
|
||||
getWallCodeRef,
|
||||
CodeRef,
|
||||
getCodeRefsByArtifactId,
|
||||
} from 'lang/std/artifactGraph'
|
||||
|
||||
export const X_AXIS_UUID = 'ad792545-7fd3-482a-a602-a93924e3055b'
|
||||
@ -41,7 +44,8 @@ export const Y_AXIS_UUID = '680fd157-266f-4b8a-984f-cdf46b8bdf01'
|
||||
|
||||
export type Axis = 'y-axis' | 'x-axis' | 'z-axis'
|
||||
|
||||
export type Selection = {
|
||||
/** @deprecated Use {@link Artifact} instead. */
|
||||
export type Selection__old = {
|
||||
type:
|
||||
| 'default'
|
||||
| 'line-end'
|
||||
@ -58,9 +62,129 @@ export type Selection = {
|
||||
| 'all'
|
||||
range: SourceRange
|
||||
}
|
||||
export type Selections = {
|
||||
/** @deprecated Use {@link Selection} instead. */
|
||||
export type Selections__old = {
|
||||
otherSelections: Axis[]
|
||||
codeBasedSelections: Selection[]
|
||||
codeBasedSelections: Selection__old[]
|
||||
}
|
||||
export interface Selection {
|
||||
artifact: Artifact
|
||||
codeRef: CodeRef
|
||||
}
|
||||
export type Selections = {
|
||||
otherSelections: Array<Axis>
|
||||
graphSelections: Array<Selection>
|
||||
}
|
||||
|
||||
/** @deprecated If you're writing a new function, it should use {@link Selection} and not {@link Selection__old}
|
||||
* this function should only be used for backwards compatibility with old functions.
|
||||
*/
|
||||
export function convertSelectionToOld(
|
||||
selection: Selection
|
||||
): Selection__old | null {
|
||||
// return {} as Selection__old
|
||||
// TODO implementation
|
||||
const _artifact = selection.artifact
|
||||
if (_artifact.type === 'solid2D') {
|
||||
const codeRef = getSolid2dCodeRef(
|
||||
_artifact,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(codeRef)) return null
|
||||
return { range: codeRef.range, type: 'solid2D' }
|
||||
// return {
|
||||
// type: 'Set selection',
|
||||
// data: {
|
||||
// selectionType: 'singleCodeCursor',
|
||||
|
||||
// selection: { range: codeRef.range, type: 'solid2D' },
|
||||
// },
|
||||
// }
|
||||
}
|
||||
if (_artifact.type === 'cap') {
|
||||
const codeRef = getCapCodeRef(_artifact, engineCommandManager.artifactGraph)
|
||||
if (err(codeRef)) return null
|
||||
return {
|
||||
range: codeRef.range,
|
||||
type: _artifact?.subType === 'end' ? 'end-cap' : 'start-cap',
|
||||
}
|
||||
// return {
|
||||
// type: 'Set selection',
|
||||
// data: {
|
||||
// selectionType: 'singleCodeCursor',
|
||||
// selection: {
|
||||
// range: codeRef.range,
|
||||
// type: _artifact?.subType === 'end' ? 'end-cap' : 'start-cap',
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
}
|
||||
if (_artifact.type === 'wall') {
|
||||
const codeRef = getWallCodeRef(
|
||||
_artifact,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(codeRef)) return null
|
||||
return { range: codeRef.range, type: 'extrude-wall' }
|
||||
// return {
|
||||
// type: 'Set selection',
|
||||
// data: {
|
||||
// selectionType: 'singleCodeCursor',
|
||||
// selection: { range: codeRef.range, type: 'extrude-wall' },
|
||||
// },
|
||||
// }
|
||||
}
|
||||
if (_artifact.type === 'segment' || _artifact.type === 'path') {
|
||||
return { range: _artifact.codeRef.range, type: 'default' }
|
||||
// return {
|
||||
// type: 'Set selection',
|
||||
// data: {
|
||||
// selectionType: 'singleCodeCursor',
|
||||
// selection: { range: _artifact.codeRef.range, type: 'default' },
|
||||
// },
|
||||
// }
|
||||
}
|
||||
if (_artifact.type === 'sweepEdge') {
|
||||
const codeRef = getSweepEdgeCodeRef(
|
||||
_artifact,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(codeRef)) return null
|
||||
if (_artifact?.subType === 'adjacent') {
|
||||
return { range: codeRef.range, type: 'adjacent-edge' }
|
||||
// return {
|
||||
// type: 'Set selection',
|
||||
// data: {
|
||||
// selectionType: 'singleCodeCursor',
|
||||
// selection: { range: codeRef.range, type: 'adjacent-edge' },
|
||||
// },
|
||||
// }
|
||||
}
|
||||
return { range: codeRef.range, type: 'edge' }
|
||||
// return {
|
||||
// type: 'Set selection',
|
||||
// data: {
|
||||
// selectionType: 'singleCodeCursor',
|
||||
// selection: { range: codeRef.range, type: 'edge' },
|
||||
// },
|
||||
// }
|
||||
}
|
||||
return null
|
||||
}
|
||||
/** @deprecated If you're writing a new function, it should use {@link Selection} and not {@link Selection__old}
|
||||
* this function should only be used for backwards compatibility with old functions.
|
||||
*/
|
||||
export function convertSelectionsToOld(selection: Selections): Selections__old {
|
||||
const selections: Selection__old[] = []
|
||||
for (const artifact of selection.graphSelections) {
|
||||
const converted = convertSelectionToOld(artifact)
|
||||
if (converted) selections.push(converted)
|
||||
}
|
||||
const selectionsOld: Selections__old = {
|
||||
otherSelections: selection.otherSelections,
|
||||
codeBasedSelections: selections,
|
||||
}
|
||||
return selectionsOld
|
||||
}
|
||||
|
||||
export async function getEventForSelectWithPoint({
|
||||
@ -85,85 +209,102 @@ export async function getEventForSelectWithPoint({
|
||||
}
|
||||
}
|
||||
let _artifact = engineCommandManager.artifactGraph.get(data.entity_id)
|
||||
if (!_artifact)
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: { selectionType: 'singleCodeCursor' },
|
||||
}
|
||||
if (_artifact.type === 'solid2D') {
|
||||
const codeRef = getSolid2dCodeRef(
|
||||
_artifact,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(codeRef)) return null
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
selection: { range: codeRef.range, type: 'solid2D' },
|
||||
},
|
||||
}
|
||||
}
|
||||
if (_artifact.type === 'cap') {
|
||||
const codeRef = getCapCodeRef(_artifact, engineCommandManager.artifactGraph)
|
||||
if (err(codeRef)) return null
|
||||
const codeRefs = getCodeRefsByArtifactId(
|
||||
data.entity_id,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (_artifact && codeRefs) {
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
selection: {
|
||||
range: codeRef.range,
|
||||
type: _artifact?.subType === 'end' ? 'end-cap' : 'start-cap',
|
||||
artifact: _artifact,
|
||||
codeRef: codeRefs[0],
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
if (_artifact.type === 'wall') {
|
||||
const codeRef = getWallCodeRef(
|
||||
_artifact,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(codeRef)) return null
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
selection: { range: codeRef.range, type: 'extrude-wall' },
|
||||
},
|
||||
}
|
||||
}
|
||||
if (_artifact.type === 'segment' || _artifact.type === 'path') {
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
selection: { range: _artifact.codeRef.range, type: 'default' },
|
||||
},
|
||||
}
|
||||
}
|
||||
if (_artifact.type === 'sweepEdge') {
|
||||
const codeRef = getSweepEdgeCodeRef(
|
||||
_artifact,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(codeRef)) return null
|
||||
if (_artifact?.subType === 'adjacent') {
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
selection: { range: codeRef.range, type: 'adjacent-edge' },
|
||||
},
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
selection: { range: codeRef.range, type: 'edge' },
|
||||
},
|
||||
}
|
||||
}
|
||||
// if (!_artifact)
|
||||
// return {
|
||||
// type: 'Set selection',
|
||||
// data: { selectionType: 'singleCodeCursor' },
|
||||
// }
|
||||
// if (_artifact.type === 'solid2D') {
|
||||
// const codeRef = getSolid2dCodeRef(
|
||||
// _artifact,
|
||||
// engineCommandManager.artifactGraph
|
||||
// )
|
||||
// if (err(codeRef)) return null
|
||||
// return {
|
||||
// type: 'Set selection',
|
||||
// data: {
|
||||
// selectionType: 'singleCodeCursor',
|
||||
|
||||
// // selection: { range: codeRef.range, type: 'solid2D' },
|
||||
// },
|
||||
// }
|
||||
// }
|
||||
// if (_artifact.type === 'cap') {
|
||||
// const codeRef = getCapCodeRef(_artifact, engineCommandManager.artifactGraph)
|
||||
// if (err(codeRef)) return null
|
||||
// return {
|
||||
// type: 'Set selection',
|
||||
// data: {
|
||||
// selectionType: 'singleCodeCursor',
|
||||
// selection: {
|
||||
// range: codeRef.range,
|
||||
// type: _artifact?.subType === 'end' ? 'end-cap' : 'start-cap',
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
// }
|
||||
// if (_artifact.type === 'wall') {
|
||||
// const codeRef = getWallCodeRef(
|
||||
// _artifact,
|
||||
// engineCommandManager.artifactGraph
|
||||
// )
|
||||
// if (err(codeRef)) return null
|
||||
// return {
|
||||
// type: 'Set selection',
|
||||
// data: {
|
||||
// selectionType: 'singleCodeCursor',
|
||||
// selection: { range: codeRef.range, type: 'extrude-wall' },
|
||||
// },
|
||||
// }
|
||||
// }
|
||||
// if (_artifact.type === 'segment' || _artifact.type === 'path') {
|
||||
// return {
|
||||
// type: 'Set selection',
|
||||
// data: {
|
||||
// selectionType: 'singleCodeCursor',
|
||||
// selection: { range: _artifact.codeRef.range, type: 'default' },
|
||||
// },
|
||||
// }
|
||||
// }
|
||||
// if (_artifact.type === 'sweepEdge') {
|
||||
// const codeRef = getSweepEdgeCodeRef(
|
||||
// _artifact,
|
||||
// engineCommandManager.artifactGraph
|
||||
// )
|
||||
// if (err(codeRef)) return null
|
||||
// if (_artifact?.subType === 'adjacent') {
|
||||
// return {
|
||||
// type: 'Set selection',
|
||||
// data: {
|
||||
// selectionType: 'singleCodeCursor',
|
||||
// selection: { range: codeRef.range, type: 'adjacent-edge' },
|
||||
// },
|
||||
// }
|
||||
// }
|
||||
// return {
|
||||
// type: 'Set selection',
|
||||
// data: {
|
||||
// selectionType: 'singleCodeCursor',
|
||||
// selection: { range: codeRef.range, type: 'edge' },
|
||||
// },
|
||||
// }
|
||||
// }
|
||||
return null
|
||||
}
|
||||
|
||||
@ -182,36 +323,56 @@ export function getEventForSegmentSelection(
|
||||
},
|
||||
}
|
||||
}
|
||||
const pathToNode = group?.userData?.pathToNode
|
||||
if (!pathToNode) return null
|
||||
// previous drags don't update ast for efficiency reasons
|
||||
// So we want to make sure we have and updated ast with
|
||||
// accurate source ranges
|
||||
const updatedAst = parse(codeManager.code)
|
||||
if (err(updatedAst)) return null
|
||||
|
||||
const nodeMeta = getNodeFromPath<CallExpression>(
|
||||
updatedAst,
|
||||
pathToNode,
|
||||
'CallExpression'
|
||||
// console.log('group', group?.userData.)
|
||||
const id = group?.userData?.id
|
||||
if (!id) return null
|
||||
const artifact = engineCommandManager.artifactGraph.get(id)
|
||||
const codeRefs = getCodeRefsByArtifactId(
|
||||
id,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(nodeMeta)) return null
|
||||
|
||||
const node = nodeMeta.node
|
||||
const range: SourceRange = [node.start, node.end]
|
||||
console.log('artifact', artifact, group.userData)
|
||||
if (!artifact || !codeRefs) return null
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
selection: { range, type: 'default' },
|
||||
selection: {
|
||||
artifact,
|
||||
codeRef: codeRefs[0],
|
||||
},
|
||||
},
|
||||
}
|
||||
// const pathToNode = group?.userData?.pathToNode
|
||||
// if (!pathToNode) return null
|
||||
// // previous drags don't update ast for efficiency reasons
|
||||
// // So we want to make sure we have and updated ast with
|
||||
// // accurate source ranges
|
||||
// const updatedAst = parse(codeManager.code)
|
||||
// if (err(updatedAst)) return null
|
||||
|
||||
// const nodeMeta = getNodeFromPath<CallExpression>(
|
||||
// updatedAst,
|
||||
// pathToNode,
|
||||
// 'CallExpression'
|
||||
// )
|
||||
// if (err(nodeMeta)) return null
|
||||
|
||||
// const node = nodeMeta.node
|
||||
// const range: SourceRange = [node.start, node.end]
|
||||
// return {
|
||||
// type: 'Set selection',
|
||||
// data: {
|
||||
// selectionType: 'singleCodeCursor',
|
||||
// selection: { range, type: 'default' },
|
||||
// },
|
||||
// }
|
||||
}
|
||||
|
||||
export function handleSelectionBatch({
|
||||
selections,
|
||||
}: {
|
||||
selections: Selections
|
||||
selections: Selections__old
|
||||
}): {
|
||||
engineEvents: Models['WebSocketRequest_type'][]
|
||||
codeMirrorSelection: EditorSelection
|
||||
@ -252,7 +413,7 @@ export function handleSelectionBatch({
|
||||
}
|
||||
}
|
||||
|
||||
type SelectionToEngine = { type: Selection['type']; id: string }
|
||||
type SelectionToEngine = { type: Selection__old['type']; id: string }
|
||||
|
||||
export function processCodeMirrorRanges({
|
||||
codeMirrorRanges,
|
||||
@ -260,14 +421,15 @@ export function processCodeMirrorRanges({
|
||||
isShiftDown,
|
||||
}: {
|
||||
codeMirrorRanges: readonly SelectionRange[]
|
||||
selectionRanges: Selections
|
||||
selectionRanges: Selections__old
|
||||
isShiftDown: boolean
|
||||
}): null | {
|
||||
modelingEvent: ModelingMachineEvent
|
||||
engineEvents: Models['WebSocketRequest_type'][]
|
||||
} {
|
||||
const isChange =
|
||||
codeMirrorRanges.length !== selectionRanges.codeBasedSelections.length ||
|
||||
// todo should this take old or new selections?
|
||||
codeMirrorRanges.length !== selectionRanges?.codeBasedSelections?.length ||
|
||||
codeMirrorRanges.some(({ from, to }, i) => {
|
||||
return (
|
||||
from !== selectionRanges.codeBasedSelections[i].range[0] ||
|
||||
@ -276,7 +438,7 @@ export function processCodeMirrorRanges({
|
||||
})
|
||||
|
||||
if (!isChange) return null
|
||||
const codeBasedSelections: Selections['codeBasedSelections'] =
|
||||
const codeBasedSelections: Selections__old['codeBasedSelections'] =
|
||||
codeMirrorRanges.map(({ from, to }) => {
|
||||
return {
|
||||
type: 'default',
|
||||
@ -285,6 +447,15 @@ export function processCodeMirrorRanges({
|
||||
})
|
||||
const idBasedSelections: SelectionToEngine[] =
|
||||
codeToIdSelections(codeBasedSelections)
|
||||
const artifacts: Selection[] = []
|
||||
for (const { id } of idBasedSelections) {
|
||||
const artifact = engineCommandManager.artifactGraph.get(id)
|
||||
const codeRefs = getCodeRefsByArtifactId(
|
||||
id,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (artifact && codeRefs) artifacts.push({ artifact, codeRef: codeRefs[0] })
|
||||
}
|
||||
|
||||
if (!selectionRanges) return null
|
||||
updateSceneObjectColors(codeBasedSelections)
|
||||
@ -295,7 +466,7 @@ export function processCodeMirrorRanges({
|
||||
selectionType: 'mirrorCodeMirrorSelections',
|
||||
selection: {
|
||||
otherSelections: isShiftDown ? selectionRanges.otherSelections : [],
|
||||
codeBasedSelections,
|
||||
graphSelections: artifacts,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -303,7 +474,7 @@ export function processCodeMirrorRanges({
|
||||
}
|
||||
}
|
||||
|
||||
function updateSceneObjectColors(codeBasedSelections: Selection[]) {
|
||||
function updateSceneObjectColors(codeBasedSelections: Selection__old[]) {
|
||||
const updated = kclManager.ast
|
||||
|
||||
Object.values(sceneEntitiesManager.activeSegments).forEach((segmentGroup) => {
|
||||
@ -358,7 +529,7 @@ function resetAndSetEngineEntitySelectionCmds(
|
||||
]
|
||||
}
|
||||
|
||||
export function isSketchPipe(selectionRanges: Selections) {
|
||||
export function isSketchPipe(selectionRanges: Selections__old) {
|
||||
if (!isSingleCursorInPipe(selectionRanges, kclManager.ast)) return false
|
||||
return isCursorInSketchCommandRange(
|
||||
engineCommandManager.artifactGraph,
|
||||
@ -367,14 +538,14 @@ export function isSketchPipe(selectionRanges: Selections) {
|
||||
}
|
||||
|
||||
export function isSelectionLastLine(
|
||||
selectionRanges: Selections,
|
||||
selectionRanges: Selections__old,
|
||||
code: string,
|
||||
i = 0
|
||||
) {
|
||||
return selectionRanges.codeBasedSelections[i].range[1] === code.length
|
||||
}
|
||||
|
||||
export function isRangeBetweenCharacters(selectionRanges: Selections) {
|
||||
export function isRangeBetweenCharacters(selectionRanges: Selections__old) {
|
||||
return (
|
||||
selectionRanges.codeBasedSelections.length === 1 &&
|
||||
selectionRanges.codeBasedSelections[0].range[0] === 0 &&
|
||||
@ -383,11 +554,14 @@ export function isRangeBetweenCharacters(selectionRanges: Selections) {
|
||||
}
|
||||
|
||||
export type CommonASTNode = {
|
||||
selection: Selection
|
||||
selection: Selection__old
|
||||
ast: Program
|
||||
}
|
||||
|
||||
function buildCommonNodeFromSelection(selectionRanges: Selections, i: number) {
|
||||
function buildCommonNodeFromSelection(
|
||||
selectionRanges: Selections__old,
|
||||
i: number
|
||||
) {
|
||||
return {
|
||||
selection: selectionRanges.codeBasedSelections[i],
|
||||
ast: kclManager.ast,
|
||||
@ -420,7 +594,7 @@ function nodeHasCircle(node: CommonASTNode) {
|
||||
})
|
||||
}
|
||||
|
||||
export function canSweepSelection(selection: Selections) {
|
||||
export function canSweepSelection(selection: Selections__old) {
|
||||
const commonNodes = selection.codeBasedSelections.map((_, i) =>
|
||||
buildCommonNodeFromSelection(selection, i)
|
||||
)
|
||||
@ -433,7 +607,7 @@ export function canSweepSelection(selection: Selections) {
|
||||
)
|
||||
}
|
||||
|
||||
export function canFilletSelection(selection: Selections) {
|
||||
export function canFilletSelection(selection: Selections__old) {
|
||||
const commonNodes = selection.codeBasedSelections.map((_, i) =>
|
||||
buildCommonNodeFromSelection(selection, i)
|
||||
) // TODO FILLET DUMMY PLACEHOLDER
|
||||
@ -444,7 +618,7 @@ export function canFilletSelection(selection: Selections) {
|
||||
)
|
||||
}
|
||||
|
||||
function canExtrudeSelectionItem(selection: Selections, i: number) {
|
||||
function canExtrudeSelectionItem(selection: Selections__old, i: number) {
|
||||
const isolatedSelection = {
|
||||
...selection,
|
||||
codeBasedSelections: [selection.codeBasedSelections[i]],
|
||||
@ -459,7 +633,7 @@ function canExtrudeSelectionItem(selection: Selections, i: number) {
|
||||
}
|
||||
|
||||
// This accounts for non-geometry selections under "other"
|
||||
export type ResolvedSelectionType = [Selection['type'] | 'other', number]
|
||||
export type ResolvedSelectionType = [Selection__old['type'] | 'other', number]
|
||||
|
||||
/**
|
||||
* In the future, I'd like this function to properly return the type of each selected entity based on
|
||||
@ -469,8 +643,9 @@ export type ResolvedSelectionType = [Selection['type'] | 'other', number]
|
||||
* @returns
|
||||
*/
|
||||
export function getSelectionType(
|
||||
selection: Selections
|
||||
selection?: Selections__old
|
||||
): ResolvedSelectionType[] {
|
||||
if (!selection) return []
|
||||
const extrudableCount = selection.codeBasedSelections.filter((_, i) => {
|
||||
const singleSelection = {
|
||||
...selection,
|
||||
@ -485,7 +660,7 @@ export function getSelectionType(
|
||||
}
|
||||
|
||||
export function getSelectionTypeDisplayText(
|
||||
selection: Selections
|
||||
selection?: Selections__old
|
||||
): string | null {
|
||||
const selectionsByType = getSelectionType(selection)
|
||||
|
||||
@ -517,7 +692,7 @@ export function canSubmitSelectionArg(
|
||||
}
|
||||
|
||||
function codeToIdSelections(
|
||||
codeBasedSelections: Selection[]
|
||||
codeBasedSelections: Selection__old[]
|
||||
): SelectionToEngine[] {
|
||||
return codeBasedSelections
|
||||
.flatMap(({ type, range, ...rest }): null | SelectionToEngine[] => {
|
||||
@ -683,13 +858,13 @@ export async function sendSelectEventToEngine(
|
||||
|
||||
export function updateSelections(
|
||||
pathToNodeMap: PathToNodeMap,
|
||||
prevSelectionRanges: Selections,
|
||||
prevSelectionRanges: Selections__old,
|
||||
ast: Program | Error
|
||||
): Selections | Error {
|
||||
): Selections__old | Error {
|
||||
if (err(ast)) return ast
|
||||
|
||||
const newSelections = Object.entries(pathToNodeMap)
|
||||
.map(([index, pathToNode]): Selection | undefined => {
|
||||
.map(([index, pathToNode]): Selection__old | undefined => {
|
||||
const nodeMeta = getNodeFromPath<Expr>(ast, pathToNode)
|
||||
if (err(nodeMeta)) return undefined
|
||||
const node = nodeMeta.node
|
||||
@ -698,7 +873,7 @@ export function updateSelections(
|
||||
type: prevSelectionRanges.codeBasedSelections[Number(index)]?.type,
|
||||
}
|
||||
})
|
||||
.filter((x?: Selection) => x !== undefined) as Selection[]
|
||||
.filter((x?: Selection__old) => x !== undefined) as Selection__old[]
|
||||
|
||||
return {
|
||||
codeBasedSelections:
|
||||
@ -708,3 +883,74 @@ export function updateSelections(
|
||||
otherSelections: prevSelectionRanges.otherSelections,
|
||||
}
|
||||
}
|
||||
|
||||
// using artifact as the selection is maybe not such a good idea.
|
||||
// is the artifact stable, once you add a constrain, there will a new artifact graph
|
||||
// then the ids will not match up
|
||||
export function updateSelections2(
|
||||
pathToNodeMap: PathToNodeMap,
|
||||
prevSelectionRanges: Selections,
|
||||
ast: Program | Error
|
||||
): Selections | Error {
|
||||
if (err(ast)) return ast
|
||||
|
||||
const newSelections = Object.entries(pathToNodeMap)
|
||||
.map(([index, pathToNode]): Selection | undefined => {
|
||||
const previousSelection =
|
||||
prevSelectionRanges.graphSelections[Number(index)]
|
||||
const nodeMeta = getNodeFromPath<Expr>(ast, pathToNode)
|
||||
if (err(nodeMeta)) return undefined
|
||||
const node = nodeMeta.node
|
||||
let artifact: Artifact | null = null
|
||||
for (const [id, a] of engineCommandManager.artifactGraph) {
|
||||
if (previousSelection.artifact.type === a.type) {
|
||||
const codeRefs = getCodeRefsByArtifactId(
|
||||
id,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
console.log('codeRef', codeRefs)
|
||||
if (!codeRefs) continue
|
||||
if (
|
||||
JSON.stringify(codeRefs[0].pathToNode) ===
|
||||
JSON.stringify(pathToNode)
|
||||
) {
|
||||
artifact = a
|
||||
console.log('found artifact', a)
|
||||
break
|
||||
}
|
||||
}
|
||||
// if (
|
||||
// a.codeRef.range[0] === node.start &&
|
||||
// a.codeRef.range[1] === node.end
|
||||
// ) {
|
||||
// artifact = a
|
||||
// break
|
||||
// }
|
||||
}
|
||||
if (!artifact) return undefined
|
||||
return {
|
||||
artifact: artifact,
|
||||
codeRef: {
|
||||
range: [node.start, node.end],
|
||||
pathToNode: pathToNode,
|
||||
},
|
||||
// codeRef: {
|
||||
// range: [node.start, node.end],
|
||||
// pathToNode: pathToNode,
|
||||
// },
|
||||
}
|
||||
// return {
|
||||
// range: [node.start, node.end],
|
||||
// type: prevSelectionRanges.codeBasedSelections[Number(index)]?.type,
|
||||
// }
|
||||
})
|
||||
.filter((x?: Selection) => x !== undefined) as Selection[]
|
||||
|
||||
return {
|
||||
graphSelections:
|
||||
newSelections.length > 0
|
||||
? newSelections
|
||||
: prevSelectionRanges.graphSelections,
|
||||
otherSelections: prevSelectionRanges.otherSelections,
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import { ProgramMemory, Expr, parse } from 'lang/wasm'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { executeAst } from 'lang/langHelpers'
|
||||
import { err, trap } from 'lib/trap'
|
||||
import { convertSelectionsToOld } from './selections'
|
||||
|
||||
const isValidVariableName = (name: string) =>
|
||||
/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)
|
||||
@ -34,9 +35,10 @@ export function useCalculateKclExpression({
|
||||
} {
|
||||
const { programMemory, code } = useKclContext()
|
||||
const { context } = useModelingContext()
|
||||
const selectionOld = convertSelectionsToOld(context.selectionRanges)
|
||||
const selectionRange:
|
||||
| (typeof context.selectionRanges.codeBasedSelections)[number]['range']
|
||||
| undefined = context.selectionRanges.codeBasedSelections[0]?.range
|
||||
| (typeof selectionOld.codeBasedSelections)[number]['range']
|
||||
| undefined = selectionOld.codeBasedSelections[0]?.range
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const [availableVarInfo, setAvailableVarInfo] = useState<
|
||||
ReturnType<typeof findAllPreviousVariables>
|
||||
|
@ -3,12 +3,13 @@ import { kclManager } from 'lib/singletons'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { findAllPreviousVariables } from 'lang/queryAst'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { convertSelectionsToOld } from './selections'
|
||||
|
||||
export function usePreviousVariables() {
|
||||
const { programMemory, code } = useKclContext()
|
||||
const { context } = useModelingContext()
|
||||
const selectionRange = context.selectionRanges.codeBasedSelections[0]
|
||||
?.range || [code.length, code.length]
|
||||
const selectionRange = convertSelectionsToOld(context.selectionRanges)
|
||||
.codeBasedSelections[0]?.range || [code.length, code.length]
|
||||
const [previousVariablesInfo, setPreviousVariablesInfo] = useState<
|
||||
ReturnType<typeof findAllPreviousVariables>
|
||||
>({
|
||||
|
@ -5,14 +5,14 @@ import {
|
||||
CommandArgumentWithName,
|
||||
KclCommandValue,
|
||||
} from 'lib/commandTypes'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Selections__old } from 'lib/selections'
|
||||
import { getCommandArgumentKclValuesOnly } from 'lib/commandUtils'
|
||||
|
||||
export type CommandBarContext = {
|
||||
commands: Command[]
|
||||
selectedCommand?: Command
|
||||
currentArgument?: CommandArgument<unknown> & { name: string }
|
||||
selectionRanges: Selections
|
||||
selectionRanges: Selections__old
|
||||
argumentsToSubmit: { [x: string]: unknown }
|
||||
}
|
||||
|
||||
@ -283,7 +283,7 @@ export const commandBarMachine = setup({
|
||||
typeof argConfig.options === 'function'
|
||||
? argConfig.options(
|
||||
input,
|
||||
argConfig.machineActor.getSnapshot().context
|
||||
argConfig.machineActor?.getSnapshot().context
|
||||
)
|
||||
: argConfig.options
|
||||
).some((o) => o.value === argValue)
|
||||
|
@ -20,6 +20,7 @@ type FileMachineEvents =
|
||||
makeDir: boolean
|
||||
content?: string
|
||||
silent?: boolean
|
||||
shouldSetToRename?: boolean
|
||||
}
|
||||
}
|
||||
| { type: 'Delete file'; data: FileEntry }
|
||||
@ -42,6 +43,7 @@ type FileMachineEvents =
|
||||
output: {
|
||||
message: string
|
||||
path: string
|
||||
shouldSetToRename: boolean
|
||||
}
|
||||
}
|
||||
| {
|
||||
@ -107,6 +109,10 @@ export const fileMachine = setup({
|
||||
},
|
||||
'Is not silent': ({ event }) =>
|
||||
event.type === 'Create file' ? !event.data.silent : false,
|
||||
'Should set to rename': ({ event }) =>
|
||||
(event.type === 'xstate.done.actor.create-and-open-file' &&
|
||||
event.output.shouldSetToRename) ||
|
||||
false,
|
||||
},
|
||||
actors: {
|
||||
readFiles: fromPromise(({ input }: { input: Project }) =>
|
||||
@ -119,6 +125,7 @@ export const fileMachine = setup({
|
||||
makeDir: boolean
|
||||
selectedDirectory: FileEntry
|
||||
content: string
|
||||
shouldSetToRename: boolean
|
||||
}
|
||||
}) => Promise.resolve({ message: '', path: '' })
|
||||
),
|
||||
@ -149,7 +156,7 @@ export const fileMachine = setup({
|
||||
),
|
||||
},
|
||||
}).createMachine({
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QDECWAbMACAtgQwGMALVAOzAGI9ZZUpSBtABgF1FQAHAe1oBdUupdiAAeiACwAmADQgAnogAcANgCsAOnHiAjOICcAZh3K9TRQHYAvpdlpMuQiXIUASmABmAJzhFmbJCDcfAJCAWIIUrIKCHpq6nraipJJKorahqrWthjY+MRkYOoAEtRYpFxY7jmwFADC3ni82FWYfsJBqPyCwuG6hurKTAYG5mlSyeJRiHqS2prKg6Mj2pKz4lkgdrmOBcWlLXCuYKR4OM05bQEdXaGgvdpD6qPiioqqieJM2gaqUxELT3MQz0eleBlMhmUGy2Dny5D2sEq1TqDSaSNarHaPE6IR6iD6BgGQxGY1Wikm8gkQM0n2UknERm0qmUw2hOVhTkKJURBxq9TAjXOrW0-k42JueIQBKJw1GujJFOiqkkTE0X3M5mUuiYgxmbPseU5CPRhwAImBMGiDpcxcFumF8dptMp1GZRlqNYYdXo-ml1IlzMkHuZVAZJAY1PrtnCuftkQB5DjHE02wLi3EOqX6QmDWWkiZ-HSKf3gph6ZWqcwJIZRjm7bkmmoAZTAvCwsAtYAITQgWAgqG83a4njkqeuGbu+MkLPU7x+iiGszJfwj4ldTvB6UURh0klrht2-MaZCgWDwpF7XCTpBPJooEEEhTIADcuABrQoEVFgAC054gP5XscP7WpiVzpvak5SnO6hJD8RYfJ8ir4kw06zqoTDiMyTA4WGPz7js8JHvwpCnv+WBATepF3mAnieMO6gcOgjTuMOODqF+ApNH+F6AdeIEXGBto4pBoj4u8GjOiMqjiKMqhJFhfyVqqEbJIoCTkmk3wETG6huCcOC3gc96PuoL7voU3gGb+oGimmdq3GJCARuY8RMroEk6MMihKeWrpYepEZMKMSw6Ua+mnEZOQULR9GeIxzG8KxnjsVZpw2YJdnjqJ4QjK59KaoGLKhh6fyBpIsFgtqKjKuCYW7OalpRZgJnwuZH7qBAnbcbZWIOZKeXqAVyhFT8EbaOYK44f6kjlhYG6FVYNibOyB7wo1rbNZQsUMUxLFsZ13UZRiWUQY5uUakNskjdOY2lZSCAqhV24LlhHpMl89Xwm4eD9tRvKtU+pCvh1DQAbyY5nZKMwqZWwxqMorzltoZUrK6YbOlJoazIoX2FD9f2ngDD5tcDFnqGDAmYLADAin1InndMKrqD85jw8ySPvH8pgulqoYWEjc16HjekCoTjYxXRu2JclqVi1TcCQ-1mYwyzcMRhz6lcw9C56Cz4Yatd05ISLxFbYDZlkx1nGCgrSsM5KTJVgMMmjKkEYmAYfwrOkQ30i8WFSF8mTLTCa2FGb-3RTt8V7UlB02z1mX0xKmZMgu8R6C8YahqYwUow9TqBkNxXiLmUgGEyIsRYZUctSTQMg5ZxzpXbdPgcrUEuW57xYUyXkGD5D2Bhog9aKsLyzQywsbOUXXwAEYeEWAKcTk5P7KH8G+ujhuHDDJTJZ0t2QGsvxrlI2q85fiBhlgMZcQq8+iqDJ3OzAML2qCCqxDEkIsNryK+jMpSV1clIck3xB6ViLIWEwmhXiJF0EYIqptUS3nIpRLaQDHajAqvKCwqxEZTxkIXVChJNTqUDCkB4L9q4t1rkTHI2DMyRAeosIawxFxDESMoLCIsNokUYZgZhUF1IDGdK7LyWgX6TULqCIagYcKSHMAya6VdQ6rTPgTLaC9hKpygnSOY8FA7kj0J6WR0QISzn0J8IYN0tIi0TMcLBHcHZp1wf6cB5UiFZxIdEcEhJKyvQ9BqGSqCuIuL0WvXoHj8HeKSL472E0KrBRfrVRGL9cbWEsEAA */
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QDECWAbMACAtgQwGMALVAOzAGI9ZZUpSBtABgF1FQAHAe1oBdUupdiAAeiACwAmADQgAnogAcANgCsAOnHiAjOICcAZh3K9TRQHYAvpdlpMuQiXIUASmABmAJzhFmbJCDcfAJCAWIIUrIKCHpq6nraipJJKorahqrWthjY+MRkYOoAEtRYpFxY7jmwFADC3ni82FWYfsJBqPyCwuG6hurKTAYG5mlSyeJRiHqS2prKg6Mj2pKz4lkgdrmOBcWlLXCuYKR4OM05bQEdXaGgvdpD6qPiioqqieJM2gaqUxELT3MQz0eleBlMhmUGy2Dny5D2sEq1TqDSaSNarHaPE6IR6iF0ij08SGklUpikym0qkm8gkJie1KYklB70UBkkTChNk2OVhTkKJURBxq9TAjXOrW0-k42JueIQfQMAyGIzGq0UNOiqg5mi+5nMlM+gxm0N5eX5CPRhwAImBMGiDpcZcFumF8dptMp1GZRpT9YZOXo-ml1IlzMkHuZVOyDGpTfZzbtBVaagB5DjHK1OwKy3FuhX6JWDYajXTqzUSRKh8FMPTa1TmBJDePbOEC-bIgDKYF4WFgdrABCaECwEFQ3iHXE8cmz1zzd3xkmUSveP0UJJWyT+sfE3o94PSbK0KxbfN2osaZCgWDwpBHXAzpCvVooEEEhTIADcuABrQoEVEwAAWlvCAgIfY4gMdTErlzV0FwVH5dx3ZdtUSA9lD+B4qW9EkmE5ZkEnMAxT0TeEL34Uhr1ArAIKfKiXzfeEv1-f9AJAu9wMfKCLilLEXVuURpmUcw91JakDAsLQRiwplFHUIxlDeUxZlGRRSJ2cjUWfGi6OfA4KDATxPCndQOHQRp3CnHB1AAsUmg4sC6J4jFpRzAT5SpHDPRGalRlUJJxF+WkEAbJgFOUZJCQ+NJvg0tt1DcE4cH0nJX3fdQWL-dRvGS4DoLcud4KEhBY1EhJ3iCqkdGGRQ-jJDQmCCwlYyYUYlnii0ktOVLMHS5jSG-bLctOfLeMKuDBPCMr4i8qrqW+SS-nDDRJK0VYXmZcRwU63ZupShiDKMkzPDMizeCszwbJGs4XLAWdJvlEZRMkcQDXDVDY20cxltWdRXjZXQzDUSQdu5GEyMKW17V6ygmI-QbWPUCABwcgr+JxYrpv1dRXvepcfi+n6QoMfDQ2ZALzH3d6rHBs1NKh1HYcM4zTPMyzrOR1GxtcjG5XzZ7cbekSCejP0-g5SR-skprVD9Kkvl2+E3DwMdDuReHMsR4axTA4UHo8-MZnCn5iNjOXXjrbRlpWb12U9Hzo1mdS6YTBnEt12Gak1rLCgaPXqgYPjYMNhDjYUhthjUJTCXeP5TC9SlowsS260JJXChVtXr2FFmTrOjmrpy3W7tgA3Mam6YdVNqOLdj62QvXIkY31YWl0+dZXdbC0KOZn3tbY+yefumDnQrzyGyJNQ3teJTYxMAwsNmIkNpeIKpC+TIu7PLT7OZ462fOy6bLs8U7vL-mEKpdd4j0F52WjUw2ob6IPXDXHUPEYspAMKlrG5coKN4ABAhgzPm84SpAUwiFKBuF8L7iZGoEEPwM6WnKCmcBWN8Skynl-CErx9CqGpPHWYAw2TKRmBySSkhUHJmFJgyuiFvqaAmItN45gdB1RCngzQrxEi6CMB9VBvcGK6UfLDBhnlRhSzLBYVYSktoyBCg8UGTwlJ6HDCkB4RDUH7QkSHce+ZIghUWLjYYeFf4qCCqg6GPZ9Fj0viVQkAxPR+RqloIhxNX6glxuGfCkgOGCKTroz26tMDAIcRA8IkU5hIXXhqDRjYvHTFrOoakd98JlQjNoVB6Zjj2PcoYq+0jQxSDkUuJId8lHRHBCuUYTU-T6mpMI7SYSwCSPzN9JIpTkjhgqYorC30pZtSIdqWMbwAr-0sEAA */
|
||||
id: 'File machine',
|
||||
|
||||
initial: 'Reading files',
|
||||
@ -222,15 +229,41 @@ export const fileMachine = setup({
|
||||
makeDir: false,
|
||||
selectedDirectory: context.selectedDirectory,
|
||||
content: '',
|
||||
shouldSetToRename: false,
|
||||
}
|
||||
return {
|
||||
name: event.data.name,
|
||||
makeDir: event.data.makeDir,
|
||||
selectedDirectory: context.selectedDirectory,
|
||||
content: event.data.content ?? '',
|
||||
shouldSetToRename: event.data.shouldSetToRename ?? false,
|
||||
}
|
||||
},
|
||||
onDone: [
|
||||
{
|
||||
target: 'Reading files',
|
||||
|
||||
actions: [
|
||||
{
|
||||
type: 'createToastSuccess',
|
||||
params: ({
|
||||
event,
|
||||
}: {
|
||||
// TODO: rely on type inference
|
||||
event: Extract<
|
||||
FileMachineEvents,
|
||||
{ type: 'xstate.done.actor.create-and-open-file' }
|
||||
>
|
||||
}) => {
|
||||
return { message: event.output.message }
|
||||
},
|
||||
},
|
||||
'addFileToRenamingQueue',
|
||||
'navigateToFile',
|
||||
],
|
||||
|
||||
guard: 'Should set to rename',
|
||||
},
|
||||
{
|
||||
target: 'Reading files',
|
||||
actions: [
|
||||
@ -248,7 +281,6 @@ export const fileMachine = setup({
|
||||
return { message: event.output.message }
|
||||
},
|
||||
},
|
||||
'addFileToRenamingQueue',
|
||||
'navigateToFile',
|
||||
],
|
||||
},
|
||||
|
@ -5,7 +5,14 @@ import {
|
||||
parse,
|
||||
recast,
|
||||
} from 'lang/wasm'
|
||||
import { Axis, Selection, Selections, updateSelections } from 'lib/selections'
|
||||
import {
|
||||
Axis,
|
||||
convertSelectionsToOld,
|
||||
convertSelectionToOld,
|
||||
Selections,
|
||||
Selection,
|
||||
updateSelections,
|
||||
} from 'lib/selections'
|
||||
import { assign, fromPromise, setup } from 'xstate'
|
||||
import { SidebarType } from 'components/ModelingSidebar/ModelingPanes'
|
||||
import {
|
||||
@ -299,7 +306,7 @@ export const modelingMachineDefaultContext: ModelingMachineContext = {
|
||||
selection: [],
|
||||
selectionRanges: {
|
||||
otherSelections: [],
|
||||
codeBasedSelections: [],
|
||||
graphSelections: [],
|
||||
},
|
||||
sketchDetails: {
|
||||
sketchPathToNode: [],
|
||||
@ -350,18 +357,24 @@ export const modelingMachine = setup({
|
||||
'is editing existing sketch': ({ context: { sketchDetails } }) =>
|
||||
isEditingExistingSketch({ sketchDetails }),
|
||||
'Can make selection horizontal': ({ context: { selectionRanges } }) => {
|
||||
const info = horzVertInfo(selectionRanges, 'horizontal')
|
||||
const info = horzVertInfo(
|
||||
convertSelectionsToOld(selectionRanges),
|
||||
'horizontal'
|
||||
)
|
||||
if (trap(info)) return false
|
||||
return info.enabled
|
||||
},
|
||||
'Can make selection vertical': ({ context: { selectionRanges } }) => {
|
||||
const info = horzVertInfo(selectionRanges, 'vertical')
|
||||
const info = horzVertInfo(
|
||||
convertSelectionsToOld(selectionRanges),
|
||||
'vertical'
|
||||
)
|
||||
if (trap(info)) return false
|
||||
return info.enabled
|
||||
},
|
||||
'Can constrain horizontal distance': ({ context: { selectionRanges } }) => {
|
||||
const info = horzVertDistanceInfo({
|
||||
selectionRanges,
|
||||
selectionRanges: convertSelectionsToOld(selectionRanges),
|
||||
constraint: 'setHorzDistance',
|
||||
})
|
||||
if (trap(info)) return false
|
||||
@ -369,47 +382,59 @@ export const modelingMachine = setup({
|
||||
},
|
||||
'Can constrain vertical distance': ({ context: { selectionRanges } }) => {
|
||||
const info = horzVertDistanceInfo({
|
||||
selectionRanges,
|
||||
selectionRanges: convertSelectionsToOld(selectionRanges),
|
||||
constraint: 'setVertDistance',
|
||||
})
|
||||
if (trap(info)) return false
|
||||
return info.enabled
|
||||
},
|
||||
'Can constrain ABS X': ({ context: { selectionRanges } }) => {
|
||||
const info = absDistanceInfo({ selectionRanges, constraint: 'xAbs' })
|
||||
const info = absDistanceInfo({
|
||||
selectionRanges: convertSelectionsToOld(selectionRanges),
|
||||
constraint: 'xAbs',
|
||||
})
|
||||
if (trap(info)) return false
|
||||
return info.enabled
|
||||
},
|
||||
'Can constrain ABS Y': ({ context: { selectionRanges } }) => {
|
||||
const info = absDistanceInfo({ selectionRanges, constraint: 'yAbs' })
|
||||
const info = absDistanceInfo({
|
||||
selectionRanges: convertSelectionsToOld(selectionRanges),
|
||||
constraint: 'yAbs',
|
||||
})
|
||||
if (trap(info)) return false
|
||||
return info.enabled
|
||||
},
|
||||
'Can constrain angle': ({ context: { selectionRanges } }) => {
|
||||
const angleBetween = angleBetweenInfo({ selectionRanges })
|
||||
const angleBetween = angleBetweenInfo({
|
||||
selectionRanges: convertSelectionsToOld(selectionRanges),
|
||||
})
|
||||
if (trap(angleBetween)) return false
|
||||
const angleLength = angleLengthInfo({
|
||||
selectionRanges,
|
||||
selectionRanges: convertSelectionsToOld(selectionRanges),
|
||||
angleOrLength: 'setAngle',
|
||||
})
|
||||
if (trap(angleLength)) return false
|
||||
return angleBetween.enabled || angleLength.enabled
|
||||
},
|
||||
'Can constrain length': ({ context: { selectionRanges } }) => {
|
||||
const angleLength = angleLengthInfo({ selectionRanges })
|
||||
const angleLength = angleLengthInfo({
|
||||
selectionRanges: convertSelectionsToOld(selectionRanges),
|
||||
})
|
||||
if (trap(angleLength)) return false
|
||||
return angleLength.enabled
|
||||
},
|
||||
'Can constrain perpendicular distance': ({
|
||||
context: { selectionRanges },
|
||||
}) => {
|
||||
const info = intersectInfo({ selectionRanges })
|
||||
const info = intersectInfo({
|
||||
selectionRanges: convertSelectionsToOld(selectionRanges),
|
||||
})
|
||||
if (trap(info)) return false
|
||||
return info.enabled
|
||||
},
|
||||
'Can constrain horizontally align': ({ context: { selectionRanges } }) => {
|
||||
const info = horzVertDistanceInfo({
|
||||
selectionRanges,
|
||||
selectionRanges: convertSelectionsToOld(selectionRanges),
|
||||
constraint: 'setHorzDistance',
|
||||
})
|
||||
if (trap(info)) return false
|
||||
@ -417,7 +442,7 @@ export const modelingMachine = setup({
|
||||
},
|
||||
'Can constrain vertically align': ({ context: { selectionRanges } }) => {
|
||||
const info = horzVertDistanceInfo({
|
||||
selectionRanges,
|
||||
selectionRanges: convertSelectionsToOld(selectionRanges),
|
||||
constraint: 'setHorzDistance',
|
||||
})
|
||||
if (trap(info)) return false
|
||||
@ -425,7 +450,7 @@ export const modelingMachine = setup({
|
||||
},
|
||||
'Can constrain snap to X': ({ context: { selectionRanges } }) => {
|
||||
const info = absDistanceInfo({
|
||||
selectionRanges,
|
||||
selectionRanges: convertSelectionsToOld(selectionRanges),
|
||||
constraint: 'snapToXAxis',
|
||||
})
|
||||
if (trap(info)) return false
|
||||
@ -433,19 +458,23 @@ export const modelingMachine = setup({
|
||||
},
|
||||
'Can constrain snap to Y': ({ context: { selectionRanges } }) => {
|
||||
const info = absDistanceInfo({
|
||||
selectionRanges,
|
||||
selectionRanges: convertSelectionsToOld(selectionRanges),
|
||||
constraint: 'snapToYAxis',
|
||||
})
|
||||
if (trap(info)) return false
|
||||
return info.enabled
|
||||
},
|
||||
'Can constrain equal length': ({ context: { selectionRanges } }) => {
|
||||
const info = setEqualLengthInfo({ selectionRanges })
|
||||
const info = setEqualLengthInfo({
|
||||
selectionRanges: convertSelectionsToOld(selectionRanges),
|
||||
})
|
||||
if (trap(info)) return false
|
||||
return info.enabled
|
||||
},
|
||||
'Can canstrain parallel': ({ context: { selectionRanges } }) => {
|
||||
const info = equalAngleInfo({ selectionRanges })
|
||||
const info = equalAngleInfo({
|
||||
selectionRanges: convertSelectionsToOld(selectionRanges),
|
||||
})
|
||||
if (err(info)) return false
|
||||
return info.enabled
|
||||
},
|
||||
@ -455,7 +484,7 @@ export const modelingMachine = setup({
|
||||
}) => {
|
||||
if (event.type !== 'Constrain remove constraints') return false
|
||||
const info = removeConstrainingValuesInfo({
|
||||
selectionRanges,
|
||||
selectionRanges: convertSelectionsToOld(selectionRanges),
|
||||
pathToNodes: event.data && [event.data],
|
||||
})
|
||||
if (trap(info)) return false
|
||||
@ -636,10 +665,14 @@ export const modelingMachine = setup({
|
||||
'AST delete selection': ({ context: { selectionRanges } }) => {
|
||||
;(async () => {
|
||||
let ast = kclManager.ast
|
||||
const oldSelection = convertSelectionToOld(
|
||||
selectionRanges.graphSelections[0]
|
||||
)
|
||||
if (!oldSelection) return
|
||||
|
||||
const modifiedAst = await deleteFromSelection(
|
||||
ast,
|
||||
selectionRanges.codeBasedSelections[0],
|
||||
oldSelection,
|
||||
kclManager.programMemory,
|
||||
getFaceDetails
|
||||
)
|
||||
@ -709,7 +742,7 @@ export const modelingMachine = setup({
|
||||
up: sketchDetails.yAxis,
|
||||
position: sketchDetails.origin,
|
||||
maybeModdedAst: kclManager.ast,
|
||||
selectionRanges,
|
||||
selectionRanges: convertSelectionsToOld(selectionRanges),
|
||||
})
|
||||
sceneInfra.resetMouseListeners()
|
||||
sceneEntitiesManager.setupSketchIdleCallbacks({
|
||||
@ -939,7 +972,7 @@ export const modelingMachine = setup({
|
||||
> & { data?: PathToNode }
|
||||
}) => {
|
||||
const constraint = applyRemoveConstrainingValues({
|
||||
selectionRanges,
|
||||
selectionRanges: convertSelectionsToOld(selectionRanges),
|
||||
pathToNodes: data && [data],
|
||||
})
|
||||
if (trap(constraint)) return
|
||||
@ -958,7 +991,7 @@ export const modelingMachine = setup({
|
||||
selectionType: 'completeSelection',
|
||||
selection: updateSelections(
|
||||
pathToNodeMap,
|
||||
selectionRanges,
|
||||
convertSelectionsToOld(selectionRanges),
|
||||
updatedAst.newAst
|
||||
),
|
||||
}
|
||||
@ -971,7 +1004,7 @@ export const modelingMachine = setup({
|
||||
input: Pick<ModelingMachineContext, 'selectionRanges' | 'sketchDetails'>
|
||||
}) => {
|
||||
const constraint = applyConstraintHorzVert(
|
||||
selectionRanges,
|
||||
convertSelectionsToOld(selectionRanges),
|
||||
'horizontal',
|
||||
kclManager.ast,
|
||||
kclManager.programMemory
|
||||
@ -992,7 +1025,7 @@ export const modelingMachine = setup({
|
||||
selectionType: 'completeSelection',
|
||||
selection: updateSelections(
|
||||
pathToNodeMap,
|
||||
selectionRanges,
|
||||
convertSelectionsToOld(selectionRanges),
|
||||
updatedAst.newAst
|
||||
),
|
||||
}
|
||||
@ -1005,7 +1038,7 @@ export const modelingMachine = setup({
|
||||
input: Pick<ModelingMachineContext, 'selectionRanges' | 'sketchDetails'>
|
||||
}) => {
|
||||
const constraint = applyConstraintHorzVert(
|
||||
selectionRanges,
|
||||
convertSelectionsToOld(selectionRanges),
|
||||
'vertical',
|
||||
kclManager.ast,
|
||||
kclManager.programMemory
|
||||
@ -1026,7 +1059,7 @@ export const modelingMachine = setup({
|
||||
selectionType: 'completeSelection',
|
||||
selection: updateSelections(
|
||||
pathToNodeMap,
|
||||
selectionRanges,
|
||||
convertSelectionsToOld(selectionRanges),
|
||||
updatedAst.newAst
|
||||
),
|
||||
}
|
||||
@ -1039,7 +1072,7 @@ export const modelingMachine = setup({
|
||||
input: Pick<ModelingMachineContext, 'selectionRanges' | 'sketchDetails'>
|
||||
}) => {
|
||||
const constraint = applyConstraintHorzVertAlign({
|
||||
selectionRanges,
|
||||
selectionRanges: convertSelectionsToOld(selectionRanges),
|
||||
constraint: 'setVertDistance',
|
||||
})
|
||||
if (trap(constraint)) return
|
||||
@ -1056,7 +1089,7 @@ export const modelingMachine = setup({
|
||||
if (!updatedAst) return
|
||||
const updatedSelectionRanges = updateSelections(
|
||||
pathToNodeMap,
|
||||
selectionRanges,
|
||||
convertSelectionsToOld(selectionRanges),
|
||||
updatedAst.newAst
|
||||
)
|
||||
return {
|
||||
@ -1072,7 +1105,7 @@ export const modelingMachine = setup({
|
||||
input: Pick<ModelingMachineContext, 'selectionRanges' | 'sketchDetails'>
|
||||
}) => {
|
||||
const constraint = applyConstraintHorzVertAlign({
|
||||
selectionRanges,
|
||||
selectionRanges: convertSelectionsToOld(selectionRanges),
|
||||
constraint: 'setHorzDistance',
|
||||
})
|
||||
if (trap(constraint)) return
|
||||
@ -1089,7 +1122,7 @@ export const modelingMachine = setup({
|
||||
if (!updatedAst) return
|
||||
const updatedSelectionRanges = updateSelections(
|
||||
pathToNodeMap,
|
||||
selectionRanges,
|
||||
convertSelectionsToOld(selectionRanges),
|
||||
updatedAst.newAst
|
||||
)
|
||||
return {
|
||||
@ -1105,7 +1138,7 @@ export const modelingMachine = setup({
|
||||
input: Pick<ModelingMachineContext, 'selectionRanges' | 'sketchDetails'>
|
||||
}) => {
|
||||
const constraint = applyConstraintAxisAlign({
|
||||
selectionRanges,
|
||||
selectionRanges: convertSelectionsToOld(selectionRanges),
|
||||
constraint: 'snapToXAxis',
|
||||
})
|
||||
if (err(constraint)) return false
|
||||
@ -1122,7 +1155,7 @@ export const modelingMachine = setup({
|
||||
if (!updatedAst) return
|
||||
const updatedSelectionRanges = updateSelections(
|
||||
pathToNodeMap,
|
||||
selectionRanges,
|
||||
convertSelectionsToOld(selectionRanges),
|
||||
updatedAst.newAst
|
||||
)
|
||||
return {
|
||||
@ -1138,7 +1171,7 @@ export const modelingMachine = setup({
|
||||
input: Pick<ModelingMachineContext, 'selectionRanges' | 'sketchDetails'>
|
||||
}) => {
|
||||
const constraint = applyConstraintAxisAlign({
|
||||
selectionRanges,
|
||||
selectionRanges: convertSelectionsToOld(selectionRanges),
|
||||
constraint: 'snapToYAxis',
|
||||
})
|
||||
if (trap(constraint)) return false
|
||||
@ -1155,7 +1188,7 @@ export const modelingMachine = setup({
|
||||
if (!updatedAst) return
|
||||
const updatedSelectionRanges = updateSelections(
|
||||
pathToNodeMap,
|
||||
selectionRanges,
|
||||
convertSelectionsToOld(selectionRanges),
|
||||
updatedAst.newAst
|
||||
)
|
||||
return {
|
||||
@ -1171,7 +1204,7 @@ export const modelingMachine = setup({
|
||||
input: Pick<ModelingMachineContext, 'selectionRanges' | 'sketchDetails'>
|
||||
}) => {
|
||||
const constraint = applyConstraintEqualAngle({
|
||||
selectionRanges,
|
||||
selectionRanges: convertSelectionsToOld(selectionRanges),
|
||||
})
|
||||
if (trap(constraint)) return false
|
||||
const { modifiedAst, pathToNodeMap } = constraint
|
||||
@ -1192,7 +1225,7 @@ export const modelingMachine = setup({
|
||||
if (!updatedAst) return
|
||||
const updatedSelectionRanges = updateSelections(
|
||||
pathToNodeMap,
|
||||
selectionRanges,
|
||||
convertSelectionsToOld(selectionRanges),
|
||||
updatedAst.newAst
|
||||
)
|
||||
return {
|
||||
@ -1208,7 +1241,7 @@ export const modelingMachine = setup({
|
||||
input: Pick<ModelingMachineContext, 'selectionRanges' | 'sketchDetails'>
|
||||
}) => {
|
||||
const constraint = applyConstraintEqualLength({
|
||||
selectionRanges,
|
||||
selectionRanges: convertSelectionsToOld(selectionRanges),
|
||||
})
|
||||
if (trap(constraint)) return false
|
||||
const { modifiedAst, pathToNodeMap } = constraint
|
||||
@ -1224,7 +1257,7 @@ export const modelingMachine = setup({
|
||||
if (!updatedAst) return
|
||||
const updatedSelectionRanges = updateSelections(
|
||||
pathToNodeMap,
|
||||
selectionRanges,
|
||||
convertSelectionsToOld(selectionRanges),
|
||||
updatedAst.newAst
|
||||
)
|
||||
return {
|
||||
|
17
src/main.ts
17
src/main.ts
@ -251,18 +251,14 @@ export function getAutoUpdater(): AppUpdater {
|
||||
return autoUpdater
|
||||
}
|
||||
|
||||
export async function checkForUpdates(autoUpdater: AppUpdater) {
|
||||
// TODO: figure out how to get the update modal back
|
||||
const result = await autoUpdater.checkForUpdatesAndNotify()
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
app.on('ready', () => {
|
||||
const autoUpdater = getAutoUpdater()
|
||||
checkForUpdates(autoUpdater).catch(reportRejection)
|
||||
setTimeout(() => {
|
||||
autoUpdater.checkForUpdates().catch(reportRejection)
|
||||
}, 1000)
|
||||
const fifteenMinutes = 15 * 60 * 1000
|
||||
setInterval(() => {
|
||||
checkForUpdates(autoUpdater).catch(reportRejection)
|
||||
autoUpdater.checkForUpdates().catch(reportRejection)
|
||||
}, fifteenMinutes)
|
||||
|
||||
autoUpdater.on('update-available', (info) => {
|
||||
@ -271,6 +267,11 @@ app.on('ready', () => {
|
||||
|
||||
autoUpdater.on('update-downloaded', (info) => {
|
||||
console.log('update-downloaded', info)
|
||||
mainWindow?.webContents.send('update-downloaded', info.version)
|
||||
})
|
||||
|
||||
ipcMain.handle('app.restart', () => {
|
||||
autoUpdater.quitAndInstall()
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -15,6 +15,9 @@ const startDeviceFlow = (host: string): Promise<string> =>
|
||||
ipcRenderer.invoke('startDeviceFlow', host)
|
||||
const loginWithDeviceFlow = (): Promise<string> =>
|
||||
ipcRenderer.invoke('loginWithDeviceFlow')
|
||||
const onUpdateDownloaded = (callback: (value: string) => void) =>
|
||||
ipcRenderer.on('update-downloaded', (_event, value) => callback(value))
|
||||
const appRestart = () => ipcRenderer.invoke('app.restart')
|
||||
|
||||
const isMac = os.platform() === 'darwin'
|
||||
const isWindows = os.platform() === 'win32'
|
||||
@ -123,4 +126,6 @@ contextBridge.exposeInMainWorld('electron', {
|
||||
kittycad,
|
||||
listMachines,
|
||||
getMachineApiIp,
|
||||
onUpdateDownloaded,
|
||||
appRestart,
|
||||
})
|
||||
|
22
src/wasm-lib/Cargo.lock
generated
22
src/wasm-lib/Cargo.lock
generated
@ -388,9 +388,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.17"
|
||||
version = "4.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac"
|
||||
checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@ -398,9 +398,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.17"
|
||||
version = "4.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73"
|
||||
checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
@ -408,9 +408,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.13"
|
||||
version = "4.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
|
||||
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
@ -1400,7 +1400,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-lib"
|
||||
version = "0.2.17"
|
||||
version = "0.2.18"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"approx 0.5.1",
|
||||
@ -3079,18 +3079,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.63"
|
||||
version = "1.0.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
|
||||
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.63"
|
||||
version = "1.0.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
|
||||
checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -16,7 +16,7 @@ async-recursion = "1.1.1"
|
||||
async-trait = "0.1.82"
|
||||
base64 = "0.22.1"
|
||||
chrono = "0.4.38"
|
||||
clap = { version = "4.5.17", default-features = false, optional = true, features = ["std", "derive"] }
|
||||
clap = { version = "4.5.18", default-features = false, optional = true, features = ["std", "derive"] }
|
||||
convert_case = "0.6.0"
|
||||
dashmap = "6.1.0"
|
||||
databake = { version = "0.1.8", features = ["derive"] }
|
||||
@ -42,7 +42,7 @@ serde = { version = "1.0.210", features = ["derive"] }
|
||||
serde_json = "1.0.128"
|
||||
sha2 = "0.10.8"
|
||||
tabled = { version = "0.15.0", optional = true }
|
||||
thiserror = "1.0.63"
|
||||
thiserror = "1.0.64"
|
||||
toml = "0.8.19"
|
||||
ts-rs = { version = "10.0.0", features = ["uuid-impl", "url-impl", "chrono-impl", "no-serde-warnings", "serde-json-impl"] }
|
||||
url = { version = "2.5.2", features = ["serde"] }
|
||||
|
@ -520,7 +520,7 @@ pub fn get_autocomplete_snippet_from_schema(
|
||||
let mut fn_docs = String::new();
|
||||
fn_docs.push_str("{\n");
|
||||
// Let's print out the object's properties.
|
||||
let mut i = 0;
|
||||
let mut i = index;
|
||||
for (prop_name, prop) in obj_val.properties.iter() {
|
||||
if prop_name.starts_with('_') {
|
||||
continue;
|
||||
@ -532,15 +532,15 @@ pub fn get_autocomplete_snippet_from_schema(
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some((_, snippet)) = get_autocomplete_snippet_from_schema(prop, index + i)? {
|
||||
if let Some((new_index, snippet)) = get_autocomplete_snippet_from_schema(prop, i)? {
|
||||
fn_docs.push_str(&format!("\t{}: {},\n", prop_name, snippet));
|
||||
i += 1;
|
||||
i = new_index + 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn_docs.push('}');
|
||||
|
||||
return Ok(Some((index + i - 1, fn_docs)));
|
||||
return Ok(Some((i - 1, fn_docs)));
|
||||
}
|
||||
|
||||
if let Some(array_val) = &o.array {
|
||||
@ -589,6 +589,7 @@ pub fn get_autocomplete_snippet_from_schema(
|
||||
|
||||
if let Some(subschemas) = &o.subschemas {
|
||||
let mut fn_docs = String::new();
|
||||
let mut i = index;
|
||||
if let Some(items) = &subschemas.one_of {
|
||||
let mut had_enum_string = false;
|
||||
let mut parsed_enum_values: Vec<String> = Vec::new();
|
||||
@ -620,13 +621,15 @@ pub fn get_autocomplete_snippet_from_schema(
|
||||
if had_enum_string && !parsed_enum_values.is_empty() {
|
||||
return Ok(Some((index, parsed_enum_values[0].to_string())));
|
||||
} else if let Some(item) = items.iter().next() {
|
||||
if let Some((_, snippet)) = get_autocomplete_snippet_from_schema(item, index)? {
|
||||
if let Some((new_index, snippet)) = get_autocomplete_snippet_from_schema(item, index)? {
|
||||
i = new_index + 1;
|
||||
fn_docs.push_str(&snippet);
|
||||
}
|
||||
}
|
||||
} else if let Some(items) = &subschemas.any_of {
|
||||
if let Some(item) = items.iter().next() {
|
||||
if let Some((_, snippet)) = get_autocomplete_snippet_from_schema(item, index)? {
|
||||
if let Some((new_index, snippet)) = get_autocomplete_snippet_from_schema(item, index)? {
|
||||
i = new_index + 1;
|
||||
fn_docs.push_str(&snippet);
|
||||
}
|
||||
}
|
||||
@ -634,7 +637,7 @@ pub fn get_autocomplete_snippet_from_schema(
|
||||
anyhow::bail!("unknown subschemas: {:#?}", subschemas);
|
||||
}
|
||||
|
||||
return Ok(Some((index, fn_docs)));
|
||||
return Ok(Some((i - 1, fn_docs)));
|
||||
}
|
||||
|
||||
if let Some(schemars::schema::SingleOrVec::Single(single)) = &o.instance_type {
|
||||
@ -915,10 +918,10 @@ mod tests {
|
||||
r#"patternCircular3d({
|
||||
arcDegrees: ${0:3.14},
|
||||
axis: [${1:3.14}, ${2:3.14}, ${3:3.14}],
|
||||
center: [${2:3.14}, ${3:3.14}, ${4:3.14}],
|
||||
repetitions: ${3:10},
|
||||
rotateDuplicates: ${4:false},
|
||||
}, ${5:%})${}"#
|
||||
center: [${4:3.14}, ${5:3.14}, ${6:3.14}],
|
||||
repetitions: ${7:10},
|
||||
rotateDuplicates: ${8:false},
|
||||
}, ${9:%})${}"#
|
||||
);
|
||||
}
|
||||
|
||||
@ -942,8 +945,36 @@ mod tests {
|
||||
snippet,
|
||||
r#"circle({
|
||||
center: [${0:3.14}, ${1:3.14}],
|
||||
radius: ${1:3.14},
|
||||
}, ${2:%})${}"#
|
||||
radius: ${2:3.14},
|
||||
}, ${3:%})${}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_autocomplete_snippet_arc() {
|
||||
let arc_fn: Box<dyn StdLibFn> = Box::new(crate::std::sketch::Arc);
|
||||
let snippet = arc_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(
|
||||
snippet,
|
||||
r#"arc({
|
||||
angleEnd: ${0:3.14},
|
||||
angleStart: ${1:3.14},
|
||||
radius: ${2:3.14},
|
||||
}, ${3:%})${}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_autocomplete_snippet_pattern_linear_2d() {
|
||||
let pattern_fn: Box<dyn StdLibFn> = Box::new(crate::std::patterns::PatternLinear2D);
|
||||
let snippet = pattern_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(
|
||||
snippet,
|
||||
r#"patternLinear2d({
|
||||
axis: [${0:3.14}, ${1:3.14}],
|
||||
distance: ${2:3.14},
|
||||
repetitions: ${3:10},
|
||||
}, ${4:%})${}"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 134 KiB |
Binary file not shown.
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 134 KiB |
Reference in New Issue
Block a user