basic circle edit working

This commit is contained in:
Kurt Hutten Irev-Dev
2024-08-24 21:08:33 +10:00
parent b7c71c01cf
commit acfa95f728
6 changed files with 649 additions and 41 deletions

View File

@ -62,9 +62,10 @@ import {
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
import { executeAst } from 'lang/langHelpers'
import {
circleSegment,
createArcGeometry,
dashedStraight,
profileStart,
createProfileStartHandle,
straightSegment,
tangentialArcToSegment,
} from './segments'
@ -72,6 +73,7 @@ import {
addCallExpressionsToPipe,
addCloseToPipe,
addNewSketchLn,
changeCircleArguments,
changeSketchArguments,
updateStartProfileAtArgs,
} from 'lang/std/sketch'
@ -119,6 +121,10 @@ export const TANGENTIAL_ARC_TO__SEGMENT_DASH =
'tangential-arc-to-segment-body-dashed'
export const TANGENTIAL_ARC_TO_SEGMENT = 'tangential-arc-to-segment'
export const TANGENTIAL_ARC_TO_SEGMENT_BODY = 'tangential-arc-to-segment-body'
export const CIRCLE_SEGMENT = 'circle-segment'
export const CIRCLE_SEGMENT_BODY = 'circle-segment-body'
export const CIRCLE_SEGMENT_DASH = 'circle-segment-body-dashed'
export const CIRCLE_CENTER_HANDLE = 'circle-center-handle'
export const SEGMENT_WIDTH_PX = 1.6
export const HIDE_SEGMENT_LENGTH = 75 // in pixels
export const HIDE_HOVER_SEGMENT_LENGTH = 60 // in pixels
@ -186,6 +192,27 @@ export class SceneEntities {
})
)
}
if (
segment.userData.from &&
segment.userData.to &&
segment.userData.center &&
segment.userData.radius &&
segment.userData.prevSegment &&
segment.userData.type === CIRCLE_SEGMENT
) {
callbacks.push(
this.updateCircleSegment({
prevSegment: segment.userData.prevSegment,
from: segment.userData.from,
to: segment.userData.to,
center: segment.userData.center,
radius: segment.userData.radius,
group: segment,
scale: factor,
})
)
}
if (segment.name === PROFILE_START) {
segment.scale.set(factor, factor, factor)
}
@ -421,19 +448,21 @@ export class SceneEntities {
maybeModdedAst,
sketchGroup.start.__geoMeta.sourceRange
)
const _profileStart = profileStart({
from: sketchGroup.start.from,
id: sketchGroup.start.__geoMeta.id,
pathToNode: segPathToNode,
scale: factor,
theme: sceneInfra._theme,
})
_profileStart.layers.set(SKETCH_LAYER)
_profileStart.traverse((child) => {
child.layers.set(SKETCH_LAYER)
})
group.add(_profileStart)
this.activeSegments[JSON.stringify(segPathToNode)] = _profileStart
if (sketchGroup?.value?.[0].type !== 'Circle') {
const _profileStart = createProfileStartHandle({
from: sketchGroup.start.from,
id: sketchGroup.start.__geoMeta.id,
pathToNode: segPathToNode,
scale: factor,
theme: sceneInfra._theme,
})
_profileStart.layers.set(SKETCH_LAYER)
_profileStart.traverse((child) => {
child.layers.set(SKETCH_LAYER)
})
group.add(_profileStart)
this.activeSegments[JSON.stringify(segPathToNode)] = _profileStart
}
const callbacks: (() => SegmentOverlayPayload | null)[] = []
sketchGroup.value.forEach((segment, index) => {
let segPathToNode = getNodePathFromSourceRange(
@ -498,6 +527,32 @@ export class SceneEntities {
scale: factor,
})
)
} else if (segment.type === 'Circle') {
seg = circleSegment({
prevSegment: sketchGroup.value[index - 1],
from: segment.from,
to: segment.to,
center: segment.center,
radius: segment.radius,
id: segment.__geoMeta.id,
pathToNode: segPathToNode,
isDraftSegment,
scale: factor,
texture: sceneInfra.extraSegmentTexture,
theme: sceneInfra._theme,
isSelected,
})
callbacks.push(
this.updateCircleSegment({
prevSegment: sketchGroup.value[index - 1],
from: segment.from,
to: segment.to,
center: segment.center,
radius: segment.radius,
group: seg,
scale: factor,
})
)
} else {
seg = straightSegment({
from: segment.from,
@ -587,6 +642,7 @@ export class SceneEntities {
segmentName: 'line' | 'tangentialArcTo' = 'line',
shouldTearDown = true
) => {
// try {
const _ast = structuredClone(kclManager.ast)
const _node1 = getNodeFromPath<VariableDeclaration>(
@ -602,11 +658,10 @@ export class SceneEntities {
kclManager.programMemory.get(variableDeclarationName),
variableDeclarationName
)
if (err(sg)) return sg
const lastSeg = sg.value?.slice(-1)[0] || sg.start
if (err(sg)) return Promise.reject(sg)
const lastSeg = sg?.value?.slice(-1)[0] || sg.start
const index = sg.value.length // because we've added a new segment that's not in the memory yet, no need for `-1`
const mod = addNewSketchLn({
node: _ast,
programMemory: kclManager.programMemory,
@ -621,7 +676,7 @@ export class SceneEntities {
const draftExpressionsIndices = { start: index, end: index }
if (shouldTearDown) await this.tearDownSketch({ removeAxis: false })
// if (shouldTearDown) await this.tearDownSketch({ removeAxis: false })
sceneInfra.resetMouseListeners()
const { truncatedAst, programMemoryOverride, sketchGroup } =
@ -725,6 +780,9 @@ export class SceneEntities {
},
...this.mouseEnterLeaveCallbacks(),
})
// } catch (e) {
// console.log('yo wtf', e)
// }
}
setupDraftRectangle = async (
sketchPathToNode: PathToNode,
@ -746,6 +804,164 @@ export class SceneEntities {
const startSketchOn = _node1.node?.declarations
const startSketchOnInit = startSketchOn?.[0]?.init
const sg = sketchGroupFromKclValue(
kclManager.programMemory.get(variableDeclarationName),
variableDeclarationName
)
if (err(sg)) return sg
const tags: [string, string, string] = [
findUniqueName(_ast, 'rectangleSegmentA'),
findUniqueName(_ast, 'rectangleSegmentB'),
findUniqueName(_ast, 'rectangleSegmentC'),
]
startSketchOn[0].init = createPipeExpression([
startSketchOnInit,
...getRectangleCallExpressions(rectangleOrigin, tags),
])
let _recastAst = parse(recast(_ast))
if (trap(_recastAst)) return Promise.reject(_recastAst)
_ast = _recastAst
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
sketchPathToNode,
forward,
up,
position: sketchOrigin,
maybeModdedAst: _ast,
draftExpressionsIndices: { start: 0, end: 3 },
})
sceneInfra.setCallbacks({
onMove: async (args) => {
// Update the width and height of the draft rectangle
const pathToNodeTwo = structuredClone(sketchPathToNode)
pathToNodeTwo[1][0] = 0
const _node = getNodeFromPath<VariableDeclaration>(
truncatedAst,
pathToNodeTwo || [],
'VariableDeclaration'
)
if (trap(_node)) return Promise.reject(_node)
const sketchInit = _node.node?.declarations?.[0]?.init
const x = (args.intersectionPoint.twoD.x || 0) - rectangleOrigin[0]
const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1]
if (sketchInit.type === 'PipeExpression') {
updateRectangleSketch(sketchInit, x, y, tags[0])
}
const { programMemory } = await executeAst({
ast: truncatedAst,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager,
programMemoryOverride,
})
this.sceneProgramMemory = programMemory
const sketchGroup = sketchGroupFromKclValue(
programMemory.get(variableDeclarationName),
variableDeclarationName
)
if (err(sketchGroup)) return Promise.reject(sketchGroup)
const sgPaths = sketchGroup.value
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
this.updateSegment(
sketchGroup.start,
0,
0,
_ast,
orthoFactor,
sketchGroup
)
sgPaths.forEach((seg, index) =>
this.updateSegment(seg, index, 0, _ast, orthoFactor, sketchGroup)
)
},
onClick: async (args) => {
// 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 x = roundOff((cornerPoint.x || 0) - rectangleOrigin[0])
const y = roundOff((cornerPoint.y || 0) - rectangleOrigin[1])
const _node = getNodeFromPath<VariableDeclaration>(
_ast,
sketchPathToNode || [],
'VariableDeclaration'
)
if (trap(_node)) return Promise.reject(_node)
const sketchInit = _node.node?.declarations?.[0]?.init
if (sketchInit.type === 'PipeExpression') {
updateRectangleSketch(sketchInit, x, y, tags[0])
let _recastAst = parse(recast(_ast))
if (trap(_recastAst)) return Promise.reject(_recastAst)
_ast = _recastAst
// Update the primary AST and unequip the rectangle tool
await kclManager.executeAstMock(_ast)
sceneInfra.modelingSend({ type: 'Finish rectangle' })
const { programMemory } = await executeAst({
ast: _ast,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager,
programMemoryOverride,
})
// Prepare to update the THREEjs scene
this.sceneProgramMemory = programMemory
const sketchGroup = sketchGroupFromKclValue(
programMemory.get(
variableDeclarationName
), variableDeclarationName)
if (err(sketchGroup)) return Promise.reject(sketchGroup)
const sgPaths = sketchGroup.value
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
// Update the starting segment of the THREEjs scene
this.updateSegment(
sketchGroup.start,
0,
0,
_ast,
orthoFactor,
sketchGroup
)
// Update the rest of the segments of the THREEjs scene
sgPaths.forEach((seg, index) =>
this.updateSegment(seg, index, 0, _ast, orthoFactor, sketchGroup)
)
}
},
})
}
setupDraftCircle = async (
sketchPathToNode: PathToNode,
forward: [number, number, number],
up: [number, number, number],
sketchOrigin: [number, number, number],
rectangleOrigin: [x: number, y: number]
) => {
let _ast = structuredClone(kclManager.ast)
const _node1 = getNodeFromPath<VariableDeclaration>(
_ast,
sketchPathToNode || [],
'VariableDeclaration'
)
if (trap(_node1)) return Promise.reject(_node1)
const variableDeclarationName =
_node1.node?.declarations?.[0]?.id?.name || ''
const startSketchOn = _node1.node?.declarations
const startSketchOnInit = startSketchOn?.[0]?.init
const tags: [string, string, string] = [
findUniqueName(_ast, 'rectangleSegmentA'),
findUniqueName(_ast, 'rectangleSegmentB'),
@ -1047,7 +1263,9 @@ export class SceneEntities {
STRAIGHT_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT,
PROFILE_START,
CIRCLE_SEGMENT,
])
const subGroup = getParentGroup(object, [ARROWHEAD, CIRCLE_CENTER_HANDLE])
if (!group) return
const pathToNode: PathToNode = structuredClone(group.userData.pathToNode)
const varDecIndex = pathToNode[1][0]
@ -1065,7 +1283,7 @@ export class SceneEntities {
group.userData.from[0],
group.userData.from[1],
]
const to: [number, number] = [intersection2d.x, intersection2d.y]
const dragTo: [number, number] = [intersection2d.x, intersection2d.y]
let modifiedAst = draftInfo ? draftInfo.truncatedAst : { ...kclManager.ast }
const _node = getNodeFromPath<CallExpression>(
@ -1088,16 +1306,42 @@ export class SceneEntities {
modded = updateStartProfileAtArgs({
node: modifiedAst,
pathToNode,
to,
to: dragTo,
from,
previousProgramMemory: kclManager.programMemory,
})
} else if (group.name === CIRCLE_SEGMENT && subGroup?.name === ARROWHEAD) {
// is dragging the radius handle
modded = changeCircleArguments(
modifiedAst,
kclManager.programMemory,
[node.start, node.end],
group.userData.center,
Math.sqrt(
(group.userData.center[0] - dragTo[0]) ** 2 +
(group.userData.center[0] - dragTo[0]) ** 2
)
)
console.log('modded', modded)
} else if (
group.name === CIRCLE_SEGMENT &&
subGroup?.name === CIRCLE_CENTER_HANDLE
) {
// is dragging the center handle
modded = changeCircleArguments(
modifiedAst,
kclManager.programMemory,
[node.start, node.end],
dragTo,
group.userData.radius
)
console.log('modded', modded)
} else {
modded = changeSketchArguments(
modifiedAst,
kclManager.programMemory,
[node.start, node.end],
to,
dragTo,
from
)
}
@ -1216,6 +1460,20 @@ export class SceneEntities {
group,
scale: factor,
})
} else if (
type === CIRCLE_SEGMENT &&
'center' in segment &&
'radius' in segment
) {
return this.updateCircleSegment({
prevSegment: sgPaths[index - 1],
from: segment.from,
to: segment.to,
center: segment.center,
radius: segment.radius,
group,
scale: factor,
})
} else if (type === PROFILE_START) {
group.position.set(segment.from[0], segment.from[1], 0)
group.scale.set(factor, factor, factor)
@ -1241,6 +1499,9 @@ export class SceneEntities {
group.userData.prevSegment = prevSegment
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
const extraSegmentGroup = group.getObjectByName(EXTRA_SEGMENT_HANDLE)
if (!prevSegment) {
console.trace('prevSegment is undefined')
}
const previousPoint =
prevSegment?.type === 'TangentialArcTo'
@ -1336,6 +1597,111 @@ export class SceneEntities {
angle,
})
}
updateCircleSegment({
prevSegment,
from,
to,
center,
radius,
group,
scale = 1,
}: {
prevSegment: SketchGroup['value'][number]
from: [number, number]
to: [number, number]
center: [number, number]
radius: number
group: Group
scale?: number
}): () => SegmentOverlayPayload | null {
group.userData.from = from
group.userData.to = to
group.userData.center = center
group.userData.radius = radius
group.userData.prevSegment = prevSegment
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
const circleCenterHandle = group.getObjectByName(
CIRCLE_CENTER_HANDLE
) as Group
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, [TANGENTIAL_ARC_TO_SEGMENT])
let isHandlesVisible = !shouldHideIdle
if (hoveredParent && hoveredParent?.uuid === group?.uuid) {
isHandlesVisible = !shouldHideHover
}
if (arrowGroup) {
arrowGroup.position.set(
center[0] + Math.cos(Math.PI / 4) * radius,
center[1] + Math.sin(Math.PI / 4) * radius,
0
)
const arrowheadAngle = Math.PI / 4
arrowGroup.quaternion.setFromUnitVectors(
new Vector3(0, 1, 0),
new Vector3(Math.cos(arrowheadAngle), Math.sin(arrowheadAngle), 0)
)
arrowGroup.scale.set(scale, scale, scale)
arrowGroup.visible = isHandlesVisible
}
if (circleCenterHandle) {
circleCenterHandle.position.set(center[0], center[1], 0)
circleCenterHandle.scale.set(scale, scale, scale)
circleCenterHandle.visible = isHandlesVisible
}
const circleSegmentBody = group.children.find(
(child) => child.userData.type === CIRCLE_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.children.find(
(child) => child.userData.type === CIRCLE_SEGMENT_DASH
) as Mesh
if (circleSegmentBodyDashed) {
// consider throttling the whole updateTangentialArcToSegment
// if there are more perf considerations going forward
this.throttledUpdateDashedArcGeo({
// ...arcInfo,
center,
radius,
ccw: true,
startAngle: 0,
endAngle: 360,
mesh: circleSegmentBodyDashed,
isDashed: true,
scale,
})
}
const angle = 0
return () =>
sceneInfra.updateOverlayDetails({
arrowGroup,
group,
isHandlesVisible,
from,
to,
angle,
})
}
throttledUpdateDashedArcGeo = throttle(
(
args: Parameters<typeof createArcGeometry>[0] & {
@ -1470,7 +1836,7 @@ export class SceneEntities {
}
private _tearDownSketch(
callDepth = 0,
resolve: (val: unknown) => void,
resolve: any,
reject: () => void,
{ removeAxis = true }: { removeAxis?: boolean }
) {
@ -1500,7 +1866,7 @@ export class SceneEntities {
this._tearDownSketch(callDepth + 1, resolve, reject, { removeAxis })
}, delay)
} else {
reject()
resolve(true)
}
}
sceneInfra.camControls.enableRotate = true
@ -1512,7 +1878,7 @@ export class SceneEntities {
removeAxis = true,
}: {
removeAxis?: boolean
} = {}) {
} = {}): Promise<void | Error> {
// I think promisifying this is mostly a side effect of not having
// "setupSketch" correctly capture a promise when it's done
// so we're effectively waiting for to be finished setting up the scene just to tear it down
@ -1578,6 +1944,16 @@ export class SceneEntities {
group: parent,
scale: factor,
})
} else if (parent.name === CIRCLE_SEGMENT) {
this.updateCircleSegment({
prevSegment: parent.userData.prevSegment,
from: parent.userData.from,
to: parent.userData.to,
center: parent.userData.center,
radius: parent.userData.radius,
group: parent,
scale: factor,
})
}
return
}
@ -1613,6 +1989,16 @@ export class SceneEntities {
group: parent,
scale: factor,
})
} else if (parent.name === CIRCLE_SEGMENT) {
this.updateCircleSegment({
prevSegment: parent.userData.prevSegment,
from: parent.userData.from,
to: parent.userData.to,
center: parent.userData.center,
radius: parent.userData.radius,
group: parent,
scale: factor,
})
}
}
const isSelected = parent?.userData?.isSelected
@ -1825,6 +2211,7 @@ function colorSegment(object: any, color: number) {
const straightSegmentBody = getParentGroup(object, [
STRAIGHT_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT,
CIRCLE_SEGMENT,
])
if (straightSegmentBody) {
straightSegmentBody.traverse((child) => {

View File

@ -24,6 +24,10 @@ import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
import { PathToNode, SketchGroup, getTangentialArcToInfo } from 'lang/wasm'
import {
CIRCLE_CENTER_HANDLE,
CIRCLE_SEGMENT,
CIRCLE_SEGMENT_BODY,
CIRCLE_SEGMENT_DASH,
EXTRA_SEGMENT_HANDLE,
EXTRA_SEGMENT_OFFSET_PX,
HIDE_SEGMENT_LENGTH,
@ -46,7 +50,7 @@ import {
import { Themes, getThemeColorForThreeJs } from 'lib/theme'
import { roundOff } from 'lib/utils'
export function profileStart({
export function createProfileStartHandle({
from,
id,
pathToNode,
@ -225,6 +229,28 @@ function createArrowhead(scale = 1, theme: Themes, color?: number): Group {
arrowGroup.scale.set(scale, scale, scale)
return arrowGroup
}
function createCircleCenterHandle(
scale = 1,
theme: Themes,
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: CIRCLE_CENTER_HANDLE,
baseColor,
}
circleCenterGroup.name = CIRCLE_CENTER_HANDLE
circleCenterGroup.scale.set(scale, scale, scale)
return circleCenterGroup
}
function createExtraSegmentHandle(
scale: number,
@ -300,6 +326,103 @@ function createLengthIndicator({
return lengthIndicatorGroup
}
export function circleSegment({
prevSegment,
from,
to,
center,
radius,
id,
pathToNode,
isDraftSegment,
scale = 1,
texture,
theme,
isSelected,
}: {
prevSegment: SketchGroup['value'][number]
from: Coords2d
center: Coords2d
radius: number
to: Coords2d
id: string
pathToNode: PathToNode
isDraftSegment?: boolean
scale?: number
texture: Texture
theme: Themes
isSelected?: boolean
}): Group {
const group = new Group()
const geometry = createArcGeometry({
center,
radius,
startAngle: 0,
endAngle: Math.PI * 2,
ccw: true,
isDashed: isDraftSegment,
scale,
})
const baseColor = getThemeColorForThreeJs(theme)
const color = isSelected ? 0x0000ff : baseColor
const body = new MeshBasicMaterial({ color })
const mesh = new Mesh(geometry, body)
mesh.userData.type = isDraftSegment
? CIRCLE_SEGMENT_DASH
: CIRCLE_SEGMENT_BODY
group.userData = {
type: CIRCLE_SEGMENT,
id,
from,
to,
radius,
center,
ccw: true,
prevSegment,
pathToNode,
isSelected,
baseColor,
}
group.name = CIRCLE_SEGMENT
const arrowGroup = createArrowhead(scale, theme, color)
arrowGroup.position.set(
center[0] + Math.cos(Math.PI / 4) * radius,
center[1] + Math.sin(Math.PI / 4) * radius,
0
)
const circleCenterGroup = createCircleCenterHandle(scale, theme, color)
circleCenterGroup.position.set(center[0], center[1], 0)
const arrowheadAngle = Math.PI / 4
arrowGroup.quaternion.setFromUnitVectors(
new Vector3(0, 1, 0),
new Vector3(Math.cos(arrowheadAngle), Math.sin(arrowheadAngle), 0)
)
const pxLength = (radius * 2 * Math.PI) / scale
const shouldHide = pxLength < HIDE_SEGMENT_LENGTH
const extraSegmentGroup = createExtraSegmentHandle(scale, texture, theme)
const extraSegmentAngle = 0
const extraSegmentOffset = new Vector2(
Math.cos(extraSegmentAngle) * radius,
Math.sin(extraSegmentAngle) * radius
)
extraSegmentGroup.position.set(
center[0] + extraSegmentOffset.x,
center[1] + extraSegmentOffset.y,
0
)
extraSegmentGroup.visible = !shouldHide
group.add(mesh, arrowGroup, circleCenterGroup, extraSegmentGroup)
return group
}
export function tangentialArcToSegment({
prevSegment,
from,

View File

@ -1611,6 +1611,37 @@ export const sketchLineHelperMap: { [key: string]: SketchLineHelper } = {
tangentialArcTo,
} as const
export function changeCircleArguments(
node: Program,
programMemory: ProgramMemory,
sourceRange: SourceRange,
center: [number, number],
radius: number
): { modifiedAst: Program; pathToNode: PathToNode } | Error {
const _node = { ...node }
const thePath = getNodePathFromSourceRange(_node, sourceRange)
const nodeMeta = getNodeFromPath<CallExpression>(_node, thePath)
if (err(nodeMeta)) return nodeMeta
const { node: callExpression, shallowPath } = nodeMeta
if (callExpression?.callee?.name === 'circle') {
const newCenter = createArrayExpression([
createLiteral(roundOff(center[0])),
createLiteral(roundOff(center[1])),
])
const newRadius = createLiteral(roundOff(radius))
callExpression.arguments[0] = newCenter
callExpression.arguments[1] = newRadius
return {
modifiedAst: _node,
pathToNode: shallowPath,
}
}
return new Error(`not a sketch line helper: ${callExpression?.callee?.name}`)
}
export function changeSketchArguments(
node: Program,
programMemory: ProgramMemory,

View File

@ -1415,6 +1415,19 @@ pub enum Path {
/// arc's direction
ccw: bool,
},
/// a complete arc
Circle {
#[serde(flatten)]
base: BasePath,
/// the arc's center
#[ts(type = "[number, number]")]
center: [f64; 2],
/// the arc's radius
radius: f64,
/// arc's direction
// Maybe this one's not needed since it's a full revolution?
ccw: bool,
},
/// A path that is horizontal.
Horizontal {
#[serde(flatten)]
@ -1447,6 +1460,7 @@ impl Path {
Path::Base { base } => base.geo_meta.id,
Path::TangentialArcTo { base, .. } => base.geo_meta.id,
Path::TangentialArc { base, .. } => base.geo_meta.id,
Path::Circle { base, .. } => base.geo_meta.id,
}
}
@ -1458,6 +1472,7 @@ impl Path {
Path::Base { base } => base.tag.clone(),
Path::TangentialArcTo { base, .. } => base.tag.clone(),
Path::TangentialArc { base, .. } => base.tag.clone(),
Path::Circle { base, .. } => base.tag.clone(),
}
}
@ -1469,6 +1484,7 @@ impl Path {
Path::Base { base } => base,
Path::TangentialArcTo { base, .. } => base,
Path::TangentialArc { base, .. } => base,
Path::Circle { base, .. } => base,
}
}
@ -1480,6 +1496,7 @@ impl Path {
Path::Base { base } => Some(base),
Path::TangentialArcTo { base, .. } => Some(base),
Path::TangentialArc { base, .. } => Some(base),
Path::Circle { base, .. } => Some(base),
}
}
}

View File

@ -204,7 +204,7 @@ pub(crate) async fn do_post_extrude(
for path in sketch_group.value.iter() {
if let Some(Some(actual_face_id)) = face_id_map.get(&path.get_base().geo_meta.id) {
match path {
Path::TangentialArc { .. } | Path::TangentialArcTo { .. } => {
Path::TangentialArc { .. } | Path::TangentialArcTo { .. } | Path::Circle { .. } => {
let extrude_surface = ExtrudeSurface::ExtrudeArc(crate::executor::ExtrudeArc {
face_id: *actual_face_id,
tag: path.get_base().tag.clone(),

View File

@ -2,16 +2,19 @@
use anyhow::Result;
use derive_docs::stdlib;
use kittycad::types::{Angle, ModelingCmd};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::{
ast::types::TagDeclarator,
errors::KclError,
executor::KclValue,
std::{Args, SketchGroup, SketchSurface},
errors::{KclError, KclErrorDetails},
executor::{BasePath, GeoMeta, KclValue, Path, Point2d, SketchGroup, SketchSurface},
std::Args,
};
use super::utils::arc_center_and_end;
/// A sketch surface or a sketch group.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
@ -65,23 +68,70 @@ async fn inner_circle(
SketchSurfaceOrGroup::SketchSurface(surface) => surface,
SketchSurfaceOrGroup::SketchGroup(group) => group.on,
};
let mut sketch_group =
let sketch_group =
crate::std::sketch::inner_start_profile_at([center[0] + radius, center[1]], sketch_surface, None, args.clone())
.await?;
// Call arc.
sketch_group = crate::std::sketch::inner_arc(
crate::std::sketch::ArcData::AnglesAndRadius {
angle_start: 0.0,
angle_end: 360.0,
radius,
let from: Point2d = sketch_group.current_pen_position()?;
let angle_start = Angle::from_degrees(0.0);
let angle_end = Angle::from_degrees(360.0);
let (center, end) = arc_center_and_end(from, angle_start, angle_end, radius);
if angle_start == angle_end {
return Err(KclError::Type(KclErrorDetails {
message: "Arc start and end angles must be different".to_string(),
source_ranges: vec![args.source_range],
}));
}
let id = uuid::Uuid::new_v4();
args.batch_modeling_cmd(
id,
ModelingCmd::ExtendPath {
path: sketch_group.id,
segment: kittycad::types::PathSegment::Arc {
start: angle_start,
end: angle_end,
center: center.into(),
radius,
relative: false,
},
},
sketch_group,
tag,
args.clone(),
)
.await?;
// Call close.
crate::std::sketch::inner_close(sketch_group, None, args).await
let current_path = Path::Circle {
base: BasePath {
from: center.into(),
// to: end.into(),
to: center.into(),
tag: tag.clone(),
geo_meta: GeoMeta {
id,
metadata: args.source_range.into(),
},
},
radius,
center: center.into(),
ccw: angle_start.degrees() < angle_end.degrees(),
};
let mut new_sketch_group = sketch_group.clone();
if let Some(tag) = &tag {
new_sketch_group.add_tag(tag, &current_path);
}
new_sketch_group.value.push(current_path);
args.batch_modeling_cmd(
id,
ModelingCmd::ClosePath {
path_id: new_sketch_group.id,
},
)
.await?;
Ok(new_sketch_group)
}