most of the fix for 3 point circle
This commit is contained in:
@ -1,475 +0,0 @@
|
|||||||
import {
|
|
||||||
Group,
|
|
||||||
Mesh,
|
|
||||||
Vector3,
|
|
||||||
Vector2,
|
|
||||||
Object3D,
|
|
||||||
SphereGeometry,
|
|
||||||
MeshBasicMaterial,
|
|
||||||
Color,
|
|
||||||
BufferGeometry,
|
|
||||||
LineDashedMaterial,
|
|
||||||
Line,
|
|
||||||
Plane,
|
|
||||||
} from 'three'
|
|
||||||
|
|
||||||
import { PathToNode, recast, parse, VariableDeclaration } from 'lang/wasm'
|
|
||||||
import { getNodeFromPath } from 'lang/queryAst'
|
|
||||||
import { LabeledArg } from 'wasm-lib/kcl/bindings/LabeledArg'
|
|
||||||
import { Literal } from 'wasm-lib/kcl/bindings/Literal'
|
|
||||||
import { calculate_circle_from_3_points } from 'wasm-lib/pkg/wasm_lib'
|
|
||||||
import { findUniqueName, createPipeExpression } from 'lang/modifyAst'
|
|
||||||
|
|
||||||
import { getThemeColorForThreeJs } from 'lib/theme'
|
|
||||||
|
|
||||||
import { err } from 'lib/trap'
|
|
||||||
import { codeManager } from 'lib/singletons'
|
|
||||||
|
|
||||||
import { SketchTool, NoOpTool } from './interfaceSketchTool'
|
|
||||||
import { createCircleGeometry } from './segments'
|
|
||||||
import { quaternionFromUpNForward } from './helpers'
|
|
||||||
import {
|
|
||||||
SKETCH_LAYER,
|
|
||||||
CIRCLE_3_POINT_DRAFT_POINT,
|
|
||||||
CIRCLE_3_POINT_DRAFT_CIRCLE,
|
|
||||||
} from './sceneInfra'
|
|
||||||
|
|
||||||
interface InitArgs {
|
|
||||||
scene: Scene
|
|
||||||
intersectionPlane: Plane
|
|
||||||
startSketchOnASTNodePath: PathToNode
|
|
||||||
maybeExistingNodePath: PathToNode
|
|
||||||
sketchNodePaths: PathToNode[]
|
|
||||||
metadata?: any
|
|
||||||
forward: Vector3
|
|
||||||
up: Vector3
|
|
||||||
sketchOrigin: Vector3
|
|
||||||
|
|
||||||
// I want the intention to be clear, but keep done() semantics general.
|
|
||||||
callDoneFnAfterBeingDefined?: true
|
|
||||||
done: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
// The reason why InitArgs are not part of the CircleThreePoint constructor is
|
|
||||||
// because we may want to re-initialize the same instance many times, w/ init()
|
|
||||||
export function CircleThreePoint(initArgs: InitArgs): SketchTool {
|
|
||||||
// lee: This is a bit long, but subsequent sketch tools don't need to detail
|
|
||||||
// all this. Use this as a template for other tools.
|
|
||||||
|
|
||||||
// The KCL to generate. Parses into an AST to be modified / manipulated.
|
|
||||||
let selfASTNode = parse(`profileVarNameToBeReplaced = circleThreePoint(
|
|
||||||
sketchVarNameToBeReplaced,
|
|
||||||
p1 = [0.0, 0.0],
|
|
||||||
p2 = [0.0, 0.0],
|
|
||||||
p3 = [0.0, 0.0],
|
|
||||||
)`)
|
|
||||||
|
|
||||||
// May be updated to false in the following code
|
|
||||||
let isNewSketch = true
|
|
||||||
const astSnapshot = structuredClone(kclManager.ast)
|
|
||||||
|
|
||||||
let sketchEntryNodePath = initArgs.maybeExistingNodePath
|
|
||||||
|
|
||||||
// AST node to work with. It's either an existing one, or a new one.
|
|
||||||
if (sketchEntryNodePath.length === 0) {
|
|
||||||
// Travel 1 node up from the sketch plane AST node, and append or
|
|
||||||
// update the new profile AST node.
|
|
||||||
|
|
||||||
const OFFSET_TO_PARENT_SCOPE = -3
|
|
||||||
|
|
||||||
// Get the index of the sketch AST node.
|
|
||||||
// (It could be in a program or function body!)
|
|
||||||
// (It could be in the middle of anywhere in the program!)
|
|
||||||
// ['body', 'index'] <- in this case we're at the top program body
|
|
||||||
// [x, 'index'] <- x is what we're incrementing here -v
|
|
||||||
const nextIndex = initArgs.startSketchOnASTNodePath[
|
|
||||||
// OFFSET_TO_PARENT_SCOPE puts us at the body of a function or
|
|
||||||
// the overall program
|
|
||||||
initArgs.startSketchOnASTNodePath.length + OFFSET_TO_PARENT_SCOPE
|
|
||||||
][0] + 1
|
|
||||||
|
|
||||||
const bodyASTNode = getNodeFromPath<VariableDeclaration>(
|
|
||||||
astSnapshot,
|
|
||||||
structuredClone(initArgs.startSketchOnASTNodePath).splice(
|
|
||||||
0,
|
|
||||||
initArgs.startSketchOnASTNodePath.length + OFFSET_TO_PARENT_SCOPE
|
|
||||||
),
|
|
||||||
'VariableDeclaration'
|
|
||||||
)
|
|
||||||
|
|
||||||
// In the event of an error, we return a no-op tool.
|
|
||||||
// Should maybe consider something else, like ExceptionTool or something.
|
|
||||||
// Logically there should never be an error.
|
|
||||||
if (err(bodyASTNode)) return new NoOpTool()
|
|
||||||
|
|
||||||
// Attach the node
|
|
||||||
bodyASTNode.node.splice(nextIndex, 0, selfASTNode.program.body[0])
|
|
||||||
|
|
||||||
// Manipulate a copy of the original path to match our new AST.
|
|
||||||
sketchEntryNodePath = structuredClone(initArgs.startSketchOnASTNodePath)
|
|
||||||
sketchEntryNodePath[sketchEntryNodePath.length + OFFSET_TO_PARENT_SCOPE][0] = nextIndex
|
|
||||||
} else {
|
|
||||||
const tmpNode = getNodeFromPath<VariableDeclaration>(
|
|
||||||
kclManager.ast,
|
|
||||||
sketchEntryNodePath,
|
|
||||||
'VariableDeclaration'
|
|
||||||
)
|
|
||||||
if (err(tmpNode)) return new NoOpTool()
|
|
||||||
|
|
||||||
// Create references to the existing circleThreePoint AST node
|
|
||||||
Object.assign(selfASTNode.program.body[0], tmpNode.node)
|
|
||||||
|
|
||||||
isNewSketch = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep track of points in the scene with their ThreeJS ids.
|
|
||||||
const points: Map<number, Vector2> = new Map()
|
|
||||||
|
|
||||||
// Keep a reference so we can destroy and recreate as needed.
|
|
||||||
let groupCircle: Group | undefined
|
|
||||||
|
|
||||||
// Add our new group to the list of groups to render
|
|
||||||
const groupOfDrafts = new Group()
|
|
||||||
groupOfDrafts.name = 'circle-3-point-group'
|
|
||||||
groupOfDrafts.layers.set(SKETCH_LAYER)
|
|
||||||
groupOfDrafts.traverse((child) => {
|
|
||||||
child.layers.set(SKETCH_LAYER)
|
|
||||||
})
|
|
||||||
Object.assign(groupOfDrafts.userData, initArgs.metadata)
|
|
||||||
|
|
||||||
initArgs.scene.add(groupOfDrafts)
|
|
||||||
|
|
||||||
// lee: Not a fan we need to re-iterate this dummy object all over the place
|
|
||||||
// just to get the scale but okie dokie.
|
|
||||||
const dummy = new Mesh()
|
|
||||||
dummy.position.set(0, 0, 0)
|
|
||||||
const scale = sceneInfra.getClientSceneScaleFactor(dummy)
|
|
||||||
|
|
||||||
// How large the points on the circle will render as
|
|
||||||
const DRAFT_POINT_RADIUS = 10 // px
|
|
||||||
|
|
||||||
// The target of our dragging
|
|
||||||
let target: Object3D | undefined = undefined
|
|
||||||
|
|
||||||
this.destroy = async () => {
|
|
||||||
initArgs.scene.remove(groupOfDrafts)
|
|
||||||
}
|
|
||||||
|
|
||||||
const createPoint = (
|
|
||||||
center: Vector3,
|
|
||||||
// This is to draw dots with no interactions; purely visual.
|
|
||||||
opts?: { noInteraction?: boolean }
|
|
||||||
): Mesh => {
|
|
||||||
const geometry = new SphereGeometry(DRAFT_POINT_RADIUS)
|
|
||||||
const color = getThemeColorForThreeJs(sceneInfra._theme)
|
|
||||||
|
|
||||||
const material = new MeshBasicMaterial({
|
|
||||||
color: opts?.noInteraction
|
|
||||||
? sceneInfra._theme === 'light'
|
|
||||||
? new Color(color).multiplyScalar(0.15)
|
|
||||||
: new Color(0x010101).multiplyScalar(2000)
|
|
||||||
: color,
|
|
||||||
})
|
|
||||||
|
|
||||||
const mesh = new Mesh(geometry, material)
|
|
||||||
mesh.userData = {
|
|
||||||
type: opts?.noInteraction ? 'ghost' : CIRCLE_3_POINT_DRAFT_POINT,
|
|
||||||
}
|
|
||||||
mesh.renderOrder = 1000
|
|
||||||
mesh.layers.set(SKETCH_LAYER)
|
|
||||||
mesh.position.copy(center)
|
|
||||||
mesh.scale.set(scale, scale, scale)
|
|
||||||
mesh.renderOrder = 100
|
|
||||||
|
|
||||||
return mesh
|
|
||||||
}
|
|
||||||
|
|
||||||
const createCircleThreePointGraphic = async (
|
|
||||||
points: Vector2[],
|
|
||||||
center: Vector2,
|
|
||||||
radius: number
|
|
||||||
) => {
|
|
||||||
if (
|
|
||||||
Number.isNaN(radius) ||
|
|
||||||
Number.isNaN(center.x) ||
|
|
||||||
Number.isNaN(center.y)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
const color = getThemeColorForThreeJs(sceneInfra._theme)
|
|
||||||
const lineCircle = createCircleGeometry({
|
|
||||||
center: [center.x, center.y],
|
|
||||||
radius,
|
|
||||||
color,
|
|
||||||
isDashed: false,
|
|
||||||
scale: 1,
|
|
||||||
})
|
|
||||||
lineCircle.userData = { type: CIRCLE_3_POINT_DRAFT_CIRCLE }
|
|
||||||
lineCircle.layers.set(SKETCH_LAYER)
|
|
||||||
|
|
||||||
if (groupCircle) groupOfDrafts.remove(groupCircle)
|
|
||||||
groupCircle = new Group()
|
|
||||||
groupCircle.renderOrder = 1
|
|
||||||
groupCircle.add(lineCircle)
|
|
||||||
|
|
||||||
const pointMesh = createPoint(new Vector3(center.x, center.y, 0), {
|
|
||||||
noInteraction: true,
|
|
||||||
})
|
|
||||||
groupCircle.add(pointMesh)
|
|
||||||
|
|
||||||
const geometryPolyLine = new BufferGeometry().setFromPoints([
|
|
||||||
...points.map((p) => new Vector3(p.x, p.y, 0)),
|
|
||||||
new Vector3(points[0].x, points[0].y, 0),
|
|
||||||
])
|
|
||||||
const materialPolyLine = new LineDashedMaterial({
|
|
||||||
color,
|
|
||||||
scale: 1 / scale,
|
|
||||||
dashSize: 6,
|
|
||||||
gapSize: 6,
|
|
||||||
})
|
|
||||||
const meshPolyLine = new Line(geometryPolyLine, materialPolyLine)
|
|
||||||
meshPolyLine.computeLineDistances()
|
|
||||||
groupCircle.add(meshPolyLine)
|
|
||||||
|
|
||||||
groupOfDrafts.add(groupCircle)
|
|
||||||
}
|
|
||||||
|
|
||||||
const insertCircleThreePointKclIntoASTSnapshot = (
|
|
||||||
points: Vector2[],
|
|
||||||
): Program => {
|
|
||||||
// Make TypeScript happy about selfASTNode property accesses.
|
|
||||||
if (err(selfASTNode) || selfASTNode.program === null)
|
|
||||||
return kclManager.ast
|
|
||||||
if (selfASTNode.program.body[0].type !== 'VariableDeclaration')
|
|
||||||
return kclManager.ast
|
|
||||||
if (
|
|
||||||
selfASTNode.program.body[0].declaration.init.type !==
|
|
||||||
'CallExpressionKw'
|
|
||||||
)
|
|
||||||
return kclManager.ast
|
|
||||||
|
|
||||||
// Make accessing the labeled arguments easier / less verbose
|
|
||||||
const arg = (x: LabeledArg): Literal[] | undefined => {
|
|
||||||
if (
|
|
||||||
'arg' in x &&
|
|
||||||
'elements' in x.arg &&
|
|
||||||
x.arg.type === 'ArrayExpression'
|
|
||||||
) {
|
|
||||||
if (x.arg.elements.every((x) => x.type === 'Literal')) {
|
|
||||||
return x.arg.elements
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the `profileXXX =` variable name if not set
|
|
||||||
if (
|
|
||||||
selfASTNode.program.body[0].declaration.id.name ===
|
|
||||||
'profileVarNameToBeReplaced'
|
|
||||||
) {
|
|
||||||
const profileVarName = findUniqueName(astSnapshot, 'profile')
|
|
||||||
selfASTNode.program.body[0].declaration.id.name = profileVarName
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used to get the sketch variable name
|
|
||||||
const startSketchOnASTNode = getNodeFromPath<VariableDeclaration>(
|
|
||||||
astSnapshot,
|
|
||||||
initArgs.startSketchOnASTNodePath,
|
|
||||||
'VariableDeclaration'
|
|
||||||
)
|
|
||||||
if (err(startSketchOnASTNode)) return astSnapshot
|
|
||||||
|
|
||||||
// Set the sketch variable name
|
|
||||||
if (/^sketch/.test(startSketchOnASTNode.node.declaration.id.name)) {
|
|
||||||
selfASTNode.program.body[0].declaration.init.unlabeled.name =
|
|
||||||
startSketchOnASTNode.node.declaration.id.name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the points 1-3
|
|
||||||
const selfASTNodeArgs =
|
|
||||||
selfASTNode.program.body[0].declaration.init.arguments
|
|
||||||
|
|
||||||
const arg0 = arg(selfASTNodeArgs[0])
|
|
||||||
if (!arg0) return kclManager.ast
|
|
||||||
arg0[0].value = points[0].x
|
|
||||||
arg0[0].raw = points[0].x.toString()
|
|
||||||
arg0[1].value = points[0].y
|
|
||||||
arg0[1].raw = points[0].y.toString()
|
|
||||||
|
|
||||||
const arg1 = arg(selfASTNodeArgs[1])
|
|
||||||
if (!arg1) return kclManager.ast
|
|
||||||
arg1[0].value = points[1].x
|
|
||||||
arg1[0].raw = points[1].x.toString()
|
|
||||||
arg1[1].value = points[1].y
|
|
||||||
arg1[1].raw = points[1].y.toString()
|
|
||||||
|
|
||||||
const arg2 = arg(selfASTNodeArgs[2])
|
|
||||||
if (!arg2) return kclManager.ast
|
|
||||||
arg2[0].value = points[2].x
|
|
||||||
arg2[0].raw = points[2].x.toString()
|
|
||||||
arg2[1].value = points[2].y
|
|
||||||
arg2[1].raw = points[2].y.toString()
|
|
||||||
|
|
||||||
// Return the `Program`
|
|
||||||
return astSnapshot
|
|
||||||
}
|
|
||||||
|
|
||||||
this.init = () => {
|
|
||||||
groupOfDrafts.position.copy(initArgs.sketchOrigin)
|
|
||||||
const orientation = quaternionFromUpNForward(initArgs.up, initArgs.forward)
|
|
||||||
|
|
||||||
// Reminder: the intersection plane is the primary way to derive a XY
|
|
||||||
// position from a mouse click in ThreeJS.
|
|
||||||
// Here, we position and orient so it's facing the viewer.
|
|
||||||
initArgs.intersectionPlane!.setRotationFromQuaternion(orientation)
|
|
||||||
initArgs.intersectionPlane!.position.copy(initArgs.sketchOrigin)
|
|
||||||
|
|
||||||
// lee: I'm keeping this here as a developer gotchya:
|
|
||||||
// If you use 3D points, do not rotate anything.
|
|
||||||
// If you use 2D points (easier to deal with, generally do this!), then
|
|
||||||
// rotate the group just like this! Remember to rotate other groups too!
|
|
||||||
|
|
||||||
// For some reason, when going into edit mode, we don't need to orient...
|
|
||||||
if (isNewSketch) {
|
|
||||||
groupOfDrafts.setRotationFromQuaternion(orientation)
|
|
||||||
}
|
|
||||||
|
|
||||||
initArgs.scene.add(groupOfDrafts)
|
|
||||||
|
|
||||||
// We're not working with an existing circleThreePoint.
|
|
||||||
if (isNewSketch) return
|
|
||||||
|
|
||||||
// Otherwise, we are :)
|
|
||||||
// Use the points in the AST as starting points.
|
|
||||||
const maybeVariableDeclaration = getNodeFromPath<VariableDeclaration>(
|
|
||||||
astSnapshot,
|
|
||||||
sketchEntryNodePath,
|
|
||||||
'VariableDeclaration'
|
|
||||||
)
|
|
||||||
|
|
||||||
// This should never happen.
|
|
||||||
if (err(maybeVariableDeclaration))
|
|
||||||
return Promise.reject(maybeVariableDeclaration)
|
|
||||||
|
|
||||||
const maybeCallExpressionKw = maybeVariableDeclaration.node.declaration.init
|
|
||||||
if (
|
|
||||||
maybeCallExpressionKw.type === 'CallExpressionKw' &&
|
|
||||||
maybeCallExpressionKw.callee.name === 'circleThreePoint'
|
|
||||||
) {
|
|
||||||
maybeCallExpressionKw?.arguments
|
|
||||||
.map(
|
|
||||||
({ arg }: any) =>
|
|
||||||
new Vector2(arg.elements[0].value, arg.elements[1].value)
|
|
||||||
)
|
|
||||||
.forEach((point: Vector2) => {
|
|
||||||
const pointMesh = createPoint(new Vector3(point.x, point.y, 0))
|
|
||||||
groupOfDrafts.add(pointMesh)
|
|
||||||
points.set(pointMesh.id, point)
|
|
||||||
})
|
|
||||||
|
|
||||||
void this.update()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.update = async () => {
|
|
||||||
const points_ = Array.from(points.values())
|
|
||||||
const circleParams = calculate_circle_from_3_points(
|
|
||||||
points_[0].x,
|
|
||||||
points_[0].y,
|
|
||||||
points_[1].x,
|
|
||||||
points_[1].y,
|
|
||||||
points_[2].x,
|
|
||||||
points_[2].y
|
|
||||||
)
|
|
||||||
|
|
||||||
if (Number.isNaN(circleParams.radius)) return
|
|
||||||
|
|
||||||
await createCircleThreePointGraphic(
|
|
||||||
points_,
|
|
||||||
new Vector2(circleParams.center_x, circleParams.center_y),
|
|
||||||
circleParams.radius
|
|
||||||
)
|
|
||||||
const astWithNewCode = insertCircleThreePointKclIntoASTSnapshot(points_)
|
|
||||||
const codeAsString = recast(astWithNewCode)
|
|
||||||
if (err(codeAsString)) return
|
|
||||||
codeManager.updateCodeStateEditor(codeAsString)
|
|
||||||
return astWithNewCode
|
|
||||||
}
|
|
||||||
|
|
||||||
this.onDrag = async (args) => {
|
|
||||||
const draftPointsIntersected = args.intersects.filter(
|
|
||||||
(intersected) =>
|
|
||||||
intersected.object.userData.type === CIRCLE_3_POINT_DRAFT_POINT
|
|
||||||
)
|
|
||||||
|
|
||||||
const firstPoint = draftPointsIntersected[0]
|
|
||||||
if (firstPoint && !target) {
|
|
||||||
target = firstPoint.object
|
|
||||||
}
|
|
||||||
|
|
||||||
// The user was off their mark! Missed the object to select.
|
|
||||||
if (!target) return
|
|
||||||
|
|
||||||
target.position.copy(
|
|
||||||
new Vector3(
|
|
||||||
args.intersectionPoint.twoD.x,
|
|
||||||
args.intersectionPoint.twoD.y,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
points.set(target.id, args.intersectionPoint.twoD)
|
|
||||||
|
|
||||||
if (points.size <= 2) return
|
|
||||||
|
|
||||||
await this.update()
|
|
||||||
}
|
|
||||||
|
|
||||||
this.onDragEnd = async (_args) => {
|
|
||||||
target = undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
this.onClick = async (args) => {
|
|
||||||
if (points.size >= 3) return
|
|
||||||
if (!args.intersectionPoint) return
|
|
||||||
|
|
||||||
const pointMesh = createPoint(
|
|
||||||
new Vector3(
|
|
||||||
args.intersectionPoint.twoD.x,
|
|
||||||
args.intersectionPoint.twoD.y,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
groupOfDrafts.add(pointMesh)
|
|
||||||
points.set(pointMesh.id, args.intersectionPoint.twoD)
|
|
||||||
|
|
||||||
if (points.size <= 2) return
|
|
||||||
|
|
||||||
const astWithNewCode = await this.update()
|
|
||||||
|
|
||||||
if (initArgs.callDoneFnAfterBeingDefined) {
|
|
||||||
// We "fake" execute to update the overall program memory.
|
|
||||||
// setupSketch needs that memory to be updated.
|
|
||||||
// We only do it at the very last moment before passing off control
|
|
||||||
// because this sketch tool logic doesn't need that at all, and is
|
|
||||||
// needless (sometimes heavy, but not here) computation.
|
|
||||||
await kclManager.executeAstMock(astWithNewCode)
|
|
||||||
|
|
||||||
if (isNewSketch) {
|
|
||||||
// Add it to our sketch's profiles
|
|
||||||
initArgs.sketchNodePaths.push(sketchEntryNodePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass the updated sketchDetails information back to the state machine.
|
|
||||||
// In most (all?) cases the sketch plane never changes.
|
|
||||||
// In many cases, sketchEntryNodePath may be a new PathToNode
|
|
||||||
// (usually simply the next index in a scope)
|
|
||||||
// updatedSketchNodePaths same deal, many cases it's the same or updated,
|
|
||||||
// depends if a new circle is made.
|
|
||||||
initArgs.done({
|
|
||||||
updatedPlaneNodePath: initArgs.startSketchOnASTNodePath,
|
|
||||||
updatedEntryNodePath: sketchEntryNodePath,
|
|
||||||
updatedSketchNodePaths: initArgs.sketchNodePaths,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
import { SceneInfra } from './sceneInfra'
|
|
||||||
|
|
||||||
export interface SketchTool {
|
|
||||||
init: () => void
|
|
||||||
|
|
||||||
// Update could mean draw, refresh editor state, etc. It's up to the
|
|
||||||
// SketchTool implementer.
|
|
||||||
update: () => void
|
|
||||||
|
|
||||||
// Clean up the state (such as ThreeJS scene)
|
|
||||||
destroy: () => void
|
|
||||||
|
|
||||||
// To be hooked into sceneInfra.callbacks or other places as necessary.
|
|
||||||
// All the necessary types exist in SceneInfra. If it ever majorly changes
|
|
||||||
// we want this to break such that they are corrected too.
|
|
||||||
onDragStart?: (typeof SceneInfra)['onDragStartCallback']
|
|
||||||
onDragEnd?: (typeof SceneInfra)['onDragEndCallback']
|
|
||||||
onDrag?: (typeof SceneInfra)['onDragCallback']
|
|
||||||
onMove?: (typeof SceneInfra)['onMoveCallback']
|
|
||||||
onClick?: (typeof SceneInfra)['onClickCallback']
|
|
||||||
onMouseEnter?: (typeof SceneInfra)['onMouseEnterCallback']
|
|
||||||
onMouseLeave?: (typeof SceneInfra)['onMouseLeaveCallback']
|
|
||||||
}
|
|
||||||
|
|
||||||
export function NoOpTool(): SketchTool {
|
|
||||||
this.init = () => {}
|
|
||||||
this.update = () => {}
|
|
||||||
this.destroy = () => {}
|
|
||||||
return this
|
|
||||||
}
|
|
@ -85,6 +85,7 @@ import {
|
|||||||
createCallExpressionStdLib,
|
createCallExpressionStdLib,
|
||||||
createIdentifier,
|
createIdentifier,
|
||||||
createLiteral,
|
createLiteral,
|
||||||
|
createNodeFromExprSnippet,
|
||||||
createObjectExpression,
|
createObjectExpression,
|
||||||
createPipeExpression,
|
createPipeExpression,
|
||||||
createPipeSubstitution,
|
createPipeSubstitution,
|
||||||
@ -134,6 +135,13 @@ export const TANGENTIAL_ARC_TO__SEGMENT_DASH =
|
|||||||
'tangential-arc-to-segment-body-dashed'
|
'tangential-arc-to-segment-body-dashed'
|
||||||
export const TANGENTIAL_ARC_TO_SEGMENT = 'tangential-arc-to-segment'
|
export const TANGENTIAL_ARC_TO_SEGMENT = 'tangential-arc-to-segment'
|
||||||
export const TANGENTIAL_ARC_TO_SEGMENT_BODY = 'tangential-arc-to-segment-body'
|
export const TANGENTIAL_ARC_TO_SEGMENT_BODY = 'tangential-arc-to-segment-body'
|
||||||
|
export const CIRCLE_THREE_POINT_SEGMENT = 'circle-three-point-segment'
|
||||||
|
export const CIRCLE_THREE_POINT_SEGMENT_BODY = 'circle-segment-body'
|
||||||
|
export const CIRCLE_THREE_POINT_SEGMENT_DASH =
|
||||||
|
'circle-three-point-segment-body-dashed'
|
||||||
|
export const CIRCLE_THREE_POINT_HANDLE1 = 'circle-three-point-handle1'
|
||||||
|
export const CIRCLE_THREE_POINT_HANDLE2 = 'circle-three-point-handle2'
|
||||||
|
export const CIRCLE_THREE_POINT_HANDLE3 = 'circle-three-point-handle3'
|
||||||
export const CIRCLE_SEGMENT = 'circle-segment'
|
export const CIRCLE_SEGMENT = 'circle-segment'
|
||||||
export const CIRCLE_SEGMENT_BODY = 'circle-segment-body'
|
export const CIRCLE_SEGMENT_BODY = 'circle-segment-body'
|
||||||
export const CIRCLE_SEGMENT_DASH = 'circle-segment-body-dashed'
|
export const CIRCLE_SEGMENT_DASH = 'circle-segment-body-dashed'
|
||||||
@ -145,6 +153,7 @@ export const SEGMENT_BODIES = [
|
|||||||
STRAIGHT_SEGMENT,
|
STRAIGHT_SEGMENT,
|
||||||
TANGENTIAL_ARC_TO_SEGMENT,
|
TANGENTIAL_ARC_TO_SEGMENT,
|
||||||
CIRCLE_SEGMENT,
|
CIRCLE_SEGMENT,
|
||||||
|
CIRCLE_THREE_POINT_SEGMENT,
|
||||||
]
|
]
|
||||||
export const SEGMENT_BODIES_PLUS_PROFILE_START = [
|
export const SEGMENT_BODIES_PLUS_PROFILE_START = [
|
||||||
...SEGMENT_BODIES,
|
...SEGMENT_BODIES,
|
||||||
@ -161,7 +170,6 @@ export class SceneEntities {
|
|||||||
scene: Scene
|
scene: Scene
|
||||||
sceneProgramMemory: ProgramMemory = ProgramMemory.empty()
|
sceneProgramMemory: ProgramMemory = ProgramMemory.empty()
|
||||||
activeSegments: { [key: string]: Group } = {}
|
activeSegments: { [key: string]: Group } = {}
|
||||||
sketchTools: SketchTool[]
|
|
||||||
intersectionPlane: Mesh | null = null
|
intersectionPlane: Mesh | null = null
|
||||||
axisGroup: Group | null = null
|
axisGroup: Group | null = null
|
||||||
draftPointGroups: Group[] = []
|
draftPointGroups: Group[] = []
|
||||||
@ -221,6 +229,20 @@ export class SceneEntities {
|
|||||||
radius: segment.userData.radius,
|
radius: segment.userData.radius,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
segment.userData.p1 &&
|
||||||
|
segment.userData.p2 &&
|
||||||
|
segment.userData.p3 &&
|
||||||
|
segment.userData.type === CIRCLE_THREE_POINT_SEGMENT
|
||||||
|
) {
|
||||||
|
update = segmentUtils.circleThreePoint.update
|
||||||
|
input = {
|
||||||
|
type: 'circle-three-point-segment',
|
||||||
|
p1: segment.userData.p1,
|
||||||
|
p2: segment.userData.p2,
|
||||||
|
p3: segment.userData.p3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const callBack = update?.({
|
const callBack = update?.({
|
||||||
prevSegment: segment.userData.prevSegment,
|
prevSegment: segment.userData.prevSegment,
|
||||||
@ -584,8 +606,6 @@ export class SceneEntities {
|
|||||||
programMemory,
|
programMemory,
|
||||||
})
|
})
|
||||||
|
|
||||||
this.sketchTools = []
|
|
||||||
|
|
||||||
this.sceneProgramMemory = programMemory
|
this.sceneProgramMemory = programMemory
|
||||||
const group = new Group()
|
const group = new Group()
|
||||||
position && group.position.set(...position)
|
position && group.position.set(...position)
|
||||||
@ -606,7 +626,10 @@ export class SceneEntities {
|
|||||||
maybeModdedAst,
|
maybeModdedAst,
|
||||||
sourceRangeFromRust(sketch.start.__geoMeta.sourceRange)
|
sourceRangeFromRust(sketch.start.__geoMeta.sourceRange)
|
||||||
)
|
)
|
||||||
if (['Circle', 'CircleThreePoint'].includes(sketch?.paths?.[0]?.type) === false) {
|
if (
|
||||||
|
['Circle', 'CircleThreePoint'].includes(sketch?.paths?.[0]?.type) ===
|
||||||
|
false
|
||||||
|
) {
|
||||||
const _profileStart = createProfileStartHandle({
|
const _profileStart = createProfileStartHandle({
|
||||||
from: sketch.start.from,
|
from: sketch.start.from,
|
||||||
id: sketch.start.__geoMeta.id,
|
id: sketch.start.__geoMeta.id,
|
||||||
@ -670,28 +693,13 @@ export class SceneEntities {
|
|||||||
if (err(_node1)) return
|
if (err(_node1)) return
|
||||||
const callExpName = _node1.node?.callee?.name
|
const callExpName = _node1.node?.callee?.name
|
||||||
|
|
||||||
if (segment.type === 'CircleThreePoint') {
|
|
||||||
const circleThreePoint = new CircleThreePoint({
|
|
||||||
scene: group,
|
|
||||||
intersectionPlane: this.intersectionPlane,
|
|
||||||
startSketchOnASTNodePath: segPathToNode,
|
|
||||||
maybeExistingNodePath: _node1.deepPath,
|
|
||||||
sketchNodePaths: sketch.paths,
|
|
||||||
metadata: segment,
|
|
||||||
forward: new Vector3(...forward),
|
|
||||||
up: new Vector3(...up),
|
|
||||||
sketchOrigin: new Vector3(...position),
|
|
||||||
})
|
|
||||||
circleThreePoint.init()
|
|
||||||
this.sketchTools.push(circleThreePoint)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const initSegment =
|
const initSegment =
|
||||||
segment.type === 'TangentialArcTo'
|
segment.type === 'TangentialArcTo'
|
||||||
? segmentUtils.tangentialArcTo.init
|
? segmentUtils.tangentialArcTo.init
|
||||||
: segment.type === 'Circle'
|
: segment.type === 'Circle'
|
||||||
? segmentUtils.circle.init
|
? segmentUtils.circle.init
|
||||||
|
: segment.type === 'CircleThreePoint'
|
||||||
|
? segmentUtils.circleThreePoint.init
|
||||||
: segmentUtils.straight.init
|
: segmentUtils.straight.init
|
||||||
const input: SegmentInputs =
|
const input: SegmentInputs =
|
||||||
segment.type === 'Circle'
|
segment.type === 'Circle'
|
||||||
@ -701,6 +709,13 @@ export class SceneEntities {
|
|||||||
center: segment.center,
|
center: segment.center,
|
||||||
radius: segment.radius,
|
radius: segment.radius,
|
||||||
}
|
}
|
||||||
|
: segment.type === 'CircleThreePoint'
|
||||||
|
? {
|
||||||
|
type: 'circle-three-point-segment',
|
||||||
|
p1: segment.p1,
|
||||||
|
p2: segment.p2,
|
||||||
|
p3: segment.p3,
|
||||||
|
}
|
||||||
: {
|
: {
|
||||||
type: 'straight-segment',
|
type: 'straight-segment',
|
||||||
from: segment.from,
|
from: segment.from,
|
||||||
@ -1399,6 +1414,185 @@ export class SceneEntities {
|
|||||||
})
|
})
|
||||||
return { updatedEntryNodePath, updatedSketchNodePaths }
|
return { updatedEntryNodePath, updatedSketchNodePaths }
|
||||||
}
|
}
|
||||||
|
setupDraftCircleThreePoint = async (
|
||||||
|
sketchEntryNodePath: PathToNode,
|
||||||
|
sketchNodePaths: PathToNode[],
|
||||||
|
planeNodePath: PathToNode,
|
||||||
|
forward: [number, number, number],
|
||||||
|
up: [number, number, number],
|
||||||
|
sketchOrigin: [number, number, number],
|
||||||
|
point1: [x: number, y: number],
|
||||||
|
point2: [x: number, y: number]
|
||||||
|
): Promise<SketchDetailsUpdate | Error> => {
|
||||||
|
let _ast = structuredClone(kclManager.ast)
|
||||||
|
|
||||||
|
const varDec = getNodeFromPath<VariableDeclarator>(
|
||||||
|
_ast,
|
||||||
|
planeNodePath,
|
||||||
|
'VariableDeclarator'
|
||||||
|
)
|
||||||
|
|
||||||
|
if (err(varDec)) return varDec
|
||||||
|
if (varDec.node.type !== 'VariableDeclarator') return new Error('not a var')
|
||||||
|
|
||||||
|
const varName = findUniqueName(_ast, 'profile')
|
||||||
|
|
||||||
|
const thirdPointCloseToWhereUserLastClicked = `[${point2[0] + 0.1}, ${
|
||||||
|
point2[1] + 0.1
|
||||||
|
}]`
|
||||||
|
const newExpression = createNodeFromExprSnippet`${varName} = circleThreePoint({
|
||||||
|
p1 = [${point1[0]}, ${point1[1]}],
|
||||||
|
p2 = [${point2[0]}, ${point2[1]}],
|
||||||
|
p3 = ${thirdPointCloseToWhereUserLastClicked}},
|
||||||
|
${varDec.node.id.name}
|
||||||
|
)`
|
||||||
|
if (err(newExpression)) return newExpression
|
||||||
|
const insertIndex = getInsertIndex(sketchNodePaths, planeNodePath, 'end')
|
||||||
|
|
||||||
|
_ast.body.splice(insertIndex, 0, newExpression)
|
||||||
|
const { updatedEntryNodePath, updatedSketchNodePaths } =
|
||||||
|
updateSketchNodePathsWithInsertIndex({
|
||||||
|
insertIndex,
|
||||||
|
insertType: 'end',
|
||||||
|
sketchNodePaths,
|
||||||
|
})
|
||||||
|
|
||||||
|
const pResult = parse(recast(_ast))
|
||||||
|
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
|
||||||
|
_ast = pResult.program
|
||||||
|
|
||||||
|
// do a quick mock execution to get the program memory up-to-date
|
||||||
|
await kclManager.executeAstMock(_ast)
|
||||||
|
|
||||||
|
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
|
||||||
|
sketchEntryNodePath: updatedEntryNodePath,
|
||||||
|
sketchNodePaths: updatedSketchNodePaths,
|
||||||
|
forward,
|
||||||
|
up,
|
||||||
|
position: sketchOrigin,
|
||||||
|
maybeModdedAst: _ast,
|
||||||
|
draftExpressionsIndices: { start: 0, end: 0 },
|
||||||
|
})
|
||||||
|
|
||||||
|
sceneInfra.setCallbacks({
|
||||||
|
onMove: async (args) => {
|
||||||
|
const nodePathWithCorrectedIndexForTruncatedAst =
|
||||||
|
structuredClone(updatedEntryNodePath)
|
||||||
|
nodePathWithCorrectedIndexForTruncatedAst[1][0] =
|
||||||
|
Number(nodePathWithCorrectedIndexForTruncatedAst[1][0]) -
|
||||||
|
Number(planeNodePath[1][0]) -
|
||||||
|
1
|
||||||
|
const _node = getNodeFromPath<VariableDeclaration>(
|
||||||
|
truncatedAst,
|
||||||
|
nodePathWithCorrectedIndexForTruncatedAst,
|
||||||
|
'VariableDeclaration'
|
||||||
|
)
|
||||||
|
let modded = structuredClone(truncatedAst)
|
||||||
|
if (trap(_node)) return
|
||||||
|
const sketchInit = _node.node.declaration.init
|
||||||
|
|
||||||
|
if (sketchInit.type === 'CallExpression') {
|
||||||
|
const moddedResult = changeSketchArguments(
|
||||||
|
modded,
|
||||||
|
kclManager.programMemory,
|
||||||
|
{
|
||||||
|
type: 'path',
|
||||||
|
pathToNode: nodePathWithCorrectedIndexForTruncatedAst,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'circle-three-point-segment',
|
||||||
|
p1: [point1[0], point1[1]],
|
||||||
|
p2: [point2[0], point2[1]],
|
||||||
|
p3: [
|
||||||
|
args.intersectionPoint.twoD.x,
|
||||||
|
args.intersectionPoint.twoD.y,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (err(moddedResult)) return
|
||||||
|
modded = moddedResult.modifiedAst
|
||||||
|
}
|
||||||
|
|
||||||
|
const { execState } = await executeAst({
|
||||||
|
ast: modded,
|
||||||
|
engineCommandManager: this.engineCommandManager,
|
||||||
|
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||||
|
programMemoryOverride,
|
||||||
|
})
|
||||||
|
const programMemory = execState.memory
|
||||||
|
this.sceneProgramMemory = programMemory
|
||||||
|
const sketch = sketchFromKclValue(programMemory.get(varName), varName)
|
||||||
|
if (err(sketch)) return
|
||||||
|
const sgPaths = sketch.paths
|
||||||
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
||||||
|
|
||||||
|
const varDecIndex = Number(updatedEntryNodePath[1][0])
|
||||||
|
|
||||||
|
this.updateSegment(
|
||||||
|
sketch.start,
|
||||||
|
0,
|
||||||
|
varDecIndex,
|
||||||
|
_ast,
|
||||||
|
orthoFactor,
|
||||||
|
sketch
|
||||||
|
)
|
||||||
|
sgPaths.forEach((seg, index) =>
|
||||||
|
this.updateSegment(seg, index, varDecIndex, _ast, orthoFactor, sketch)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick: async (args) => {
|
||||||
|
// If there is a valid camera interaction that matches, do that instead
|
||||||
|
const interaction = sceneInfra.camControls.getInteractionType(
|
||||||
|
args.mouseEvent
|
||||||
|
)
|
||||||
|
if (interaction !== 'none') return
|
||||||
|
// Commit the rectangle to the full AST/code and return to sketch.idle
|
||||||
|
const cornerPoint = args.intersectionPoint?.twoD
|
||||||
|
if (!cornerPoint || args.mouseEvent.button !== 0) return
|
||||||
|
|
||||||
|
const _node = getNodeFromPath<VariableDeclaration>(
|
||||||
|
_ast,
|
||||||
|
updatedEntryNodePath || [],
|
||||||
|
'VariableDeclaration'
|
||||||
|
)
|
||||||
|
if (trap(_node)) return
|
||||||
|
const sketchInit = _node.node?.declaration.init
|
||||||
|
|
||||||
|
let modded = structuredClone(_ast)
|
||||||
|
if (sketchInit.type === 'CallExpression') {
|
||||||
|
const moddedResult = changeSketchArguments(
|
||||||
|
modded,
|
||||||
|
kclManager.programMemory,
|
||||||
|
{
|
||||||
|
type: 'path',
|
||||||
|
pathToNode: updatedEntryNodePath,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'circle-three-point-segment',
|
||||||
|
p1: [point1[0], point1[1]],
|
||||||
|
p2: [point2[0], point2[1]],
|
||||||
|
p3: [cornerPoint.x || 0, cornerPoint.y || 0],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (err(moddedResult)) return
|
||||||
|
modded = moddedResult.modifiedAst
|
||||||
|
|
||||||
|
const newCode = recast(modded)
|
||||||
|
if (err(newCode)) return
|
||||||
|
const pResult = parse(newCode)
|
||||||
|
if (trap(pResult) || !resultIsOk(pResult))
|
||||||
|
return Promise.reject(pResult)
|
||||||
|
_ast = pResult.program
|
||||||
|
|
||||||
|
// Update the primary AST and unequip the rectangle tool
|
||||||
|
await kclManager.executeAstMock(_ast)
|
||||||
|
sceneInfra.modelingSend({ type: 'Finish circle three point' })
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(_ast)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return { updatedEntryNodePath, updatedSketchNodePaths }
|
||||||
|
}
|
||||||
setupDraftCircle = async (
|
setupDraftCircle = async (
|
||||||
sketchEntryNodePath: PathToNode,
|
sketchEntryNodePath: PathToNode,
|
||||||
sketchNodePaths: PathToNode[],
|
sketchNodePaths: PathToNode[],
|
||||||
@ -1790,7 +1984,13 @@ export class SceneEntities {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const group = getParentGroup(object, SEGMENT_BODIES_PLUS_PROFILE_START)
|
const group = getParentGroup(object, SEGMENT_BODIES_PLUS_PROFILE_START)
|
||||||
const subGroup = getParentGroup(object, [ARROWHEAD, CIRCLE_CENTER_HANDLE])
|
const subGroup = getParentGroup(object, [
|
||||||
|
ARROWHEAD,
|
||||||
|
CIRCLE_CENTER_HANDLE,
|
||||||
|
CIRCLE_THREE_POINT_HANDLE1,
|
||||||
|
CIRCLE_THREE_POINT_HANDLE2,
|
||||||
|
CIRCLE_THREE_POINT_HANDLE3,
|
||||||
|
])
|
||||||
if (!group) return
|
if (!group) return
|
||||||
const pathToNode: PathToNode = structuredClone(group.userData.pathToNode)
|
const pathToNode: PathToNode = structuredClone(group.userData.pathToNode)
|
||||||
const varDecIndex = pathToNode[1][0]
|
const varDecIndex = pathToNode[1][0]
|
||||||
@ -1802,8 +2002,8 @@ export class SceneEntities {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const from: [number, number] = [
|
const from: [number, number] = [
|
||||||
group.userData.from[0],
|
group.userData?.from?.[0],
|
||||||
group.userData.from[1],
|
group.userData?.from?.[1],
|
||||||
]
|
]
|
||||||
const dragTo: [number, number] = [snappedPoint.x, snappedPoint.y]
|
const dragTo: [number, number] = [snappedPoint.x, snappedPoint.y]
|
||||||
let modifiedAst = draftInfo ? draftInfo.truncatedAst : { ...kclManager.ast }
|
let modifiedAst = draftInfo ? draftInfo.truncatedAst : { ...kclManager.ast }
|
||||||
@ -1858,6 +2058,29 @@ export class SceneEntities {
|
|||||||
center: dragTo,
|
center: dragTo,
|
||||||
radius: group.userData.radius,
|
radius: group.userData.radius,
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
subGroup?.name &&
|
||||||
|
[
|
||||||
|
CIRCLE_THREE_POINT_HANDLE1,
|
||||||
|
CIRCLE_THREE_POINT_HANDLE2,
|
||||||
|
CIRCLE_THREE_POINT_HANDLE3,
|
||||||
|
].includes(subGroup?.name)
|
||||||
|
) {
|
||||||
|
const input: SegmentInputs = {
|
||||||
|
type: 'circle-three-point-segment',
|
||||||
|
p1: group.userData.p1,
|
||||||
|
p2: group.userData.p2,
|
||||||
|
p3: group.userData.p3,
|
||||||
|
}
|
||||||
|
if (subGroup?.name === CIRCLE_THREE_POINT_HANDLE1) {
|
||||||
|
input.p1 = dragTo
|
||||||
|
} else if (subGroup?.name === CIRCLE_THREE_POINT_HANDLE2) {
|
||||||
|
input.p2 = dragTo
|
||||||
|
} else if (subGroup?.name === CIRCLE_THREE_POINT_HANDLE3) {
|
||||||
|
input.p3 = dragTo
|
||||||
|
}
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
||||||
// straight segment is the default
|
// straight segment is the default
|
||||||
return {
|
return {
|
||||||
@ -2011,6 +2234,18 @@ export class SceneEntities {
|
|||||||
center: segment.center,
|
center: segment.center,
|
||||||
radius: segment.radius,
|
radius: segment.radius,
|
||||||
}
|
}
|
||||||
|
} else if (
|
||||||
|
type === CIRCLE_THREE_POINT_SEGMENT &&
|
||||||
|
'type' in segment &&
|
||||||
|
segment.type === 'CircleThreePoint'
|
||||||
|
) {
|
||||||
|
update = segmentUtils.circleThreePoint.update
|
||||||
|
input = {
|
||||||
|
type: 'circle-three-point-segment',
|
||||||
|
p1: segment.p1,
|
||||||
|
p2: segment.p2,
|
||||||
|
p3: segment.p3,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const callBack =
|
const callBack =
|
||||||
update &&
|
update &&
|
||||||
@ -2060,9 +2295,6 @@ export class SceneEntities {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Remove all sketch tools
|
// Remove all sketch tools
|
||||||
for (let tool of this.sketchTools) {
|
|
||||||
await tool.destroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.axisGroup && removeAxis) this.scene.remove(this.axisGroup)
|
if (this.axisGroup && removeAxis) this.scene.remove(this.axisGroup)
|
||||||
const sketchSegments = this.scene.children.find(
|
const sketchSegments = this.scene.children.find(
|
||||||
|
@ -31,6 +31,12 @@ import {
|
|||||||
CIRCLE_SEGMENT,
|
CIRCLE_SEGMENT,
|
||||||
CIRCLE_SEGMENT_BODY,
|
CIRCLE_SEGMENT_BODY,
|
||||||
CIRCLE_SEGMENT_DASH,
|
CIRCLE_SEGMENT_DASH,
|
||||||
|
CIRCLE_THREE_POINT_HANDLE1,
|
||||||
|
CIRCLE_THREE_POINT_HANDLE2,
|
||||||
|
CIRCLE_THREE_POINT_HANDLE3,
|
||||||
|
CIRCLE_THREE_POINT_SEGMENT,
|
||||||
|
CIRCLE_THREE_POINT_SEGMENT_BODY,
|
||||||
|
CIRCLE_THREE_POINT_SEGMENT_DASH,
|
||||||
EXTRA_SEGMENT_HANDLE,
|
EXTRA_SEGMENT_HANDLE,
|
||||||
EXTRA_SEGMENT_OFFSET_PX,
|
EXTRA_SEGMENT_OFFSET_PX,
|
||||||
HIDE_HOVER_SEGMENT_LENGTH,
|
HIDE_HOVER_SEGMENT_LENGTH,
|
||||||
@ -48,11 +54,13 @@ import {
|
|||||||
import { getTangentPointFromPreviousArc } from 'lib/utils2d'
|
import { getTangentPointFromPreviousArc } from 'lib/utils2d'
|
||||||
import {
|
import {
|
||||||
ARROWHEAD,
|
ARROWHEAD,
|
||||||
|
CIRCLE_3_POINT_DRAFT_CIRCLE,
|
||||||
DRAFT_POINT,
|
DRAFT_POINT,
|
||||||
SceneInfra,
|
SceneInfra,
|
||||||
SEGMENT_LENGTH_LABEL,
|
SEGMENT_LENGTH_LABEL,
|
||||||
SEGMENT_LENGTH_LABEL_OFFSET_PX,
|
SEGMENT_LENGTH_LABEL_OFFSET_PX,
|
||||||
SEGMENT_LENGTH_LABEL_TEXT,
|
SEGMENT_LENGTH_LABEL_TEXT,
|
||||||
|
SKETCH_LAYER,
|
||||||
} from './sceneInfra'
|
} from './sceneInfra'
|
||||||
import { Themes, getThemeColorForThreeJs } from 'lib/theme'
|
import { Themes, getThemeColorForThreeJs } from 'lib/theme'
|
||||||
import { normaliseAngle, roundOff } from 'lib/utils'
|
import { normaliseAngle, roundOff } from 'lib/utils'
|
||||||
@ -61,6 +69,7 @@ import { SegmentInputs } from 'lang/std/stdTypes'
|
|||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
import { editorManager, sceneInfra } from 'lib/singletons'
|
import { editorManager, sceneInfra } from 'lib/singletons'
|
||||||
import { Selections } from 'lib/selections'
|
import { Selections } from 'lib/selections'
|
||||||
|
import { calculate_circle_from_3_points } from 'wasm-lib/pkg/wasm_lib'
|
||||||
|
|
||||||
interface CreateSegmentArgs {
|
interface CreateSegmentArgs {
|
||||||
input: SegmentInputs
|
input: SegmentInputs
|
||||||
@ -693,6 +702,194 @@ class CircleSegment implements SegmentUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CircleThreePointSegment implements SegmentUtils {
|
||||||
|
init: SegmentUtils['init'] = ({
|
||||||
|
input,
|
||||||
|
id,
|
||||||
|
pathToNode,
|
||||||
|
isDraftSegment,
|
||||||
|
scale = 1,
|
||||||
|
theme,
|
||||||
|
isSelected = false,
|
||||||
|
sceneInfra,
|
||||||
|
prevSegment,
|
||||||
|
}) => {
|
||||||
|
if (input.type !== 'circle-three-point-segment') {
|
||||||
|
return new Error('Invalid segment type')
|
||||||
|
}
|
||||||
|
const { p1, p2, p3 } = input
|
||||||
|
const { center_x, center_y, radius } = calculate_circle_from_3_points(
|
||||||
|
p1[0],
|
||||||
|
p1[1],
|
||||||
|
p2[0],
|
||||||
|
p2[1],
|
||||||
|
p3[0],
|
||||||
|
p3[1]
|
||||||
|
)
|
||||||
|
const center: [number, number] = [center_x, center_y]
|
||||||
|
const baseColor = getThemeColorForThreeJs(theme)
|
||||||
|
const color = isSelected ? 0x0000ff : baseColor
|
||||||
|
|
||||||
|
const group = new Group()
|
||||||
|
const geometry = createArcGeometry({
|
||||||
|
center,
|
||||||
|
radius,
|
||||||
|
startAngle: 0,
|
||||||
|
endAngle: Math.PI * 2,
|
||||||
|
ccw: true,
|
||||||
|
isDashed: isDraftSegment,
|
||||||
|
scale,
|
||||||
|
})
|
||||||
|
const mat = new MeshBasicMaterial({ color })
|
||||||
|
const arcMesh = new Mesh(geometry, mat)
|
||||||
|
const meshType = isDraftSegment
|
||||||
|
? CIRCLE_THREE_POINT_SEGMENT_DASH
|
||||||
|
: CIRCLE_THREE_POINT_SEGMENT_BODY
|
||||||
|
const handle1 = createCircleThreePointHandle(
|
||||||
|
scale,
|
||||||
|
theme,
|
||||||
|
CIRCLE_THREE_POINT_HANDLE1,
|
||||||
|
color
|
||||||
|
)
|
||||||
|
const handle2 = createCircleThreePointHandle(
|
||||||
|
scale,
|
||||||
|
theme,
|
||||||
|
CIRCLE_THREE_POINT_HANDLE2,
|
||||||
|
color
|
||||||
|
)
|
||||||
|
const handle3 = createCircleThreePointHandle(
|
||||||
|
scale,
|
||||||
|
theme,
|
||||||
|
CIRCLE_THREE_POINT_HANDLE3,
|
||||||
|
color
|
||||||
|
)
|
||||||
|
|
||||||
|
arcMesh.userData.type = meshType
|
||||||
|
arcMesh.name = meshType
|
||||||
|
group.userData = {
|
||||||
|
type: CIRCLE_THREE_POINT_SEGMENT,
|
||||||
|
draft: isDraftSegment,
|
||||||
|
id,
|
||||||
|
p1,
|
||||||
|
p2,
|
||||||
|
p3,
|
||||||
|
ccw: true,
|
||||||
|
prevSegment,
|
||||||
|
pathToNode,
|
||||||
|
isSelected,
|
||||||
|
baseColor,
|
||||||
|
}
|
||||||
|
group.name = CIRCLE_THREE_POINT_SEGMENT
|
||||||
|
|
||||||
|
group.add(arcMesh, handle1, handle2, handle3)
|
||||||
|
const updateOverlaysCallback = this.update({
|
||||||
|
prevSegment,
|
||||||
|
input,
|
||||||
|
group,
|
||||||
|
scale,
|
||||||
|
sceneInfra,
|
||||||
|
})
|
||||||
|
if (err(updateOverlaysCallback)) return updateOverlaysCallback
|
||||||
|
|
||||||
|
return {
|
||||||
|
group,
|
||||||
|
updateOverlaysCallback,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
update: SegmentUtils['update'] = ({
|
||||||
|
input,
|
||||||
|
group,
|
||||||
|
scale = 1,
|
||||||
|
sceneInfra,
|
||||||
|
}) => {
|
||||||
|
if (input.type !== 'circle-three-point-segment') {
|
||||||
|
return new Error('Invalid segment type')
|
||||||
|
}
|
||||||
|
const { p1, p2, p3 } = input
|
||||||
|
group.userData.p1 = p1
|
||||||
|
group.userData.p2 = p2
|
||||||
|
group.userData.p3 = p3
|
||||||
|
const { center_x, center_y, radius } = calculate_circle_from_3_points(
|
||||||
|
p1[0],
|
||||||
|
p1[1],
|
||||||
|
p2[0],
|
||||||
|
p2[1],
|
||||||
|
p3[0],
|
||||||
|
p3[1]
|
||||||
|
)
|
||||||
|
const center: [number, number] = [center_x, center_y]
|
||||||
|
const points = [p1, p2, p3]
|
||||||
|
const handles = [
|
||||||
|
CIRCLE_THREE_POINT_HANDLE1,
|
||||||
|
CIRCLE_THREE_POINT_HANDLE2,
|
||||||
|
CIRCLE_THREE_POINT_HANDLE3,
|
||||||
|
].map((handle) => group.getObjectByName(handle) as Group)
|
||||||
|
handles.forEach((handle, i) => {
|
||||||
|
const point = points[i]
|
||||||
|
console.log('point', point, handle)
|
||||||
|
if (handle && point) {
|
||||||
|
handle.position.set(point[0], point[1], 0)
|
||||||
|
handle.scale.set(scale, scale, scale)
|
||||||
|
handle.visible = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const pxLength = (2 * radius * Math.PI) / scale
|
||||||
|
const shouldHideIdle = pxLength < HIDE_SEGMENT_LENGTH
|
||||||
|
const shouldHideHover = pxLength < HIDE_HOVER_SEGMENT_LENGTH
|
||||||
|
|
||||||
|
const hoveredParent =
|
||||||
|
sceneInfra.hoveredObject &&
|
||||||
|
getParentGroup(sceneInfra.hoveredObject, [CIRCLE_SEGMENT])
|
||||||
|
let isHandlesVisible = !shouldHideIdle
|
||||||
|
if (hoveredParent && hoveredParent?.uuid === group?.uuid) {
|
||||||
|
isHandlesVisible = !shouldHideHover
|
||||||
|
}
|
||||||
|
|
||||||
|
const circleSegmentBody = group.children.find(
|
||||||
|
(child) => child.userData.type === CIRCLE_THREE_POINT_SEGMENT_BODY
|
||||||
|
) as Mesh
|
||||||
|
|
||||||
|
if (circleSegmentBody) {
|
||||||
|
const newGeo = createArcGeometry({
|
||||||
|
radius,
|
||||||
|
center,
|
||||||
|
startAngle: 0,
|
||||||
|
endAngle: Math.PI * 2,
|
||||||
|
ccw: true,
|
||||||
|
scale,
|
||||||
|
})
|
||||||
|
circleSegmentBody.geometry = newGeo
|
||||||
|
}
|
||||||
|
const circleSegmentBodyDashed = group.getObjectByName(
|
||||||
|
CIRCLE_THREE_POINT_SEGMENT_DASH
|
||||||
|
)
|
||||||
|
if (circleSegmentBodyDashed instanceof Mesh) {
|
||||||
|
// consider throttling the whole updateTangentialArcToSegment
|
||||||
|
// if there are more perf considerations going forward
|
||||||
|
circleSegmentBodyDashed.geometry = createArcGeometry({
|
||||||
|
center,
|
||||||
|
radius,
|
||||||
|
ccw: true,
|
||||||
|
// make the start end where the handle is
|
||||||
|
startAngle: Math.PI * 0.25,
|
||||||
|
endAngle: Math.PI * 2.25,
|
||||||
|
isDashed: true,
|
||||||
|
scale,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return () =>
|
||||||
|
sceneInfra.updateOverlayDetails({
|
||||||
|
arrowGroup: {} as any,
|
||||||
|
group,
|
||||||
|
isHandlesVisible,
|
||||||
|
from: [0, 0],
|
||||||
|
to: [center[0], center[1]],
|
||||||
|
angle: Math.PI / 4,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function createProfileStartHandle({
|
export function createProfileStartHandle({
|
||||||
from,
|
from,
|
||||||
isDraft = false,
|
isDraft = false,
|
||||||
@ -775,6 +972,32 @@ function createCircleCenterHandle(
|
|||||||
circleCenterGroup.scale.set(scale, scale, scale)
|
circleCenterGroup.scale.set(scale, scale, scale)
|
||||||
return circleCenterGroup
|
return circleCenterGroup
|
||||||
}
|
}
|
||||||
|
function createCircleThreePointHandle(
|
||||||
|
scale = 1,
|
||||||
|
theme: Themes,
|
||||||
|
name:
|
||||||
|
| 'circle-three-point-handle1'
|
||||||
|
| 'circle-three-point-handle2'
|
||||||
|
| 'circle-three-point-handle3',
|
||||||
|
color?: number
|
||||||
|
): Group {
|
||||||
|
const circleCenterGroup = new Group()
|
||||||
|
|
||||||
|
const geometry = new BoxGeometry(12, 12, 12) // in pixels scaled later
|
||||||
|
const baseColor = getThemeColorForThreeJs(theme)
|
||||||
|
const body = new MeshBasicMaterial({ color })
|
||||||
|
const mesh = new Mesh(geometry, body)
|
||||||
|
|
||||||
|
circleCenterGroup.add(mesh)
|
||||||
|
|
||||||
|
circleCenterGroup.userData = {
|
||||||
|
type: name,
|
||||||
|
baseColor,
|
||||||
|
}
|
||||||
|
circleCenterGroup.name = name
|
||||||
|
circleCenterGroup.scale.set(scale, scale, scale)
|
||||||
|
return circleCenterGroup
|
||||||
|
}
|
||||||
|
|
||||||
function createExtraSegmentHandle(
|
function createExtraSegmentHandle(
|
||||||
scale: number,
|
scale: number,
|
||||||
@ -1101,4 +1324,5 @@ export const segmentUtils = {
|
|||||||
straight: new StraightSegment(),
|
straight: new StraightSegment(),
|
||||||
tangentialArcTo: new TangentialArcToSegment(),
|
tangentialArcTo: new TangentialArcToSegment(),
|
||||||
circle: new CircleSegment(),
|
circle: new CircleSegment(),
|
||||||
|
circleThreePoint: new CircleThreePointSegment(),
|
||||||
} as const
|
} as const
|
||||||
|
@ -300,7 +300,9 @@ export const FileMachineProvider = ({
|
|||||||
async (data) => {
|
async (data) => {
|
||||||
if (data.method === 'overwrite') {
|
if (data.method === 'overwrite') {
|
||||||
codeManager.updateCodeStateEditor(data.code)
|
codeManager.updateCodeStateEditor(data.code)
|
||||||
await kclManager.executeCode(true)
|
await kclManager.executeCode({
|
||||||
|
zoomToFit: true,
|
||||||
|
})
|
||||||
await codeManager.writeToFile()
|
await codeManager.writeToFile()
|
||||||
} else if (data.method === 'newFile' && isDesktop()) {
|
} else if (data.method === 'newFile' && isDesktop()) {
|
||||||
send({
|
send({
|
||||||
|
@ -1306,6 +1306,29 @@ export const ModelingMachineProvider = ({
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
'set-up-draft-circle-three-point': fromPromise(
|
||||||
|
async ({ input: { sketchDetails, data } }) => {
|
||||||
|
if (!sketchDetails || !data)
|
||||||
|
return reject('No sketch details or data')
|
||||||
|
await sceneEntitiesManager.tearDownSketch({ removeAxis: false })
|
||||||
|
|
||||||
|
const result =
|
||||||
|
await sceneEntitiesManager.setupDraftCircleThreePoint(
|
||||||
|
sketchDetails.sketchEntryNodePath,
|
||||||
|
sketchDetails.sketchNodePaths,
|
||||||
|
sketchDetails.planeNodePath,
|
||||||
|
sketchDetails.zAxis,
|
||||||
|
sketchDetails.yAxis,
|
||||||
|
sketchDetails.origin,
|
||||||
|
data.p1,
|
||||||
|
data.p2
|
||||||
|
)
|
||||||
|
if (err(result)) return reject(result)
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
),
|
||||||
'set-up-draft-rectangle': fromPromise(
|
'set-up-draft-rectangle': fromPromise(
|
||||||
async ({ input: { sketchDetails, data } }) => {
|
async ({ input: { sketchDetails, data } }) => {
|
||||||
if (!sketchDetails || !data)
|
if (!sketchDetails || !data)
|
||||||
@ -1355,7 +1378,7 @@ export const ModelingMachineProvider = ({
|
|||||||
sceneEntitiesManager.tearDownSketch({ removeAxis: false })
|
sceneEntitiesManager.tearDownSketch({ removeAxis: false })
|
||||||
}
|
}
|
||||||
sceneInfra.resetMouseListeners()
|
sceneInfra.resetMouseListeners()
|
||||||
const { sketchTools } = await sceneEntitiesManager.setupSketch({
|
await sceneEntitiesManager.setupSketch({
|
||||||
sketchEntryNodePath: sketchDetails?.sketchEntryNodePath || [],
|
sketchEntryNodePath: sketchDetails?.sketchEntryNodePath || [],
|
||||||
sketchNodePaths: sketchDetails.sketchNodePaths,
|
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||||
forward: sketchDetails.zAxis,
|
forward: sketchDetails.zAxis,
|
||||||
|
@ -496,7 +496,7 @@ export class KclManager {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomToFit = this.tryToZoomToFitOnCodeUpdate(ast, opts?.zoomToFit)
|
// zoomToFit = this.tryToZoomToFitOnCodeUpdate(ast, opts?.zoomToFit)
|
||||||
|
|
||||||
this.ast = { ...ast }
|
this.ast = { ...ast }
|
||||||
return this.executeAst(opts)
|
return this.executeAst(opts)
|
||||||
|
@ -20,6 +20,7 @@ import {
|
|||||||
SourceRange,
|
SourceRange,
|
||||||
sketchFromKclValue,
|
sketchFromKclValue,
|
||||||
isPathToNodeNumber,
|
isPathToNodeNumber,
|
||||||
|
parse,
|
||||||
} from './wasm'
|
} from './wasm'
|
||||||
import {
|
import {
|
||||||
isNodeSafeToReplacePath,
|
isNodeSafeToReplacePath,
|
||||||
@ -49,6 +50,7 @@ import { ExtrudeFacePlane } from 'machines/modelingMachine'
|
|||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
import { KclExpressionWithVariable } from 'lib/commandTypes'
|
import { KclExpressionWithVariable } from 'lib/commandTypes'
|
||||||
import { Artifact, getPathsFromArtifact } from './std/artifactGraph'
|
import { Artifact, getPathsFromArtifact } from './std/artifactGraph'
|
||||||
|
import { BodyItem } from 'wasm-lib/kcl/bindings/BodyItem'
|
||||||
|
|
||||||
export function startSketchOnDefault(
|
export function startSketchOnDefault(
|
||||||
node: Node<Program>,
|
node: Node<Program>,
|
||||||
@ -316,7 +318,6 @@ export function extrudeSketch(
|
|||||||
const lastSketchNodePath =
|
const lastSketchNodePath =
|
||||||
orderedSketchNodePaths[orderedSketchNodePaths.length - 1]
|
orderedSketchNodePaths[orderedSketchNodePaths.length - 1]
|
||||||
|
|
||||||
console.log('lastSketchNodePath', lastSketchNodePath, orderedSketchNodePaths)
|
|
||||||
const sketchIndexInBody = Number(lastSketchNodePath[1][0])
|
const sketchIndexInBody = Number(lastSketchNodePath[1][0])
|
||||||
_node.body.splice(sketchIndexInBody + 1, 0, VariableDeclaration)
|
_node.body.splice(sketchIndexInBody + 1, 0, VariableDeclaration)
|
||||||
|
|
||||||
@ -1508,3 +1509,19 @@ export function splitPipedProfile(
|
|||||||
pathToPlane,
|
pathToPlane,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createNodeFromExprSnippet(
|
||||||
|
strings: TemplateStringsArray,
|
||||||
|
...expressions: any[]
|
||||||
|
): Node<BodyItem> | Error {
|
||||||
|
const code = strings.reduce(
|
||||||
|
(acc, str, i) => acc + str + (expressions[i] || ''),
|
||||||
|
''
|
||||||
|
)
|
||||||
|
let program = parse(code)
|
||||||
|
if (err(program)) return program
|
||||||
|
console.log('code', code, program)
|
||||||
|
const node = program.program?.body[0]
|
||||||
|
if (!node) return new Error('No node found')
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
@ -2091,7 +2091,7 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
ast: Node<Program>,
|
ast: Node<Program>,
|
||||||
artifactCommands: ArtifactCommand[],
|
artifactCommands: ArtifactCommand[],
|
||||||
execStateArtifacts: ExecState['artifacts'],
|
execStateArtifacts: ExecState['artifacts'],
|
||||||
isPartialExecution?: true
|
isPartialExecution?: boolean
|
||||||
) {
|
) {
|
||||||
const newGraphArtifacts = createArtifactGraph({
|
const newGraphArtifacts = createArtifactGraph({
|
||||||
artifactCommands,
|
artifactCommands,
|
||||||
|
@ -61,6 +61,9 @@ const STRAIGHT_SEGMENT_ERR = new Error(
|
|||||||
'Invalid input, expected "straight-segment"'
|
'Invalid input, expected "straight-segment"'
|
||||||
)
|
)
|
||||||
const ARC_SEGMENT_ERR = new Error('Invalid input, expected "arc-segment"')
|
const ARC_SEGMENT_ERR = new Error('Invalid input, expected "arc-segment"')
|
||||||
|
const CIRCLE_THREE_POINT_SEGMENT_ERR = new Error(
|
||||||
|
'Invalid input, expected "circle-three-point-segment"'
|
||||||
|
)
|
||||||
|
|
||||||
export type Coords2d = [number, number]
|
export type Coords2d = [number, number]
|
||||||
|
|
||||||
@ -1130,6 +1133,287 @@ export const circle: SketchLineHelper = {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
export const circleThreePoint: SketchLineHelper = {
|
||||||
|
add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
|
||||||
|
if (segmentInput.type !== 'circle-three-point-segment')
|
||||||
|
return CIRCLE_THREE_POINT_SEGMENT_ERR
|
||||||
|
|
||||||
|
const { p1, p2, p3 } = segmentInput
|
||||||
|
const _node = { ...node }
|
||||||
|
const nodeMeta = getNodeFromPath<PipeExpression>(
|
||||||
|
_node,
|
||||||
|
pathToNode,
|
||||||
|
'PipeExpression'
|
||||||
|
)
|
||||||
|
if (err(nodeMeta)) return nodeMeta
|
||||||
|
|
||||||
|
const { node: pipe } = nodeMeta
|
||||||
|
|
||||||
|
const createRoundedLiteral = (val: number) =>
|
||||||
|
createLiteral(roundOff(val, 2))
|
||||||
|
if (replaceExistingCallback) {
|
||||||
|
const result = replaceExistingCallback([
|
||||||
|
{
|
||||||
|
type: 'arrayInObject',
|
||||||
|
index: 0,
|
||||||
|
key: 'p1',
|
||||||
|
argType: 'xAbsolute',
|
||||||
|
expr: createRoundedLiteral(p1[0]),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'arrayInObject',
|
||||||
|
index: 1,
|
||||||
|
key: 'p1',
|
||||||
|
argType: 'yAbsolute',
|
||||||
|
expr: createRoundedLiteral(p1[1]),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'arrayInObject',
|
||||||
|
index: 0,
|
||||||
|
key: 'p2',
|
||||||
|
argType: 'xAbsolute',
|
||||||
|
expr: createRoundedLiteral(p2[0]),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'arrayInObject',
|
||||||
|
index: 1,
|
||||||
|
key: 'p2',
|
||||||
|
argType: 'yAbsolute',
|
||||||
|
expr: createRoundedLiteral(p2[1]),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'arrayInObject',
|
||||||
|
index: 0,
|
||||||
|
key: 'p3',
|
||||||
|
argType: 'xAbsolute',
|
||||||
|
expr: createRoundedLiteral(p3[0]),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'arrayInObject',
|
||||||
|
index: 1,
|
||||||
|
key: 'p3',
|
||||||
|
argType: 'yAbsolute',
|
||||||
|
expr: createRoundedLiteral(p3[1]),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
if (err(result)) return result
|
||||||
|
const { callExp, valueUsedInTransform } = result
|
||||||
|
|
||||||
|
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
|
||||||
|
pipe.body[callIndex] = callExp
|
||||||
|
|
||||||
|
return {
|
||||||
|
modifiedAst: _node,
|
||||||
|
pathToNode,
|
||||||
|
valueUsedInTransform,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Error('not implemented')
|
||||||
|
},
|
||||||
|
updateArgs: ({ node, pathToNode, input }) => {
|
||||||
|
if (input.type !== 'circle-three-point-segment')
|
||||||
|
return CIRCLE_THREE_POINT_SEGMENT_ERR
|
||||||
|
const { p1, p2, p3 } = input
|
||||||
|
const _node = { ...node }
|
||||||
|
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
|
||||||
|
if (err(nodeMeta)) return nodeMeta
|
||||||
|
|
||||||
|
const { node: callExpression, shallowPath } = nodeMeta
|
||||||
|
const createRounded2DPointArr = (point: [number, number]) =>
|
||||||
|
createArrayExpression([
|
||||||
|
createLiteral(roundOff(point[0], 2)),
|
||||||
|
createLiteral(roundOff(point[1], 2)),
|
||||||
|
])
|
||||||
|
|
||||||
|
const firstArg = callExpression.arguments?.[0]
|
||||||
|
const newP1 = createRounded2DPointArr(p1)
|
||||||
|
const newP2 = createRounded2DPointArr(p2)
|
||||||
|
const newP3 = createRounded2DPointArr(p3)
|
||||||
|
mutateObjExpProp(firstArg, newP1, 'p1')
|
||||||
|
mutateObjExpProp(firstArg, newP2, 'p2')
|
||||||
|
mutateObjExpProp(firstArg, newP3, 'p3')
|
||||||
|
|
||||||
|
return {
|
||||||
|
modifiedAst: _node,
|
||||||
|
pathToNode: shallowPath,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getTag: getTag(),
|
||||||
|
addTag: addTag(),
|
||||||
|
getConstraintInfo: (callExp: CallExpression, code, pathToNode) => {
|
||||||
|
if (callExp.type !== 'CallExpression') return []
|
||||||
|
const firstArg = callExp.arguments?.[0]
|
||||||
|
if (firstArg.type !== 'ObjectExpression') return []
|
||||||
|
const p1Details = getObjExprProperty(firstArg, 'p1')
|
||||||
|
const p2Details = getObjExprProperty(firstArg, 'p2')
|
||||||
|
const p3Details = getObjExprProperty(firstArg, 'p3')
|
||||||
|
if (!p1Details || !p2Details || !p3Details) return []
|
||||||
|
if (
|
||||||
|
p1Details.expr.type !== 'ArrayExpression' ||
|
||||||
|
p2Details.expr.type !== 'ArrayExpression' ||
|
||||||
|
p3Details.expr.type !== 'ArrayExpression'
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
|
||||||
|
const pathToP1ArrayExpression: PathToNode = [
|
||||||
|
...pathToNode,
|
||||||
|
['arguments', 'CallExpression'],
|
||||||
|
[0, 'index'],
|
||||||
|
['properties', 'ObjectExpression'],
|
||||||
|
[p1Details.index, 'index'],
|
||||||
|
['value', 'Property'],
|
||||||
|
['elements', 'ArrayExpression'],
|
||||||
|
]
|
||||||
|
const pathToP2ArrayExpression: PathToNode = [
|
||||||
|
...pathToNode,
|
||||||
|
['arguments', 'CallExpression'],
|
||||||
|
[0, 'index'],
|
||||||
|
['properties', 'ObjectExpression'],
|
||||||
|
[p2Details.index, 'index'],
|
||||||
|
['value', 'Property'],
|
||||||
|
['elements', 'ArrayExpression'],
|
||||||
|
]
|
||||||
|
const pathToP3ArrayExpression: PathToNode = [
|
||||||
|
...pathToNode,
|
||||||
|
['arguments', 'CallExpression'],
|
||||||
|
[0, 'index'],
|
||||||
|
['properties', 'ObjectExpression'],
|
||||||
|
[p3Details.index, 'index'],
|
||||||
|
['value', 'Property'],
|
||||||
|
['elements', 'ArrayExpression'],
|
||||||
|
]
|
||||||
|
|
||||||
|
const pathToP1XArg: PathToNode = [...pathToP1ArrayExpression, [0, 'index']]
|
||||||
|
const pathToP1YArg: PathToNode = [...pathToP1ArrayExpression, [1, 'index']]
|
||||||
|
const pathToP2XArg: PathToNode = [...pathToP2ArrayExpression, [0, 'index']]
|
||||||
|
const pathToP2YArg: PathToNode = [...pathToP2ArrayExpression, [1, 'index']]
|
||||||
|
const pathToP3XArg: PathToNode = [...pathToP3ArrayExpression, [0, 'index']]
|
||||||
|
const pathToP3YArg: PathToNode = [...pathToP3ArrayExpression, [1, 'index']]
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
stdLibFnName: 'circle',
|
||||||
|
type: 'xAbsolute',
|
||||||
|
isConstrained: isNotLiteralArrayOrStatic(p1Details.expr.elements[0]),
|
||||||
|
sourceRange: [
|
||||||
|
p1Details.expr.elements[0].start,
|
||||||
|
p1Details.expr.elements[0].end,
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
pathToNode: pathToP1XArg,
|
||||||
|
value: code.slice(
|
||||||
|
p1Details.expr.elements[0].start,
|
||||||
|
p1Details.expr.elements[0].end
|
||||||
|
),
|
||||||
|
argPosition: {
|
||||||
|
type: 'arrayInObject',
|
||||||
|
index: 0,
|
||||||
|
key: 'p1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stdLibFnName: 'circle',
|
||||||
|
type: 'yAbsolute',
|
||||||
|
isConstrained: isNotLiteralArrayOrStatic(p1Details.expr.elements[1]),
|
||||||
|
sourceRange: [
|
||||||
|
p1Details.expr.elements[1].start,
|
||||||
|
p1Details.expr.elements[1].end,
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
pathToNode: pathToP1YArg,
|
||||||
|
value: code.slice(
|
||||||
|
p1Details.expr.elements[1].start,
|
||||||
|
p1Details.expr.elements[1].end
|
||||||
|
),
|
||||||
|
argPosition: {
|
||||||
|
type: 'arrayInObject',
|
||||||
|
index: 1,
|
||||||
|
key: 'p1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stdLibFnName: 'circle',
|
||||||
|
type: 'xAbsolute',
|
||||||
|
isConstrained: isNotLiteralArrayOrStatic(p2Details.expr.elements[0]),
|
||||||
|
sourceRange: [
|
||||||
|
p2Details.expr.elements[0].start,
|
||||||
|
p2Details.expr.elements[0].end,
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
pathToNode: pathToP2XArg,
|
||||||
|
value: code.slice(
|
||||||
|
p2Details.expr.elements[0].start,
|
||||||
|
p2Details.expr.elements[0].end
|
||||||
|
),
|
||||||
|
argPosition: {
|
||||||
|
type: 'arrayInObject',
|
||||||
|
index: 0,
|
||||||
|
key: 'p2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stdLibFnName: 'circle',
|
||||||
|
type: 'yAbsolute',
|
||||||
|
isConstrained: isNotLiteralArrayOrStatic(p2Details.expr.elements[1]),
|
||||||
|
sourceRange: [
|
||||||
|
p2Details.expr.elements[1].start,
|
||||||
|
p2Details.expr.elements[1].end,
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
pathToNode: pathToP2YArg,
|
||||||
|
value: code.slice(
|
||||||
|
p2Details.expr.elements[1].start,
|
||||||
|
p2Details.expr.elements[1].end
|
||||||
|
),
|
||||||
|
argPosition: {
|
||||||
|
type: 'arrayInObject',
|
||||||
|
index: 1,
|
||||||
|
key: 'p2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stdLibFnName: 'circle',
|
||||||
|
type: 'xAbsolute',
|
||||||
|
isConstrained: isNotLiteralArrayOrStatic(p3Details.expr.elements[0]),
|
||||||
|
sourceRange: [
|
||||||
|
p3Details.expr.elements[0].start,
|
||||||
|
p3Details.expr.elements[0].end,
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
pathToNode: pathToP3XArg,
|
||||||
|
value: code.slice(
|
||||||
|
p3Details.expr.elements[0].start,
|
||||||
|
p3Details.expr.elements[0].end
|
||||||
|
),
|
||||||
|
argPosition: {
|
||||||
|
type: 'arrayInObject',
|
||||||
|
index: 0,
|
||||||
|
key: 'p3',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stdLibFnName: 'circle',
|
||||||
|
type: 'yAbsolute',
|
||||||
|
isConstrained: isNotLiteralArrayOrStatic(p3Details.expr.elements[1]),
|
||||||
|
sourceRange: [
|
||||||
|
p3Details.expr.elements[1].start,
|
||||||
|
p3Details.expr.elements[1].end,
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
pathToNode: pathToP3YArg,
|
||||||
|
value: code.slice(
|
||||||
|
p3Details.expr.elements[1].start,
|
||||||
|
p3Details.expr.elements[1].end
|
||||||
|
),
|
||||||
|
argPosition: {
|
||||||
|
type: 'arrayInObject',
|
||||||
|
index: 1,
|
||||||
|
key: 'p3',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
export const angledLine: SketchLineHelper = {
|
export const angledLine: SketchLineHelper = {
|
||||||
add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
|
add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
|
||||||
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
|
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
|
||||||
@ -1898,6 +2182,7 @@ export const sketchLineHelperMap: { [key: string]: SketchLineHelper } = {
|
|||||||
angledLineThatIntersects,
|
angledLineThatIntersects,
|
||||||
tangentialArcTo,
|
tangentialArcTo,
|
||||||
circle,
|
circle,
|
||||||
|
circleThreePoint,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export function changeSketchArguments(
|
export function changeSketchArguments(
|
||||||
|
@ -44,6 +44,13 @@ interface ArcSegmentInput {
|
|||||||
center: [number, number]
|
center: [number, number]
|
||||||
radius: number
|
radius: number
|
||||||
}
|
}
|
||||||
|
/** Inputs for three point circle */
|
||||||
|
interface CircleThreePointSegmentInput {
|
||||||
|
type: 'circle-three-point-segment'
|
||||||
|
p1: [number, number]
|
||||||
|
p2: [number, number]
|
||||||
|
p3: [number, number]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SegmentInputs is a union type that can be either a StraightSegmentInput or an ArcSegmentInput.
|
* SegmentInputs is a union type that can be either a StraightSegmentInput or an ArcSegmentInput.
|
||||||
@ -51,7 +58,10 @@ interface ArcSegmentInput {
|
|||||||
* - StraightSegmentInput: Represents a straight segment with a starting point (from) and an ending point (to).
|
* - StraightSegmentInput: Represents a straight segment with a starting point (from) and an ending point (to).
|
||||||
* - ArcSegmentInput: Represents an arc segment with a starting point (from), a center point, and a radius.
|
* - ArcSegmentInput: Represents an arc segment with a starting point (from), a center point, and a radius.
|
||||||
*/
|
*/
|
||||||
export type SegmentInputs = StraightSegmentInput | ArcSegmentInput
|
export type SegmentInputs =
|
||||||
|
| StraightSegmentInput
|
||||||
|
| ArcSegmentInput
|
||||||
|
| CircleThreePointSegmentInput
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for adding or replacing a sketch stblib call expression to a sketch.
|
* Interface for adding or replacing a sketch stblib call expression to a sketch.
|
||||||
@ -84,6 +94,9 @@ export type InputArgKeys =
|
|||||||
| 'intersectTag'
|
| 'intersectTag'
|
||||||
| 'radius'
|
| 'radius'
|
||||||
| 'center'
|
| 'center'
|
||||||
|
| 'p1'
|
||||||
|
| 'p2'
|
||||||
|
| 'p3'
|
||||||
export interface SingleValueInput<T> {
|
export interface SingleValueInput<T> {
|
||||||
type: 'singleValue'
|
type: 'singleValue'
|
||||||
argType: LineInputsType
|
argType: LineInputsType
|
||||||
|
@ -449,7 +449,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
disabled: (state) => state.matches('Sketch no face'),
|
disabled: (state) => state.matches('Sketch no face'),
|
||||||
isActive: (state) =>
|
isActive: (state) =>
|
||||||
state.matches({ Sketch: 'Circle tool' }) ||
|
state.matches({ Sketch: 'Circle tool' }) ||
|
||||||
state.matches({ Sketch: 'circle3PointToolSelect' }),
|
state.matches({ Sketch: 'Circle three point tool' }),
|
||||||
hotkey: (state) =>
|
hotkey: (state) =>
|
||||||
state.matches({ Sketch: 'Circle tool' }) ? ['Esc', 'C'] : 'C',
|
state.matches({ Sketch: 'Circle tool' }) ? ['Esc', 'C'] : 'C',
|
||||||
showTitle: false,
|
showTitle: false,
|
||||||
@ -463,9 +463,9 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
type: 'change tool',
|
type: 'change tool',
|
||||||
data: {
|
data: {
|
||||||
tool: !modelingState.matches({
|
tool: !modelingState.matches({
|
||||||
Sketch: 'circleThreePointToolSelect',
|
Sketch: 'Circle three point tool',
|
||||||
})
|
})
|
||||||
? 'circleThreePoint'
|
? 'circleThreePointNeo'
|
||||||
: 'none',
|
: 'none',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user