WIP
This commit is contained in:
436
src/clientSideScene/circleThreePoint.ts
Normal file
436
src/clientSideScene/circleThreePoint.ts
Normal file
@ -0,0 +1,436 @@
|
|||||||
|
import {
|
||||||
|
Group,
|
||||||
|
Mesh,
|
||||||
|
Vector3,
|
||||||
|
Vector2,
|
||||||
|
Object3D,
|
||||||
|
SphereGeometry,
|
||||||
|
MeshBasicMaterial,
|
||||||
|
Color,
|
||||||
|
BufferGeometry,
|
||||||
|
LineDashedMaterial,
|
||||||
|
Line,
|
||||||
|
} 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: any
|
||||||
|
startSketchOnASTNodePath: PathToNode
|
||||||
|
maybeExistingNodePath: PathToNode
|
||||||
|
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.
|
||||||
|
const selfASTNode = parse(`profileVarNameToBeReplaced = circleThreePoint(
|
||||||
|
sketchVarNameToBeReplaced,
|
||||||
|
p1 = [0.0, 0.0],
|
||||||
|
p2 = [0.0, 0.0],
|
||||||
|
p3 = [0.0, 0.0],
|
||||||
|
)`)
|
||||||
|
|
||||||
|
// AST node to work with. It's either an existing one, or a new one.
|
||||||
|
|
||||||
|
let isNewSketch = true
|
||||||
|
const astSnapshot = structuredClone(kclManager.ast)
|
||||||
|
|
||||||
|
if (initArgs.maybeExistingNodePath.length === 0) {
|
||||||
|
// Travel 1 node up from the sketch plane AST node, and append or
|
||||||
|
// update the new profile AST node.
|
||||||
|
|
||||||
|
// 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']
|
||||||
|
// [8, 'index'] <- ...[1]?.[0] refers to 8.
|
||||||
|
const nextIndex = initArgs.startSketchOnASTNodePath[
|
||||||
|
// - 3 puts us at the body of a function or the overall program
|
||||||
|
initArgs.startSketchOnASTNodePath.length - 3
|
||||||
|
][0] + 1
|
||||||
|
|
||||||
|
const bodyASTNode = getNodeFromPath<VariableDeclaration>(
|
||||||
|
astSnapshot,
|
||||||
|
Array.from(initArgs.startSketchOnASTNodePath).splice(
|
||||||
|
0,
|
||||||
|
initArgs.startSketchOnASTNodePath.length - 3
|
||||||
|
),
|
||||||
|
'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])
|
||||||
|
} else {
|
||||||
|
selfASTNode = getNodeFromPath<VariableDeclaration>(
|
||||||
|
kclManager.ast,
|
||||||
|
initArgs.maybeExistingNodePath,
|
||||||
|
'VariableDeclaration'
|
||||||
|
)
|
||||||
|
if (err(selfASTNode)) return new NoOpTool()
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
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!
|
||||||
|
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,
|
||||||
|
selfASTNode,
|
||||||
|
'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)
|
||||||
|
initArgs.done()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
src/clientSideScene/interfaceSketchTool.ts
Normal file
30
src/clientSideScene/interfaceSketchTool.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
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
|
||||||
|
}
|
@ -36,8 +36,6 @@ import {
|
|||||||
SKETCH_LAYER,
|
SKETCH_LAYER,
|
||||||
X_AXIS,
|
X_AXIS,
|
||||||
Y_AXIS,
|
Y_AXIS,
|
||||||
CIRCLE_3_POINT_DRAFT_POINT,
|
|
||||||
CIRCLE_3_POINT_DRAFT_CIRCLE,
|
|
||||||
} from './sceneInfra'
|
} from './sceneInfra'
|
||||||
import { isQuaternionVertical, quaternionFromUpNForward } from './helpers'
|
import { isQuaternionVertical, quaternionFromUpNForward } from './helpers'
|
||||||
import {
|
import {
|
||||||
@ -58,7 +56,6 @@ import {
|
|||||||
resultIsOk,
|
resultIsOk,
|
||||||
SourceRange,
|
SourceRange,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import { calculate_circle_from_3_points } from '../wasm-lib/pkg/wasm_lib'
|
|
||||||
import {
|
import {
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
kclManager,
|
kclManager,
|
||||||
@ -74,6 +71,7 @@ import {
|
|||||||
SegmentUtils,
|
SegmentUtils,
|
||||||
segmentUtils,
|
segmentUtils,
|
||||||
} from './segments'
|
} from './segments'
|
||||||
|
import { CircleThreePoint } from './circleThreePoint'
|
||||||
import {
|
import {
|
||||||
addCallExpressionsToPipe,
|
addCallExpressionsToPipe,
|
||||||
addCloseToPipe,
|
addCloseToPipe,
|
||||||
@ -163,6 +161,7 @@ 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[] = []
|
||||||
@ -585,6 +584,8 @@ 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)
|
||||||
@ -669,6 +670,20 @@ 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: this.scene,
|
||||||
|
intersectionPlane: this.intersectionPlane,
|
||||||
|
startSketchOnASTNodePath: segPathToNode,
|
||||||
|
forward,
|
||||||
|
up,
|
||||||
|
sketchOrigin: position,
|
||||||
|
})
|
||||||
|
circleThreePoint.init()
|
||||||
|
this.sketchTools.push(circleThreePoint)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const initSegment =
|
const initSegment =
|
||||||
segment.type === 'TangentialArcTo'
|
segment.type === 'TangentialArcTo'
|
||||||
? segmentUtils.tangentialArcTo.init
|
? segmentUtils.tangentialArcTo.init
|
||||||
@ -1381,349 +1396,6 @@ export class SceneEntities {
|
|||||||
})
|
})
|
||||||
return { updatedEntryNodePath, updatedSketchNodePaths }
|
return { updatedEntryNodePath, updatedSketchNodePaths }
|
||||||
}
|
}
|
||||||
|
|
||||||
// lee: Well, it appears all our code in sceneEntities each act as their own
|
|
||||||
// kind of classes. In this case, I'll keep utility functions pertaining to
|
|
||||||
// circle3Point here. Feel free to extract as needed.
|
|
||||||
entryDraftCircle3Point = (
|
|
||||||
done: () => void,
|
|
||||||
startSketchOnASTNodePath: PathToNode,
|
|
||||||
forward: Vector3,
|
|
||||||
up: Vector3,
|
|
||||||
sketchOrigin: Vector3
|
|
||||||
): (() => void) => {
|
|
||||||
// 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)
|
|
||||||
|
|
||||||
const orientation = quaternionFromUpNForward(up, 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.
|
|
||||||
this.intersectionPlane!.setRotationFromQuaternion(orientation)
|
|
||||||
this.intersectionPlane!.position.copy(sketchOrigin)
|
|
||||||
|
|
||||||
// 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.position.copy(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!
|
|
||||||
groupOfDrafts.setRotationFromQuaternion(orientation)
|
|
||||||
this.scene.add(groupOfDrafts)
|
|
||||||
|
|
||||||
// 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
|
|
||||||
|
|
||||||
// The KCL this will generate.
|
|
||||||
const kclCircle3Point = parse(`profileVarNameToBeReplaced = circleThreePoint(
|
|
||||||
sketchVarNameToBeReplaced,
|
|
||||||
p1 = [0.0, 0.0],
|
|
||||||
p2 = [0.0, 0.0],
|
|
||||||
p3 = [0.0, 0.0],
|
|
||||||
)`)
|
|
||||||
|
|
||||||
const createPoint = (
|
|
||||||
center: Vector3,
|
|
||||||
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 createCircle3PointGraphic = 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)
|
|
||||||
// devnote: it's a mistake to use these with EllipseCurve :)
|
|
||||||
// lineCircle.position.set(center.x, center.y, 0)
|
|
||||||
// lineCircle.scale.set(scale, scale, scale)
|
|
||||||
|
|
||||||
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 insertCircle3PointKclIntoAstSnapshot = (
|
|
||||||
points: Vector2[],
|
|
||||||
sketchVarName: string
|
|
||||||
): Program => {
|
|
||||||
if (err(kclCircle3Point) || kclCircle3Point.program === null)
|
|
||||||
return kclManager.ast
|
|
||||||
if (kclCircle3Point.program.body[0].type !== 'VariableDeclaration')
|
|
||||||
return kclManager.ast
|
|
||||||
if (
|
|
||||||
kclCircle3Point.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 (kclCircle3Point.program.body[0].declaration.id.name === 'profileVarNameToBeReplaced') {
|
|
||||||
const profileVarName = findUniqueName(_ast, 'profile')
|
|
||||||
kclCircle3Point.program.body[0].declaration.id.name = profileVarName
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the sketch variable name
|
|
||||||
kclCircle3Point.program.body[0].declaration.init.unlabeled.name = sketchVarName
|
|
||||||
|
|
||||||
// Set the points 1-3
|
|
||||||
const kclCircle3PointArgs =
|
|
||||||
kclCircle3Point.program.body[0].declaration.init.arguments
|
|
||||||
|
|
||||||
const arg0 = arg(kclCircle3PointArgs[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(kclCircle3PointArgs[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(kclCircle3PointArgs[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()
|
|
||||||
|
|
||||||
const astSnapshot = structuredClone(kclManager.ast)
|
|
||||||
const startSketchOnASTNode = getNodeFromPath<VariableDeclaration>(
|
|
||||||
astSnapshot,
|
|
||||||
startSketchOnASTNodePath,
|
|
||||||
'VariableDeclaration'
|
|
||||||
)
|
|
||||||
if (err(startSketchOnASTNode)) return astSnapshot
|
|
||||||
|
|
||||||
// It's possible we're not the first profile on this sketch!
|
|
||||||
// It's also possible we've already added this profile, so modify it.
|
|
||||||
if (
|
|
||||||
startSketchOnASTNode.node.declaration.init.type === 'PipeExpression' &&
|
|
||||||
startSketchOnASTNode.node.declaration.init.body[1].type ===
|
|
||||||
'CallExpressionKw' &&
|
|
||||||
startSketchOnASTNode.node.declaration.init.body.length >= 2
|
|
||||||
) {
|
|
||||||
startSketchOnASTNode.node.declaration.init.body[1].arguments =
|
|
||||||
kclCircle3Point.program.body[0].expression.arguments
|
|
||||||
} else {
|
|
||||||
// Clone a new node based on the old, and replace the old with the new.
|
|
||||||
const clonedStartSketchOnASTNode = structuredClone(startSketchOnASTNode)
|
|
||||||
startSketchOnASTNode.node.declaration.init = createPipeExpression([
|
|
||||||
clonedStartSketchOnASTNode.node.declaration.init,
|
|
||||||
kclCircle3Point.program.body[0].expression,
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the `Program`
|
|
||||||
return astSnapshot
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateCircle3Point = async (opts?: { execute?: true }) => {
|
|
||||||
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 createCircle3PointGraphic(
|
|
||||||
points_,
|
|
||||||
new Vector2(circleParams.center_x, circleParams.center_y),
|
|
||||||
circleParams.radius
|
|
||||||
)
|
|
||||||
const astWithNewCode = insertCircle3PointKclIntoAstSnapshot(points_)
|
|
||||||
const codeAsString = recast(astWithNewCode)
|
|
||||||
if (err(codeAsString)) return
|
|
||||||
codeManager.updateCodeStateEditor(codeAsString)
|
|
||||||
}
|
|
||||||
|
|
||||||
const cleanupFn = () => {
|
|
||||||
this.scene.remove(groupOfDrafts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The AST node we extracted earlier may already have a circleThreePoint!
|
|
||||||
// Use the points in the AST as starting points.
|
|
||||||
const astSnapshot = structuredClone(kclManager.ast)
|
|
||||||
const maybeVariableDeclaration = getNodeFromPath<VariableDeclaration>(
|
|
||||||
astSnapshot,
|
|
||||||
startSketchOnASTNodePath,
|
|
||||||
'VariableDeclaration'
|
|
||||||
)
|
|
||||||
if (err(maybeVariableDeclaration))
|
|
||||||
return () => {
|
|
||||||
done()
|
|
||||||
}
|
|
||||||
|
|
||||||
const maybeCallExpressionKw = maybeVariableDeclaration.node.declaration.init
|
|
||||||
if (
|
|
||||||
maybeCallExpressionKw.type === 'VariableDeclaration' &&
|
|
||||||
maybeCallExpressionKw.body[1].type === 'CallExpressionKw' &&
|
|
||||||
maybeCallExpressionKw.body[1]?.callee.name === 'circleThreePoint'
|
|
||||||
) {
|
|
||||||
maybeCallExpressionKw?.body[1].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 updateCircle3Point()
|
|
||||||
}
|
|
||||||
|
|
||||||
sceneInfra.setCallbacks({
|
|
||||||
async onDrag(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 updateCircle3Point()
|
|
||||||
},
|
|
||||||
async onDragEnd(_args) {
|
|
||||||
target = undefined
|
|
||||||
},
|
|
||||||
async onClick(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
|
|
||||||
|
|
||||||
await updateCircle3Point()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return cleanupFn
|
|
||||||
}
|
|
||||||
setupDraftCircle = async (
|
setupDraftCircle = async (
|
||||||
sketchEntryNodePath: PathToNode,
|
sketchEntryNodePath: PathToNode,
|
||||||
sketchNodePaths: PathToNode[],
|
sketchNodePaths: PathToNode[],
|
||||||
@ -2577,7 +2249,7 @@ function prepareTruncatedMemoryAndAst(
|
|||||||
'VariableDeclaration'
|
'VariableDeclaration'
|
||||||
)
|
)
|
||||||
if (err(_node)) return _node
|
if (err(_node)) return _node
|
||||||
const variableDeclarationName = _node.node?.declaration.id?.name || ''
|
const variableDeclarationName = _node.node?.declaration?.id?.name || ''
|
||||||
const sg = sketchFromKclValue(
|
const sg = sketchFromKclValue(
|
||||||
programMemory.get(variableDeclarationName),
|
programMemory.get(variableDeclarationName),
|
||||||
variableDeclarationName
|
variableDeclarationName
|
||||||
|
@ -1355,7 +1355,7 @@ export const ModelingMachineProvider = ({
|
|||||||
sceneEntitiesManager.tearDownSketch({ removeAxis: false })
|
sceneEntitiesManager.tearDownSketch({ removeAxis: false })
|
||||||
}
|
}
|
||||||
sceneInfra.resetMouseListeners()
|
sceneInfra.resetMouseListeners()
|
||||||
await sceneEntitiesManager.setupSketch({
|
const { sketchTools } = await sceneEntitiesManager.setupSketch({
|
||||||
sketchEntryNodePath: sketchDetails?.sketchEntryNodePath || [],
|
sketchEntryNodePath: sketchDetails?.sketchEntryNodePath || [],
|
||||||
sketchNodePaths: sketchDetails.sketchNodePaths,
|
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||||
forward: sketchDetails.zAxis,
|
forward: sketchDetails.zAxis,
|
||||||
@ -1373,7 +1373,12 @@ export const ModelingMachineProvider = ({
|
|||||||
position: sketchDetails.origin,
|
position: sketchDetails.origin,
|
||||||
sketchNodePaths: sketchDetails.sketchNodePaths,
|
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||||
planeNodePath: sketchDetails.planeNodePath,
|
planeNodePath: sketchDetails.planeNodePath,
|
||||||
|
// We will want to pass sketchTools here
|
||||||
|
// to add their interactions
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// We will want to update the context with sketchTools.
|
||||||
|
// They'll be used for their .destroy() in tearDownSketch
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
@ -446,8 +446,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
icon: 'circle',
|
icon: 'circle',
|
||||||
status: 'available',
|
status: 'available',
|
||||||
title: 'Center circle',
|
title: 'Center circle',
|
||||||
disabled: (state) =>
|
disabled: (state) => state.matches('Sketch no face'),
|
||||||
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: 'circle3PointToolSelect' }),
|
||||||
@ -464,9 +463,9 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
type: 'change tool',
|
type: 'change tool',
|
||||||
data: {
|
data: {
|
||||||
tool: !modelingState.matches({
|
tool: !modelingState.matches({
|
||||||
Sketch: 'circle3PointToolSelect',
|
Sketch: 'circleThreePointToolSelect',
|
||||||
})
|
})
|
||||||
? 'circle3Points'
|
? 'circleThreePoint'
|
||||||
: 'none',
|
: 'none',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
@ -84,6 +84,7 @@ import { MachineManager } from 'components/MachineManagerProvider'
|
|||||||
import { addShell } from 'lang/modifyAst/addShell'
|
import { addShell } from 'lang/modifyAst/addShell'
|
||||||
import { KclCommandValue } from 'lib/commandTypes'
|
import { KclCommandValue } from 'lib/commandTypes'
|
||||||
import { getPathsFromPlaneArtifact } from 'lang/std/artifactGraph'
|
import { getPathsFromPlaneArtifact } from 'lang/std/artifactGraph'
|
||||||
|
import { CircleThreePoint } from '../clientSideScene/circleThreePoint'
|
||||||
|
|
||||||
export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY'
|
export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY'
|
||||||
|
|
||||||
@ -228,7 +229,7 @@ export type SketchTool =
|
|||||||
| 'rectangle'
|
| 'rectangle'
|
||||||
| 'center rectangle'
|
| 'center rectangle'
|
||||||
| 'circle'
|
| 'circle'
|
||||||
| 'circle3Points'
|
| 'circleThreePoints'
|
||||||
| 'none'
|
| 'none'
|
||||||
|
|
||||||
export type ModelingMachineEvent =
|
export type ModelingMachineEvent =
|
||||||
@ -432,8 +433,6 @@ export const modelingMachine = setup({
|
|||||||
'has valid selection for deletion': () => false,
|
'has valid selection for deletion': () => false,
|
||||||
'is editing existing sketch': ({ context: { sketchDetails } }) =>
|
'is editing existing sketch': ({ context: { sketchDetails } }) =>
|
||||||
isEditingExistingSketch({ sketchDetails }),
|
isEditingExistingSketch({ sketchDetails }),
|
||||||
'is editing 3-point circle': ({ context: { sketchDetails } }) =>
|
|
||||||
isEditing3PointCircle({ sketchDetails }),
|
|
||||||
'Can make selection horizontal': ({ context: { selectionRanges } }) => {
|
'Can make selection horizontal': ({ context: { selectionRanges } }) => {
|
||||||
const info = horzVertInfo(selectionRanges, 'horizontal')
|
const info = horzVertInfo(selectionRanges, 'horizontal')
|
||||||
if (trap(info)) return false
|
if (trap(info)) return false
|
||||||
@ -583,8 +582,8 @@ export const modelingMachine = setup({
|
|||||||
currentTool === 'center rectangle',
|
currentTool === 'center rectangle',
|
||||||
'next is circle': ({ context: { currentTool } }) =>
|
'next is circle': ({ context: { currentTool } }) =>
|
||||||
currentTool === 'circle',
|
currentTool === 'circle',
|
||||||
'next is circle 3 point': ({ context: { currentTool } }) =>
|
'next is circle three point': ({ context: { currentTool } }) =>
|
||||||
currentTool === 'circle3Points',
|
currentTool === 'circleThreePoint',
|
||||||
'next is line': ({ context }) => context.currentTool === 'line',
|
'next is line': ({ context }) => context.currentTool === 'line',
|
||||||
'next is none': ({ context }) => context.currentTool === 'none',
|
'next is none': ({ context }) => context.currentTool === 'none',
|
||||||
},
|
},
|
||||||
@ -977,6 +976,7 @@ export const modelingMachine = setup({
|
|||||||
},
|
},
|
||||||
'update sketchDetails': assign(({ event, context }) => {
|
'update sketchDetails': assign(({ event, context }) => {
|
||||||
if (
|
if (
|
||||||
|
event.type !== 'xstate.done.actor.actor-circle-three-point' &&
|
||||||
event.type !== 'xstate.done.actor.set-up-draft-circle' &&
|
event.type !== 'xstate.done.actor.set-up-draft-circle' &&
|
||||||
event.type !== 'xstate.done.actor.set-up-draft-rectangle' &&
|
event.type !== 'xstate.done.actor.set-up-draft-rectangle' &&
|
||||||
event.type !== 'xstate.done.actor.set-up-draft-center-rectangle' &&
|
event.type !== 'xstate.done.actor.set-up-draft-center-rectangle' &&
|
||||||
@ -1863,7 +1863,7 @@ export const modelingMachine = setup({
|
|||||||
// lee: I REALLY wanted to inline this at the location of the actor invocation
|
// lee: I REALLY wanted to inline this at the location of the actor invocation
|
||||||
// but the type checker loses it's fricking mind because the `actors` prop
|
// but the type checker loses it's fricking mind because the `actors` prop
|
||||||
// this exists on now doesn't have the correct type if I do that. *agh*.
|
// this exists on now doesn't have the correct type if I do that. *agh*.
|
||||||
actorCircle3Point: fromCallback<
|
actorCircleThreePoint: fromCallback<
|
||||||
{ type: '' }, // Not used. We receive() no events in this actor.
|
{ type: '' }, // Not used. We receive() no events in this actor.
|
||||||
SketchDetails | undefined,
|
SketchDetails | undefined,
|
||||||
// Doesn't type-check anything for some reason.
|
// Doesn't type-check anything for some reason.
|
||||||
@ -1873,17 +1873,39 @@ export const modelingMachine = setup({
|
|||||||
// destroying the actor and going back to idle state.
|
// destroying the actor and going back to idle state.
|
||||||
if (!sketchDetails) return
|
if (!sketchDetails) return
|
||||||
|
|
||||||
const cleanupFn = sceneEntitiesManager.entryDraftCircle3Point(
|
let tool = new CircleThreePoint({
|
||||||
// I make it clear that the stop is coming from an internal call
|
scene: sceneEntitiesManager.scene,
|
||||||
() => sendBack({ type: 'stop-internal' }),
|
intersectionPlane: sceneEntitiesManager.intersectionPlane,
|
||||||
sketchDetails.planeNodePath,
|
startSketchOnASTNodePath: sketchDetails.planeNodePath,
|
||||||
new Vector3(...sketchDetails.zAxis),
|
maybeExistingNodePath: sketchDetails.sketchEntryNodePath,
|
||||||
new Vector3(...sketchDetails.yAxis),
|
forward: new Vector3(...sketchDetails.zAxis),
|
||||||
new Vector3(...sketchDetails.origin)
|
up: new Vector3(...sketchDetails.yAxis),
|
||||||
)
|
sketchOrigin: new Vector3(...sketchDetails.origin),
|
||||||
|
|
||||||
|
// Needed because of our current architecture of initializing
|
||||||
|
// shapes and then immediately entering "generic" sketch editing mode.
|
||||||
|
callDoneFnAfterBeingDefined: true,
|
||||||
|
done(output) {
|
||||||
|
sendBack({
|
||||||
|
type: 'xstate.done.actor.actor-circle-three-point',
|
||||||
|
output: {
|
||||||
|
updatedPlaneNodePath,
|
||||||
|
updatedEntryNodePath,
|
||||||
|
updatedSketchNodePaths,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
sceneInfra.setCallbacks({
|
||||||
|
// After the third click this actor will transition.
|
||||||
|
onClick: tool.onClick,
|
||||||
|
})
|
||||||
|
|
||||||
|
tool.init()
|
||||||
|
|
||||||
// When the state is exited (by anything, even itself), this is run!
|
// When the state is exited (by anything, even itself), this is run!
|
||||||
return cleanupFn
|
return tool.destroy
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
// end actors
|
// end actors
|
||||||
@ -2271,10 +2293,6 @@ export const modelingMachine = setup({
|
|||||||
target: 'SketchIdle',
|
target: 'SketchIdle',
|
||||||
guard: 'is editing existing sketch',
|
guard: 'is editing existing sketch',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
target: 'circle3PointToolSelect',
|
|
||||||
guard: 'is editing 3-point circle',
|
|
||||||
},
|
|
||||||
'Line tool',
|
'Line tool',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -2650,9 +2668,9 @@ export const modelingMachine = setup({
|
|||||||
guard: 'next is center rectangle',
|
guard: 'next is center rectangle',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: 'circle3PointToolSelect',
|
target: 'circleThreePointToolSelect',
|
||||||
reenter: true,
|
reenter: true,
|
||||||
guard: 'next is circle 3 point',
|
guard: 'next is circle three point',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -2756,14 +2774,14 @@ export const modelingMachine = setup({
|
|||||||
initial: 'splitting sketch pipe',
|
initial: 'splitting sketch pipe',
|
||||||
entry: ['assign tool in context', 'reset selections'],
|
entry: ['assign tool in context', 'reset selections'],
|
||||||
},
|
},
|
||||||
circle3PointToolSelect: {
|
circleThreePointToolSelect: {
|
||||||
invoke: {
|
invoke: {
|
||||||
id: 'actor-circle-3-point',
|
id: 'actor-circle-three-point',
|
||||||
input: function ({ context }) {
|
input: function ({ context }) {
|
||||||
if (!context.sketchDetails) return
|
if (!context.sketchDetails) return
|
||||||
return context.sketchDetails
|
return context.sketchDetails
|
||||||
},
|
},
|
||||||
src: 'actorCircle3Point',
|
src: 'actorCircleThreePoint',
|
||||||
},
|
},
|
||||||
on: {
|
on: {
|
||||||
// We still need this action to trigger (legacy code support)
|
// We still need this action to trigger (legacy code support)
|
||||||
@ -2771,6 +2789,7 @@ export const modelingMachine = setup({
|
|||||||
// On stop event, transition to our usual SketchIdle state
|
// On stop event, transition to our usual SketchIdle state
|
||||||
'stop-internal': {
|
'stop-internal': {
|
||||||
target: '#Modeling.Sketch.SketchIdle',
|
target: '#Modeling.Sketch.SketchIdle',
|
||||||
|
actions: 'update sketchDetails',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -3018,44 +3037,28 @@ export function isEditingExistingSketch({
|
|||||||
maybePipeExpression.callee.name === 'circle')
|
maybePipeExpression.callee.name === 'circle')
|
||||||
)
|
)
|
||||||
return true
|
return true
|
||||||
|
if (
|
||||||
|
maybePipeExpression.type === 'CallExpressionKw' &&
|
||||||
|
(maybePipeExpression.callee.name === 'startProfileAt' ||
|
||||||
|
maybePipeExpression.callee.name === 'circleThreePoint')
|
||||||
|
)
|
||||||
|
return true
|
||||||
if (maybePipeExpression.type !== 'PipeExpression') return false
|
if (maybePipeExpression.type !== 'PipeExpression') return false
|
||||||
const hasStartProfileAt = maybePipeExpression.body.some(
|
const hasStartProfileAt = maybePipeExpression.body.some(
|
||||||
(item) =>
|
(item) =>
|
||||||
item.type === 'CallExpression' && item.callee.name === 'startProfileAt'
|
item.type === 'CallExpression' && item.callee.name === 'startProfileAt'
|
||||||
)
|
)
|
||||||
const hasCircle = maybePipeExpression.body.some(
|
const hasCircle =
|
||||||
(item) => item.type === 'CallExpression' && item.callee.name === 'circle'
|
maybePipeExpression.body.some(
|
||||||
)
|
(item) => item.type === 'CallExpression' && item.callee.name === 'circle'
|
||||||
|
) ||
|
||||||
|
maybePipeExpression.body.some(
|
||||||
|
(item) =>
|
||||||
|
item.type === 'CallExpressionKw' &&
|
||||||
|
item.callee.name === 'circleThreePoint'
|
||||||
|
)
|
||||||
return (hasStartProfileAt && maybePipeExpression.body.length > 1) || hasCircle
|
return (hasStartProfileAt && maybePipeExpression.body.length > 1) || hasCircle
|
||||||
}
|
}
|
||||||
export function isEditing3PointCircle({
|
|
||||||
sketchDetails,
|
|
||||||
}: {
|
|
||||||
sketchDetails: SketchDetails | null
|
|
||||||
}): boolean {
|
|
||||||
if (!sketchDetails?.sketchEntryNodePath) return false
|
|
||||||
const variableDeclaration = getNodeFromPath<VariableDeclarator>(
|
|
||||||
kclManager.ast,
|
|
||||||
sketchDetails.sketchEntryNodePath,
|
|
||||||
'VariableDeclarator'
|
|
||||||
)
|
|
||||||
if (err(variableDeclaration)) return false
|
|
||||||
if (variableDeclaration.node.type !== 'VariableDeclarator') return false
|
|
||||||
const pipeExpression = variableDeclaration.node.init
|
|
||||||
if (pipeExpression.type !== 'PipeExpression') return false
|
|
||||||
const hasStartProfileAt = pipeExpression.body.some(
|
|
||||||
(item) =>
|
|
||||||
item.type === 'CallExpression' && item.callee.name === 'startProfileAt'
|
|
||||||
)
|
|
||||||
const hasCircle3Point = pipeExpression.body.some(
|
|
||||||
(item) =>
|
|
||||||
item.type === 'CallExpressionKw' &&
|
|
||||||
item.callee.name === 'circleThreePoint'
|
|
||||||
)
|
|
||||||
return (
|
|
||||||
(hasStartProfileAt && pipeExpression.body.length > 2) || hasCircle3Point
|
|
||||||
)
|
|
||||||
}
|
|
||||||
export function pipeHasCircle({
|
export function pipeHasCircle({
|
||||||
sketchDetails,
|
sketchDetails,
|
||||||
}: {
|
}: {
|
||||||
|
@ -1402,6 +1402,19 @@ pub enum Path {
|
|||||||
/// This is used to compute the tangential angle.
|
/// This is used to compute the tangential angle.
|
||||||
ccw: bool,
|
ccw: bool,
|
||||||
},
|
},
|
||||||
|
CircleThreePoint {
|
||||||
|
#[serde(flatten)]
|
||||||
|
base: BasePath,
|
||||||
|
/// Point 1 of the circle
|
||||||
|
#[ts(type = "[number, number]")]
|
||||||
|
p1: [f64; 2],
|
||||||
|
/// Point 2 of the circle
|
||||||
|
#[ts(type = "[number, number]")]
|
||||||
|
p2: [f64; 2],
|
||||||
|
/// Point 3 of the circle
|
||||||
|
#[ts(type = "[number, number]")]
|
||||||
|
p3: [f64; 2],
|
||||||
|
},
|
||||||
/// A path that is horizontal.
|
/// A path that is horizontal.
|
||||||
Horizontal {
|
Horizontal {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
@ -1444,6 +1457,7 @@ enum PathType {
|
|||||||
TangentialArc,
|
TangentialArc,
|
||||||
TangentialArcTo,
|
TangentialArcTo,
|
||||||
Circle,
|
Circle,
|
||||||
|
CircleThreePoint,
|
||||||
Horizontal,
|
Horizontal,
|
||||||
AngledLineTo,
|
AngledLineTo,
|
||||||
Arc,
|
Arc,
|
||||||
@ -1456,6 +1470,7 @@ impl From<&Path> for PathType {
|
|||||||
Path::TangentialArcTo { .. } => Self::TangentialArcTo,
|
Path::TangentialArcTo { .. } => Self::TangentialArcTo,
|
||||||
Path::TangentialArc { .. } => Self::TangentialArc,
|
Path::TangentialArc { .. } => Self::TangentialArc,
|
||||||
Path::Circle { .. } => Self::Circle,
|
Path::Circle { .. } => Self::Circle,
|
||||||
|
Path::CircleThreePoint { .. } => Self::CircleThreePoint,
|
||||||
Path::Horizontal { .. } => Self::Horizontal,
|
Path::Horizontal { .. } => Self::Horizontal,
|
||||||
Path::AngledLineTo { .. } => Self::AngledLineTo,
|
Path::AngledLineTo { .. } => Self::AngledLineTo,
|
||||||
Path::Base { .. } => Self::Base,
|
Path::Base { .. } => Self::Base,
|
||||||
@ -1474,6 +1489,7 @@ impl Path {
|
|||||||
Path::TangentialArcTo { base, .. } => base.geo_meta.id,
|
Path::TangentialArcTo { base, .. } => base.geo_meta.id,
|
||||||
Path::TangentialArc { base, .. } => base.geo_meta.id,
|
Path::TangentialArc { base, .. } => base.geo_meta.id,
|
||||||
Path::Circle { base, .. } => base.geo_meta.id,
|
Path::Circle { base, .. } => base.geo_meta.id,
|
||||||
|
Path::CircleThreePoint { base, .. } => base.geo_meta.id,
|
||||||
Path::Arc { base, .. } => base.geo_meta.id,
|
Path::Arc { base, .. } => base.geo_meta.id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1487,6 +1503,7 @@ impl Path {
|
|||||||
Path::TangentialArcTo { base, .. } => base.tag.clone(),
|
Path::TangentialArcTo { base, .. } => base.tag.clone(),
|
||||||
Path::TangentialArc { base, .. } => base.tag.clone(),
|
Path::TangentialArc { base, .. } => base.tag.clone(),
|
||||||
Path::Circle { base, .. } => base.tag.clone(),
|
Path::Circle { base, .. } => base.tag.clone(),
|
||||||
|
Path::CircleThreePoint { base, .. } => base.tag.clone(),
|
||||||
Path::Arc { base, .. } => base.tag.clone(),
|
Path::Arc { base, .. } => base.tag.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1500,6 +1517,7 @@ impl Path {
|
|||||||
Path::TangentialArcTo { base, .. } => base,
|
Path::TangentialArcTo { base, .. } => base,
|
||||||
Path::TangentialArc { base, .. } => base,
|
Path::TangentialArc { base, .. } => base,
|
||||||
Path::Circle { base, .. } => base,
|
Path::Circle { base, .. } => base,
|
||||||
|
Path::CircleThreePoint { base, .. } => base,
|
||||||
Path::Arc { base, .. } => base,
|
Path::Arc { base, .. } => base,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1537,6 +1555,10 @@ impl Path {
|
|||||||
linear_distance(self.get_from(), self.get_to())
|
linear_distance(self.get_from(), self.get_to())
|
||||||
}
|
}
|
||||||
Self::Circle { radius, .. } => 2.0 * std::f64::consts::PI * radius,
|
Self::Circle { radius, .. } => 2.0 * std::f64::consts::PI * radius,
|
||||||
|
Self::CircleThreePoint { p1, p2, p3, .. } => {
|
||||||
|
let circle_params = crate::std::utils::calculate_circle_from_3_points([p1.into(), p2.into(), p3.into()]);
|
||||||
|
2.0 * std::f64::consts::PI * circle_params.radius
|
||||||
|
},
|
||||||
Self::Arc { .. } => {
|
Self::Arc { .. } => {
|
||||||
// TODO: Call engine utils to figure this out.
|
// TODO: Call engine utils to figure this out.
|
||||||
linear_distance(self.get_from(), self.get_to())
|
linear_distance(self.get_from(), self.get_to())
|
||||||
@ -1553,6 +1575,7 @@ impl Path {
|
|||||||
Path::TangentialArcTo { base, .. } => Some(base),
|
Path::TangentialArcTo { base, .. } => Some(base),
|
||||||
Path::TangentialArc { base, .. } => Some(base),
|
Path::TangentialArc { base, .. } => Some(base),
|
||||||
Path::Circle { base, .. } => Some(base),
|
Path::Circle { base, .. } => Some(base),
|
||||||
|
Path::CircleThreePoint { base, .. } => Some(base),
|
||||||
Path::Arc { base, .. } => Some(base),
|
Path::Arc { base, .. } => Some(base),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1572,6 +1595,16 @@ impl Path {
|
|||||||
ccw: *ccw,
|
ccw: *ccw,
|
||||||
radius: *radius,
|
radius: *radius,
|
||||||
},
|
},
|
||||||
|
Path::CircleThreePoint {
|
||||||
|
p1, p2, p3, ..
|
||||||
|
} => {
|
||||||
|
let circle_params = crate::std::utils::calculate_circle_from_3_points([p1.into(), p2.into(), p3.into()]);
|
||||||
|
GetTangentialInfoFromPathsResult::Circle {
|
||||||
|
center: circle_params.center.into(),
|
||||||
|
ccw: true,
|
||||||
|
radius: circle_params.radius,
|
||||||
|
}
|
||||||
|
},
|
||||||
Path::ToPoint { .. } | Path::Horizontal { .. } | Path::AngledLineTo { .. } | Path::Base { .. } => {
|
Path::ToPoint { .. } | Path::Horizontal { .. } | Path::AngledLineTo { .. } | Path::Base { .. } => {
|
||||||
let base = self.get_base();
|
let base = self.get_base();
|
||||||
GetTangentialInfoFromPathsResult::PreviousPoint(base.from)
|
GetTangentialInfoFromPathsResult::PreviousPoint(base.from)
|
||||||
|
@ -231,7 +231,7 @@ pub(crate) async fn do_post_extrude(
|
|||||||
Path::Arc { .. }
|
Path::Arc { .. }
|
||||||
| Path::TangentialArc { .. }
|
| Path::TangentialArc { .. }
|
||||||
| Path::TangentialArcTo { .. }
|
| Path::TangentialArcTo { .. }
|
||||||
| Path::Circle { .. } => {
|
| Path::Circle { .. } | Path::CircleThreePoint { .. } => {
|
||||||
let extrude_surface = ExtrudeSurface::ExtrudeArc(crate::execution::ExtrudeArc {
|
let extrude_surface = ExtrudeSurface::ExtrudeArc(crate::execution::ExtrudeArc {
|
||||||
face_id: *actual_face_id,
|
face_id: *actual_face_id,
|
||||||
tag: path.get_base().tag.clone(),
|
tag: path.get_base().tag.clone(),
|
||||||
|
@ -182,6 +182,9 @@ pub async fn circle_three_point(exec_state: &mut ExecState, args: Args) -> Resul
|
|||||||
tag = "Identifier for the circle to reference elsewhere.",
|
tag = "Identifier for the circle to reference elsewhere.",
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
/// Similar to inner_circle, but needs to retain 3-point information in the
|
||||||
|
/// path so it can be used for other features, otherwise it's lost.
|
||||||
async fn inner_circle_three_point(
|
async fn inner_circle_three_point(
|
||||||
p1: [f64; 2],
|
p1: [f64; 2],
|
||||||
p2: [f64; 2],
|
p2: [f64; 2],
|
||||||
@ -192,20 +195,72 @@ async fn inner_circle_three_point(
|
|||||||
args: Args,
|
args: Args,
|
||||||
) -> Result<Sketch, KclError> {
|
) -> Result<Sketch, KclError> {
|
||||||
let center = calculate_circle_center(p1, p2, p3);
|
let center = calculate_circle_center(p1, p2, p3);
|
||||||
inner_circle(
|
// It can be the distance to any of the 3 points - they all lay on the circumference.
|
||||||
CircleData {
|
let radius = distance(center.into(), p2.into());
|
||||||
center,
|
|
||||||
// It can be the distance to any of the 3 points - they all lay on the circumference.
|
let sketch_surface = match sketch_surface_or_group {
|
||||||
radius: distance(center.into(), p2.into()),
|
SketchOrSurface::SketchSurface(surface) => surface,
|
||||||
},
|
SketchOrSurface::Sketch(group) => group.on,
|
||||||
sketch_surface_or_group,
|
};
|
||||||
tag,
|
let sketch = crate::std::sketch::inner_start_profile_at(
|
||||||
|
[center[0] + radius, center[1]],
|
||||||
|
sketch_surface,
|
||||||
|
None,
|
||||||
exec_state,
|
exec_state,
|
||||||
args,
|
args.clone(),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
|
|
||||||
|
let from = [center[0] + radius, center[1]];
|
||||||
|
let angle_start = Angle::zero();
|
||||||
|
let angle_end = Angle::turn();
|
||||||
|
|
||||||
|
let id = exec_state.next_uuid();
|
||||||
|
|
||||||
|
args.batch_modeling_cmd(
|
||||||
|
id,
|
||||||
|
ModelingCmd::from(mcmd::ExtendPath {
|
||||||
|
path: sketch.id.into(),
|
||||||
|
segment: PathSegment::Arc {
|
||||||
|
start: angle_start,
|
||||||
|
end: angle_end,
|
||||||
|
center: KPoint2d::from(center).map(LengthUnit),
|
||||||
|
radius: radius.into(),
|
||||||
|
relative: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let current_path = Path::CircleThreePoint {
|
||||||
|
base: BasePath {
|
||||||
|
from,
|
||||||
|
to: from,
|
||||||
|
tag: tag.clone(),
|
||||||
|
geo_meta: GeoMeta {
|
||||||
|
id,
|
||||||
|
metadata: args.source_range.into(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
p1,
|
||||||
|
p2,
|
||||||
|
p3,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut new_sketch = sketch.clone();
|
||||||
|
if let Some(tag) = &tag {
|
||||||
|
new_sketch.add_tag(tag, ¤t_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
new_sketch.paths.push(current_path);
|
||||||
|
|
||||||
|
args.batch_modeling_cmd(id, ModelingCmd::from(mcmd::ClosePath { path_id: new_sketch.id }))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(new_sketch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Type of the polygon
|
/// Type of the polygon
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Default)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Default)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
|
Reference in New Issue
Block a user