Merge remote-tracking branch 'origin' into kurt-bring-back-multi-profile
This commit is contained in:
Binary file not shown.
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 144 KiB |
@ -1,6 +1,17 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test, expect } from '@playwright/test'
|
||||||
|
import {
|
||||||
import { getUtils, setup, tearDown, TEST_COLORS } from './test-utils'
|
test as testFixture,
|
||||||
|
expect as expectFixture,
|
||||||
|
} from './fixtures/fixtureSetup'
|
||||||
|
import { join } from 'path'
|
||||||
|
import {
|
||||||
|
getUtils,
|
||||||
|
setup,
|
||||||
|
tearDown,
|
||||||
|
TEST_COLORS,
|
||||||
|
executorInputPath,
|
||||||
|
} from './test-utils'
|
||||||
|
import * as fsp from 'fs/promises'
|
||||||
import { XOR } from 'lib/utils'
|
import { XOR } from 'lib/utils'
|
||||||
|
|
||||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||||
@ -1028,3 +1039,58 @@ part002 = startSketchOn('XZ')
|
|||||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(2)
|
await expect(page.getByTestId('segment-overlay')).toHaveCount(2)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
testFixture.describe('Electron constraint tests', () => {
|
||||||
|
testFixture(
|
||||||
|
'Able to double click label to set constraint',
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ tronApp, homePage, scene, editor, toolbar }) => {
|
||||||
|
await tronApp.initialise({
|
||||||
|
fixtures: { homePage, scene, editor, toolbar },
|
||||||
|
folderSetupFn: async (dir) => {
|
||||||
|
const bracketDir = join(dir, 'test-sample')
|
||||||
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
|
await fsp.copyFile(
|
||||||
|
executorInputPath('angled_line.kcl'),
|
||||||
|
join(bracketDir, 'main.kcl')
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const [clickHandler] = scene.makeMouseHelpers(600, 300)
|
||||||
|
|
||||||
|
await test.step('setup test', async () => {
|
||||||
|
await homePage.expectState({
|
||||||
|
projectCards: [
|
||||||
|
{
|
||||||
|
title: 'test-sample',
|
||||||
|
fileCount: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sortBy: 'last-modified-desc',
|
||||||
|
})
|
||||||
|
await homePage.openProject('test-sample')
|
||||||
|
await scene.waitForExecutionDone()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Double click to constrain', async () => {
|
||||||
|
await clickHandler()
|
||||||
|
await tronApp.page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
const child = tronApp.page
|
||||||
|
.locator('.segment-length-label-text')
|
||||||
|
.first()
|
||||||
|
.locator('xpath=..')
|
||||||
|
await child.dblclick()
|
||||||
|
const cmdBarSubmitButton = tronApp.page.getByRole('button', {
|
||||||
|
name: 'arrow right Continue',
|
||||||
|
})
|
||||||
|
await cmdBarSubmitButton.click()
|
||||||
|
await expectFixture(tronApp.page.locator('.cm-content')).toContainText(
|
||||||
|
'length001 = 15.3'
|
||||||
|
)
|
||||||
|
await expectFixture(tronApp.page.locator('.cm-content')).toContainText(
|
||||||
|
'|> angledLine([9, length001], %)'
|
||||||
|
)
|
||||||
|
await tronApp.page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
@ -174,8 +174,13 @@ export const ClientSideScene = ({
|
|||||||
const Overlays = () => {
|
const Overlays = () => {
|
||||||
const { context } = useModelingContext()
|
const { context } = useModelingContext()
|
||||||
if (context.mouseState.type === 'isDragging') return null
|
if (context.mouseState.type === 'isDragging') return null
|
||||||
|
// Set a large zIndex, the overlay for hover dropdown menu on line segments needs to render
|
||||||
|
// over the length labels on the line segments
|
||||||
return (
|
return (
|
||||||
<div className="absolute inset-0 pointer-events-none">
|
<div
|
||||||
|
className="absolute inset-0 pointer-events-none"
|
||||||
|
style={{ zIndex: '99999999' }}
|
||||||
|
>
|
||||||
{Object.entries(context.segmentOverlays)
|
{Object.entries(context.segmentOverlays)
|
||||||
.filter((a) => a[1].visible)
|
.filter((a) => a[1].visible)
|
||||||
.map(([pathToNodeString, overlay], index) => {
|
.map(([pathToNodeString, overlay], index) => {
|
||||||
|
@ -49,6 +49,7 @@ import {
|
|||||||
defaultSourceRange,
|
defaultSourceRange,
|
||||||
sourceRangeFromRust,
|
sourceRangeFromRust,
|
||||||
resultIsOk,
|
resultIsOk,
|
||||||
|
SourceRange,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import {
|
import {
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
@ -110,6 +111,7 @@ import { SegmentInputs } from 'lang/std/stdTypes'
|
|||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
import { radToDeg } from 'three/src/math/MathUtils'
|
import { radToDeg } from 'three/src/math/MathUtils'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
|
import { getArtifactFromRange, codeRefFromRange } from 'lang/std/artifactGraph'
|
||||||
|
|
||||||
type DraftSegment = 'line' | 'tangentialArcTo'
|
type DraftSegment = 'line' | 'tangentialArcTo'
|
||||||
|
|
||||||
@ -648,7 +650,7 @@ export class SceneEntities {
|
|||||||
)
|
)
|
||||||
|
|
||||||
let seg: Group
|
let seg: Group
|
||||||
const _node1 = getNodeFromPath<CallExpression>(
|
const _node1 = getNodeFromPath<Node<CallExpression>>(
|
||||||
maybeModdedAst,
|
maybeModdedAst,
|
||||||
segPathToNode,
|
segPathToNode,
|
||||||
'CallExpression'
|
'CallExpression'
|
||||||
@ -675,6 +677,13 @@ export class SceneEntities {
|
|||||||
from: segment.from,
|
from: segment.from,
|
||||||
to: segment.to,
|
to: segment.to,
|
||||||
}
|
}
|
||||||
|
const startRange = _node1.node.start
|
||||||
|
const endRange = _node1.node.end
|
||||||
|
const sourceRange: SourceRange = [startRange, endRange, true]
|
||||||
|
const selection: Selections = computeSelectionFromSourceRangeAndAST(
|
||||||
|
sourceRange,
|
||||||
|
maybeModdedAst
|
||||||
|
)
|
||||||
const result = initSegment({
|
const result = initSegment({
|
||||||
prevSegment: sketch.paths[index - 1],
|
prevSegment: sketch.paths[index - 1],
|
||||||
callExpName,
|
callExpName,
|
||||||
@ -687,6 +696,7 @@ export class SceneEntities {
|
|||||||
theme: sceneInfra._theme,
|
theme: sceneInfra._theme,
|
||||||
isSelected,
|
isSelected,
|
||||||
sceneInfra,
|
sceneInfra,
|
||||||
|
selection,
|
||||||
})
|
})
|
||||||
if (err(result)) return
|
if (err(result)) return
|
||||||
const { group: _group, updateOverlaysCallback } = result
|
const { group: _group, updateOverlaysCallback } = result
|
||||||
@ -2541,3 +2551,26 @@ function getSketchesInfo({
|
|||||||
}
|
}
|
||||||
return sketchesInfo
|
return sketchesInfo
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Given a SourceRange [x,y,boolean] create a Selections object which contains
|
||||||
|
* graphSelections with the artifact and codeRef.
|
||||||
|
* This can be passed to 'Set selection' to internally set the selection of the
|
||||||
|
* modelingMachine from code.
|
||||||
|
*/
|
||||||
|
function computeSelectionFromSourceRangeAndAST(
|
||||||
|
sourceRange: SourceRange,
|
||||||
|
ast: Node<Program>
|
||||||
|
): Selections {
|
||||||
|
const artifactGraph = engineCommandManager.artifactGraph
|
||||||
|
const artifact = getArtifactFromRange(sourceRange, artifactGraph) || undefined
|
||||||
|
const selection: Selections = {
|
||||||
|
graphSelections: [
|
||||||
|
{
|
||||||
|
artifact,
|
||||||
|
codeRef: codeRefFromRange(sourceRange, ast),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
otherSelections: [],
|
||||||
|
}
|
||||||
|
return selection
|
||||||
|
}
|
||||||
|
@ -229,7 +229,6 @@ export class SceneInfra {
|
|||||||
const vector = new Vector3(0, 0, 0)
|
const vector = new Vector3(0, 0, 0)
|
||||||
|
|
||||||
// Get the position of the object3D in world space
|
// Get the position of the object3D in world space
|
||||||
// console.log('arrowGroup', arrowGroup)
|
|
||||||
arrowGroup.getWorldPosition(vector)
|
arrowGroup.getWorldPosition(vector)
|
||||||
|
|
||||||
// Project that position to screen space
|
// Project that position to screen space
|
||||||
@ -347,7 +346,6 @@ export class SceneInfra {
|
|||||||
requestAnimationFrame(this.animate)
|
requestAnimationFrame(this.animate)
|
||||||
TWEEN.update() // This will update all tweens during the animation loop
|
TWEEN.update() // This will update all tweens during the animation loop
|
||||||
if (!this.isFovAnimationInProgress) {
|
if (!this.isFovAnimationInProgress) {
|
||||||
// console.log('animation frame', this.cameraControls.camera)
|
|
||||||
this.camControls.update()
|
this.camControls.update()
|
||||||
this.renderer.render(this.scene, this.camControls.camera)
|
this.renderer.render(this.scene, this.camControls.camera)
|
||||||
this.labelRenderer.render(this.scene, this.camControls.camera)
|
this.labelRenderer.render(this.scene, this.camControls.camera)
|
||||||
@ -434,7 +432,6 @@ export class SceneInfra {
|
|||||||
if (!this.selected.hasBeenDragged && hasBeenDragged) {
|
if (!this.selected.hasBeenDragged && hasBeenDragged) {
|
||||||
this.selected.hasBeenDragged = true
|
this.selected.hasBeenDragged = true
|
||||||
// this is where we could fire a onDragStart event
|
// this is where we could fire a onDragStart event
|
||||||
// console.log('onDragStart', this.selected)
|
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
hasBeenDragged &&
|
hasBeenDragged &&
|
||||||
|
@ -56,6 +56,8 @@ import { normaliseAngle, roundOff } from 'lib/utils'
|
|||||||
import { SegmentOverlayPayload } from 'machines/modelingMachine'
|
import { SegmentOverlayPayload } from 'machines/modelingMachine'
|
||||||
import { SegmentInputs } from 'lang/std/stdTypes'
|
import { SegmentInputs } from 'lang/std/stdTypes'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
|
import { editorManager, sceneInfra } from 'lib/singletons'
|
||||||
|
import { Selections } from 'lib/selections'
|
||||||
|
|
||||||
interface CreateSegmentArgs {
|
interface CreateSegmentArgs {
|
||||||
input: SegmentInputs
|
input: SegmentInputs
|
||||||
@ -69,6 +71,7 @@ interface CreateSegmentArgs {
|
|||||||
theme: Themes
|
theme: Themes
|
||||||
isSelected?: boolean
|
isSelected?: boolean
|
||||||
sceneInfra: SceneInfra
|
sceneInfra: SceneInfra
|
||||||
|
selection?: Selections
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateSegmentArgs {
|
interface UpdateSegmentArgs {
|
||||||
@ -118,6 +121,7 @@ class StraightSegment implements SegmentUtils {
|
|||||||
isSelected = false,
|
isSelected = false,
|
||||||
sceneInfra,
|
sceneInfra,
|
||||||
prevSegment,
|
prevSegment,
|
||||||
|
selection,
|
||||||
}) => {
|
}) => {
|
||||||
if (input.type !== 'straight-segment')
|
if (input.type !== 'straight-segment')
|
||||||
return new Error('Invalid segment type')
|
return new Error('Invalid segment type')
|
||||||
@ -156,6 +160,7 @@ class StraightSegment implements SegmentUtils {
|
|||||||
isSelected,
|
isSelected,
|
||||||
callExpName,
|
callExpName,
|
||||||
baseColor,
|
baseColor,
|
||||||
|
selection,
|
||||||
}
|
}
|
||||||
|
|
||||||
// All segment types get an extra segment handle,
|
// All segment types get an extra segment handle,
|
||||||
@ -825,8 +830,37 @@ function createLengthIndicator({
|
|||||||
lengthIndicatorText.innerText = roundOff(length).toString()
|
lengthIndicatorText.innerText = roundOff(length).toString()
|
||||||
const lengthIndicatorWrapper = document.createElement('div')
|
const lengthIndicatorWrapper = document.createElement('div')
|
||||||
|
|
||||||
|
// Double click workflow
|
||||||
|
lengthIndicatorWrapper.ondblclick = () => {
|
||||||
|
const selection = lengthIndicatorGroup.parent?.userData.selection
|
||||||
|
if (!selection) {
|
||||||
|
console.error('Unable to dimension segment when clicking the label.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sceneInfra.modelingSend({
|
||||||
|
type: 'Set selection',
|
||||||
|
data: {
|
||||||
|
selectionType: 'singleCodeCursor',
|
||||||
|
selection: selection.graphSelections[0],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Command Bar
|
||||||
|
editorManager.commandBarSend({
|
||||||
|
type: 'Find and select command',
|
||||||
|
data: {
|
||||||
|
name: 'Constrain length',
|
||||||
|
groupId: 'modeling',
|
||||||
|
argDefaultValues: {
|
||||||
|
selection,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Style the elements
|
// Style the elements
|
||||||
lengthIndicatorWrapper.style.position = 'absolute'
|
lengthIndicatorWrapper.style.position = 'absolute'
|
||||||
|
lengthIndicatorWrapper.style.pointerEvents = 'auto'
|
||||||
lengthIndicatorWrapper.appendChild(lengthIndicatorText)
|
lengthIndicatorWrapper.appendChild(lengthIndicatorText)
|
||||||
const cssObject = new CSS2DObject(lengthIndicatorWrapper)
|
const cssObject = new CSS2DObject(lengthIndicatorWrapper)
|
||||||
cssObject.name = SEGMENT_LENGTH_LABEL_TEXT
|
cssObject.name = SEGMENT_LENGTH_LABEL_TEXT
|
||||||
|
@ -109,6 +109,7 @@ function DisplayObj({
|
|||||||
setHasCursor(false)
|
setHasCursor(false)
|
||||||
}
|
}
|
||||||
}, [node.start, node.end, node.type])
|
}, [node.start, node.end, node.type])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<pre
|
<pre
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
@ -1209,3 +1209,20 @@ function getWallOrCapPlaneCodeRef(
|
|||||||
range: planeCodeRef[0].range,
|
range: planeCodeRef[0].range,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Get an artifact from a code source range
|
||||||
|
*/
|
||||||
|
export function getArtifactFromRange(
|
||||||
|
range: SourceRange,
|
||||||
|
artifactGraph: ArtifactGraph
|
||||||
|
): Artifact | null {
|
||||||
|
for (const artifact of artifactGraph.values()) {
|
||||||
|
if ('codeRef' in artifact && artifact.codeRef) {
|
||||||
|
const match =
|
||||||
|
artifact?.codeRef.range[0] === range[0] &&
|
||||||
|
artifact?.codeRef.range[1] === range[1]
|
||||||
|
if (match) return artifact
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user