Compare commits
6 Commits
jtran/upda
...
franknoiro
Author | SHA1 | Date | |
---|---|---|---|
b95272cf03 | |||
fa973e7b89 | |||
ee77af74c1 | |||
0e85da7e36 | |||
8831046bdd | |||
01975a5447 |
@ -98,6 +98,7 @@ import {
|
|||||||
import { getThemeColorForThreeJs } from 'lib/theme'
|
import { getThemeColorForThreeJs } from 'lib/theme'
|
||||||
import { err, trap } from 'lib/trap'
|
import { err, trap } from 'lib/trap'
|
||||||
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
|
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
|
||||||
|
import { addCircleToSketchAst } from 'lib/circleTool'
|
||||||
|
|
||||||
type DraftSegment = 'line' | 'tangentialArcTo'
|
type DraftSegment = 'line' | 'tangentialArcTo'
|
||||||
|
|
||||||
@ -714,11 +715,8 @@ export class SceneEntities {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
setupDraftRectangle = async (
|
setupDraftRectangle = async (
|
||||||
sketchPathToNode: PathToNode,
|
rectangleOrigin: [number, number],
|
||||||
forward: [number, number, number],
|
{ sketchPathToNode, origin, zAxis, yAxis }: SketchDetails
|
||||||
up: [number, number, number],
|
|
||||||
sketchOrigin: [number, number, number],
|
|
||||||
rectangleOrigin: [x: number, y: number]
|
|
||||||
) => {
|
) => {
|
||||||
let _ast = structuredClone(kclManager.ast)
|
let _ast = structuredClone(kclManager.ast)
|
||||||
|
|
||||||
@ -750,9 +748,9 @@ export class SceneEntities {
|
|||||||
|
|
||||||
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
|
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
|
||||||
sketchPathToNode,
|
sketchPathToNode,
|
||||||
forward,
|
forward: yAxis,
|
||||||
up,
|
up: zAxis,
|
||||||
position: sketchOrigin,
|
position: origin,
|
||||||
maybeModdedAst: _ast,
|
maybeModdedAst: _ast,
|
||||||
draftExpressionsIndices: { start: 0, end: 3 },
|
draftExpressionsIndices: { start: 0, end: 3 },
|
||||||
})
|
})
|
||||||
@ -862,6 +860,52 @@ export class SceneEntities {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
setupCircleOriginListener = () => {
|
||||||
|
sceneInfra.setCallbacks({
|
||||||
|
onClick: (args) => {
|
||||||
|
const twoD = args.intersectionPoint?.twoD
|
||||||
|
if (!twoD) {
|
||||||
|
console.warn(`This click didn't have a 2D intersection`, args)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sceneInfra.modelingSend({
|
||||||
|
type: 'Add circle origin',
|
||||||
|
data: [twoD.x, twoD.y],
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setupDraftCircle = async (
|
||||||
|
circleOrigin: [number, number],
|
||||||
|
{ sketchPathToNode, origin, zAxis, yAxis }: SketchDetails
|
||||||
|
) => {
|
||||||
|
const astWithCircle = await addCircleToSketchAst({
|
||||||
|
sourceAst: kclManager.ast,
|
||||||
|
sketchPathToNode,
|
||||||
|
circleOrigin,
|
||||||
|
})
|
||||||
|
|
||||||
|
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
|
||||||
|
sketchPathToNode,
|
||||||
|
forward: yAxis,
|
||||||
|
up: zAxis,
|
||||||
|
position: origin,
|
||||||
|
maybeModdedAst: astWithCircle,
|
||||||
|
draftExpressionsIndices: { start: 0, end: 3 },
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: Move this into the onclick handler to get the real shit
|
||||||
|
// Update the primary AST and unequip the rectangle tool
|
||||||
|
await kclManager.executeAstMock(astWithCircle)
|
||||||
|
sceneInfra.modelingSend({ type: 'CancelSketch' })
|
||||||
|
|
||||||
|
const { programMemory } = await executeAst({
|
||||||
|
ast: astWithCircle,
|
||||||
|
useFakeExecutor: true,
|
||||||
|
engineCommandManager: this.engineCommandManager,
|
||||||
|
programMemoryOverride,
|
||||||
|
})
|
||||||
|
}
|
||||||
setupSketchIdleCallbacks = ({
|
setupSketchIdleCallbacks = ({
|
||||||
pathToNode,
|
pathToNode,
|
||||||
up,
|
up,
|
||||||
@ -1176,6 +1220,9 @@ export class SceneEntities {
|
|||||||
? orthoFactor
|
? orthoFactor
|
||||||
: perspScale(sceneInfra.camControls.camera, group)) /
|
: perspScale(sceneInfra.camControls.camera, group)) /
|
||||||
sceneInfra._baseUnitMultiplier
|
sceneInfra._baseUnitMultiplier
|
||||||
|
|
||||||
|
console.log('segment type', type)
|
||||||
|
|
||||||
if (type === TANGENTIAL_ARC_TO_SEGMENT) {
|
if (type === TANGENTIAL_ARC_TO_SEGMENT) {
|
||||||
return this.updateTangentialArcToSegment({
|
return this.updateTangentialArcToSegment({
|
||||||
prevSegment: sgPaths[index - 1],
|
prevSegment: sgPaths[index - 1],
|
||||||
|
92
src/lib/circleTool.ts
Normal file
92
src/lib/circleTool.ts
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import {
|
||||||
|
createArrayExpression,
|
||||||
|
createCallExpressionStdLib,
|
||||||
|
createLiteral,
|
||||||
|
createPipeExpression,
|
||||||
|
createPipeSubstitution,
|
||||||
|
createTagDeclarator,
|
||||||
|
findUniqueName,
|
||||||
|
} from 'lang/modifyAst'
|
||||||
|
import { roundOff } from './utils'
|
||||||
|
import {
|
||||||
|
PathToNode,
|
||||||
|
Program,
|
||||||
|
VariableDeclaration,
|
||||||
|
parse,
|
||||||
|
recast,
|
||||||
|
} from 'lang/wasm'
|
||||||
|
import { getNodeFromPath } from 'lang/queryAst'
|
||||||
|
import { trap } from './trap'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide away the working with the AST
|
||||||
|
*/
|
||||||
|
export async function addCircleToSketchAst({
|
||||||
|
sourceAst,
|
||||||
|
sketchPathToNode,
|
||||||
|
circleOrigin,
|
||||||
|
}: {
|
||||||
|
sourceAst: Program
|
||||||
|
sketchPathToNode?: PathToNode
|
||||||
|
circleOrigin?: [number, number]
|
||||||
|
}) {
|
||||||
|
let _ast = JSON.parse(JSON.stringify(sourceAst))
|
||||||
|
|
||||||
|
const _node1 = getNodeFromPath<VariableDeclaration>(
|
||||||
|
_ast,
|
||||||
|
sketchPathToNode || [],
|
||||||
|
'VariableDeclaration'
|
||||||
|
)
|
||||||
|
if (trap(_node1)) return Promise.reject(_node1)
|
||||||
|
|
||||||
|
const tag = findUniqueName(_ast, 'circle')
|
||||||
|
|
||||||
|
const _node2 = getNodeFromPath<VariableDeclaration>(
|
||||||
|
_ast,
|
||||||
|
sketchPathToNode || [],
|
||||||
|
'VariableDeclaration'
|
||||||
|
)
|
||||||
|
if (trap(_node2)) return Promise.reject(_node2)
|
||||||
|
const startSketchOn = _node2.node?.declarations
|
||||||
|
|
||||||
|
const startSketchOnInit = startSketchOn?.[0]?.init
|
||||||
|
startSketchOn[0].init = createPipeExpression([
|
||||||
|
startSketchOnInit,
|
||||||
|
...getCircleCallExpressions({
|
||||||
|
center: circleOrigin,
|
||||||
|
tag,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
|
||||||
|
const maybeModdedAst = parse(recast(_ast))
|
||||||
|
if (trap(maybeModdedAst)) return Promise.reject(maybeModdedAst)
|
||||||
|
return Promise.resolve(maybeModdedAst)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns AST expressions for this KCL code:
|
||||||
|
* const yo = startSketchOn('XY')
|
||||||
|
* |> startProfileAt([0, 0], %)
|
||||||
|
* |> circle([0, 0], 0, %) <- this line
|
||||||
|
*/
|
||||||
|
export function getCircleCallExpressions({
|
||||||
|
center = [0, 0],
|
||||||
|
radius = 10,
|
||||||
|
tag,
|
||||||
|
}: {
|
||||||
|
center?: [number, number]
|
||||||
|
radius?: number
|
||||||
|
tag: string
|
||||||
|
}) {
|
||||||
|
return [
|
||||||
|
createCallExpressionStdLib('circle', [
|
||||||
|
createArrayExpression([
|
||||||
|
createLiteral(roundOff(center[0])),
|
||||||
|
createLiteral(roundOff(center[1])),
|
||||||
|
]),
|
||||||
|
createLiteral(roundOff(radius)),
|
||||||
|
createPipeSubstitution(),
|
||||||
|
createTagDeclarator(tag),
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
}
|
@ -293,7 +293,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
status: 'available',
|
status: 'available',
|
||||||
disabled: (state) =>
|
disabled: (state) =>
|
||||||
state.matches('Sketch no face') ||
|
state.matches('Sketch no face') ||
|
||||||
state.matches('Sketch.Rectangle tool.Awaiting second corner'),
|
state.matches('Sketch.Rectangle tool.Awaiting second corner') ||
|
||||||
|
state.matches('Sketch.Circle tool.Awaiting perimeter click'),
|
||||||
title: 'Line',
|
title: 'Line',
|
||||||
hotkey: (state) =>
|
hotkey: (state) =>
|
||||||
state.matches('Sketch.Line tool') ? ['Esc', 'L'] : 'L',
|
state.matches('Sketch.Line tool') ? ['Esc', 'L'] : 'L',
|
||||||
@ -355,9 +356,20 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
id: 'circle-center',
|
id: 'circle-center',
|
||||||
onClick: () => console.error('Center circle not yet implemented'),
|
onClick: ({ modelingStateMatches, modelingSend }) =>
|
||||||
|
modelingSend({
|
||||||
|
type: 'change tool',
|
||||||
|
data: {
|
||||||
|
tool: !modelingStateMatches('Sketch.Circle tool')
|
||||||
|
? 'circle'
|
||||||
|
: 'none',
|
||||||
|
},
|
||||||
|
}),
|
||||||
icon: 'circle',
|
icon: 'circle',
|
||||||
status: 'unavailable',
|
status: 'available',
|
||||||
|
disabled: (state) =>
|
||||||
|
!canRectangleTool(state.context) &&
|
||||||
|
!state.matches('Sketch.Circle tool'),
|
||||||
title: 'Center circle',
|
title: 'Center circle',
|
||||||
showTitle: false,
|
showTitle: false,
|
||||||
description: 'Start drawing a circle from its center',
|
description: 'Start drawing a circle from its center',
|
||||||
@ -367,6 +379,9 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
url: 'https://github.com/KittyCAD/modeling-app/issues/1501',
|
url: 'https://github.com/KittyCAD/modeling-app/issues/1501',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
hotkey: (state) =>
|
||||||
|
state.matches('Sketch.Circle tool') ? ['Esc', 'C'] : 'C',
|
||||||
|
isActive: (state) => state.matches('Sketch.Circle tool'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'circle-three-points',
|
id: 'circle-three-points',
|
||||||
|
@ -141,7 +141,12 @@ interface Store {
|
|||||||
openPanes: SidebarType[]
|
openPanes: SidebarType[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SketchTool = 'line' | 'tangentialArc' | 'rectangle' | 'none'
|
export type SketchTool =
|
||||||
|
| 'line'
|
||||||
|
| 'tangentialArc'
|
||||||
|
| 'rectangle'
|
||||||
|
| 'circle'
|
||||||
|
| 'none'
|
||||||
|
|
||||||
export type ModelingMachineEvent =
|
export type ModelingMachineEvent =
|
||||||
| {
|
| {
|
||||||
@ -209,6 +214,10 @@ export type ModelingMachineEvent =
|
|||||||
type: 'Add rectangle origin'
|
type: 'Add rectangle origin'
|
||||||
data: [x: number, y: number]
|
data: [x: number, y: number]
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: 'Add circle origin'
|
||||||
|
data: [x: number, y: number]
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
type: 'done.invoke.animate-to-face' | 'done.invoke.animate-to-sketch'
|
type: 'done.invoke.animate-to-face' | 'done.invoke.animate-to-sketch'
|
||||||
data: SketchDetails
|
data: SketchDetails
|
||||||
@ -591,6 +600,8 @@ export const modelingMachine = createMachine(
|
|||||||
Cancel: '#Modeling.Sketch.undo startSketchOn',
|
Cancel: '#Modeling.Sketch.undo startSketchOn',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'new state 1': {},
|
||||||
},
|
},
|
||||||
|
|
||||||
initial: 'Init',
|
initial: 'Init',
|
||||||
@ -796,8 +807,31 @@ export const modelingMachine = createMachine(
|
|||||||
target: 'Tangential arc to',
|
target: 'Tangential arc to',
|
||||||
cond: 'next is tangential arc',
|
cond: 'next is tangential arc',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
target: 'Circle tool',
|
||||||
|
cond: 'next is circle',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'Circle tool': {
|
||||||
|
entry: 'listen for circle origin',
|
||||||
|
|
||||||
|
states: {
|
||||||
|
'Awaiting origin': {
|
||||||
|
on: {
|
||||||
|
'Add circle origin': {
|
||||||
|
target: 'Awaiting perimeter click',
|
||||||
|
actions: 'set up draft circle',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
'Awaiting perimeter click': {},
|
||||||
|
},
|
||||||
|
|
||||||
|
initial: 'Awaiting origin',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
initial: 'Init',
|
initial: 'Init',
|
||||||
@ -1041,6 +1075,14 @@ export const modelingMachine = createMachine(
|
|||||||
if ((state?.event as any).data.tool !== 'rectangle') return false
|
if ((state?.event as any).data.tool !== 'rectangle') return false
|
||||||
return canRectangleTool({ sketchDetails })
|
return canRectangleTool({ sketchDetails })
|
||||||
},
|
},
|
||||||
|
'next is circle': ({ sketchDetails }, _, { state }) => {
|
||||||
|
if ((state?.event as any).data.tool !== 'circle') return false
|
||||||
|
// TODO: Both this an rectangle can currently only be used
|
||||||
|
// if the sketch is empty. They share limited implementations,
|
||||||
|
// so I'm using the same cond until we have
|
||||||
|
// multi-profile sketch support in.
|
||||||
|
return canRectangleTool({ sketchDetails })
|
||||||
|
},
|
||||||
'next is line': (_, __, { state }) =>
|
'next is line': (_, __, { state }) =>
|
||||||
(state?.event as any).data.tool === 'line',
|
(state?.event as any).data.tool === 'line',
|
||||||
'next is none': (_, __, { state }) =>
|
'next is none': (_, __, { state }) =>
|
||||||
@ -1281,13 +1323,18 @@ export const modelingMachine = createMachine(
|
|||||||
},
|
},
|
||||||
'set up draft rectangle': ({ sketchDetails }, { data }) => {
|
'set up draft rectangle': ({ sketchDetails }, { data }) => {
|
||||||
if (!sketchDetails || !data) return
|
if (!sketchDetails || !data) return
|
||||||
sceneEntitiesManager.setupDraftRectangle(
|
|
||||||
sketchDetails.sketchPathToNode,
|
sceneEntitiesManager.setupDraftRectangle(data, sketchDetails)
|
||||||
sketchDetails.zAxis,
|
},
|
||||||
sketchDetails.yAxis,
|
'listen for circle origin': ({ sketchDetails }) => {
|
||||||
sketchDetails.origin,
|
if (!sketchDetails) return
|
||||||
data
|
sceneEntitiesManager.setupCircleOriginListener()
|
||||||
)
|
},
|
||||||
|
'set up draft circle': ({ sketchDetails }, { data }) => {
|
||||||
|
if (!sketchDetails || !data) return
|
||||||
|
console.log('setting up draft circle', data)
|
||||||
|
|
||||||
|
sceneEntitiesManager.setupDraftCircle(data, sketchDetails)
|
||||||
},
|
},
|
||||||
'set up draft line without teardown': ({ sketchDetails }) => {
|
'set up draft line without teardown': ({ sketchDetails }) => {
|
||||||
if (!sketchDetails) return
|
if (!sketchDetails) return
|
||||||
@ -1658,7 +1705,10 @@ export function isEditingExistingSketch({
|
|||||||
(item) =>
|
(item) =>
|
||||||
item.type === 'CallExpression' && item.callee.name === 'startProfileAt'
|
item.type === 'CallExpression' && item.callee.name === 'startProfileAt'
|
||||||
)
|
)
|
||||||
return hasStartProfileAt && pipeExpression.body.length > 2
|
const hasCircle = pipeExpression.body.some(
|
||||||
|
(item) => item.type === 'CallExpression' && item.callee.name === 'circle'
|
||||||
|
)
|
||||||
|
return (hasStartProfileAt && pipeExpression.body.length > 2) || hasCircle
|
||||||
}
|
}
|
||||||
|
|
||||||
export function canRectangleTool({
|
export function canRectangleTool({
|
||||||
|
Reference in New Issue
Block a user