Merge branch 'main' into franknoirot/4088/create-file-url
This commit is contained in:
2
.github/workflows/build-and-store-wasm.yml
vendored
2
.github/workflows/build-and-store-wasm.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
||||
|
||||
|
||||
# Upload the WASM bundle as an artifact
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wasm-bundle
|
||||
path: src/wasm-lib/pkg
|
||||
|
||||
@ -108,6 +108,8 @@ export class CameraControls {
|
||||
interactionGuards: MouseGuard = cameraMouseDragGuards.Zoo
|
||||
isFovAnimationInProgress = false
|
||||
perspectiveFovBeforeOrtho = 45
|
||||
// NOTE: Duplicated state across Provider and singleton. Mapped from settingsMachine
|
||||
_setting_allowOrbitInSketchMode = false
|
||||
get isPerspective() {
|
||||
return this.camera instanceof PerspectiveCamera
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import {
|
||||
BoxGeometry,
|
||||
Color,
|
||||
DoubleSide,
|
||||
Group,
|
||||
Intersection,
|
||||
@ -59,6 +60,7 @@ import {
|
||||
resultIsOk,
|
||||
SourceRange,
|
||||
} from 'lang/wasm'
|
||||
import { calculate_circle_from_3_points } from '../wasm-lib/pkg/wasm_lib'
|
||||
import {
|
||||
engineCommandManager,
|
||||
kclManager,
|
||||
@ -70,7 +72,7 @@ import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||
import { executeAst, ToolTip } from 'lang/langHelpers'
|
||||
import {
|
||||
createProfileStartHandle,
|
||||
createArcGeometry,
|
||||
createCircleGeometry,
|
||||
SegmentUtils,
|
||||
segmentUtils,
|
||||
} from './segments'
|
||||
@ -109,6 +111,8 @@ import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
|
||||
import { Point3d } from 'wasm-lib/kcl/bindings/Point3d'
|
||||
import { SegmentInputs } from 'lang/std/stdTypes'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
import { LabeledArg } from 'wasm-lib/kcl/bindings/LabeledArg'
|
||||
import { Literal } from 'wasm-lib/kcl/bindings/Literal'
|
||||
import { radToDeg } from 'three/src/math/MathUtils'
|
||||
import { getArtifactFromRange, codeRefFromRange } from 'lang/std/artifactGraph'
|
||||
|
||||
@ -1261,110 +1265,98 @@ export class SceneEntities {
|
||||
const groupOfDrafts = new Group()
|
||||
groupOfDrafts.name = 'circle-3-point-group'
|
||||
groupOfDrafts.position.copy(sketchOrigin)
|
||||
|
||||
// lee: I'm keeping this here as a developer gotchya:
|
||||
// Do not reorient your surfaces to the intersection plane. Your points are
|
||||
// already in 3D space, not 2D. If you intersect say XZ, you want the points
|
||||
// to continue to live at the 3D intersection point, not be rotated to end
|
||||
// up elsewhere!
|
||||
// groupOfDrafts.setRotationFromQuaternion(orientation)
|
||||
// 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)
|
||||
|
||||
const DRAFT_POINT_RADIUS = 6
|
||||
// How large the points on the circle will render as
|
||||
const DRAFT_POINT_RADIUS = 10 // px
|
||||
|
||||
const createPoint = (center: Vector3): number => {
|
||||
// The target of our dragging
|
||||
let target: Object3D | undefined = undefined
|
||||
|
||||
// The KCL this will generate.
|
||||
const kclCircle3Point = parse(`circleThreePoint(
|
||||
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 })
|
||||
|
||||
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: CIRCLE_3_POINT_DRAFT_POINT }
|
||||
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
|
||||
|
||||
groupOfDrafts.add(mesh)
|
||||
|
||||
return mesh.id
|
||||
return mesh
|
||||
}
|
||||
|
||||
const circle3Point = (
|
||||
points: Vector2[]
|
||||
): undefined | { center: Vector3; radius: number } => {
|
||||
// A 3-point circle is undefined if it doesn't have 3 points :)
|
||||
if (points.length !== 3) return undefined
|
||||
|
||||
// y = (i/j)(x-h) + b
|
||||
// i and j variables for the slopes
|
||||
const i = [points[1].x - points[0].x, points[2].x - points[1].x]
|
||||
const j = [points[1].y - points[0].y, points[2].y - points[1].y]
|
||||
|
||||
// Our / threejs coordinate system affects this a lot. If you take this
|
||||
// code into a different code base, you may have to adjust a/b to being
|
||||
// -1/a/b, b/a, etc! In this case, a/-b did the trick.
|
||||
const m = [i[0] / -j[0], i[1] / -j[1]]
|
||||
|
||||
const h = [
|
||||
(points[0].x + points[1].x) / 2,
|
||||
(points[1].x + points[2].x) / 2,
|
||||
]
|
||||
const b = [
|
||||
(points[0].y + points[1].y) / 2,
|
||||
(points[1].y + points[2].y) / 2,
|
||||
]
|
||||
|
||||
// Algebraically derived
|
||||
const x = (-m[0] * h[0] + b[0] - b[1] + m[1] * h[1]) / (m[1] - m[0])
|
||||
const y = m[0] * (x - h[0]) + b[0]
|
||||
|
||||
const center = new Vector3(x, y, 0)
|
||||
const radius = Math.sqrt((points[1].x - x) ** 2 + (points[1].y - y) ** 2)
|
||||
|
||||
return {
|
||||
center,
|
||||
radius,
|
||||
}
|
||||
}
|
||||
|
||||
// TO BE SHORT LIVED: unused function to draw the circle and lines.
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line
|
||||
const createCircle3Point = (points: Vector2[]) => {
|
||||
const circleParams = circle3Point(points)
|
||||
|
||||
// A circle cannot be created for these points.
|
||||
if (!circleParams) return
|
||||
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 geometryCircle = createArcGeometry({
|
||||
center: [circleParams.center.x, circleParams.center.y],
|
||||
radius: circleParams.radius,
|
||||
startAngle: 0,
|
||||
endAngle: Math.PI * 2,
|
||||
ccw: true,
|
||||
isDashed: true,
|
||||
scale,
|
||||
const lineCircle = createCircleGeometry({
|
||||
center: [center.x, center.y],
|
||||
radius,
|
||||
color,
|
||||
isDashed: false,
|
||||
scale: 1,
|
||||
})
|
||||
const materialCircle = new MeshBasicMaterial({ color })
|
||||
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 meshCircle = new Mesh(geometryCircle, materialCircle)
|
||||
meshCircle.userData = { type: CIRCLE_3_POINT_DRAFT_CIRCLE }
|
||||
meshCircle.layers.set(SKETCH_LAYER)
|
||||
meshCircle.position.set(circleParams.center.x, circleParams.center.y, 0)
|
||||
meshCircle.scale.set(scale, scale, scale)
|
||||
groupCircle.add(meshCircle)
|
||||
const pointMesh = createPoint(new Vector3(center.x, center.y, 0), {
|
||||
noInteraction: true,
|
||||
})
|
||||
groupCircle.add(pointMesh)
|
||||
|
||||
const geometryPolyLine = new BufferGeometry().setFromPoints([
|
||||
...points,
|
||||
points[0],
|
||||
...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,
|
||||
scale: 1 / scale,
|
||||
dashSize: 6,
|
||||
gapSize: 6,
|
||||
})
|
||||
@ -1375,13 +1367,146 @@ export class SceneEntities {
|
||||
groupOfDrafts.add(groupCircle)
|
||||
}
|
||||
|
||||
// The target of our dragging
|
||||
let target: Object3D | undefined = undefined
|
||||
const insertCircle3PointKclIntoAstSnapshot = (
|
||||
points: Vector2[]
|
||||
): Program => {
|
||||
if (err(kclCircle3Point) || kclCircle3Point.program === null)
|
||||
return kclManager.ast
|
||||
if (kclCircle3Point.program.body[0].type !== 'ExpressionStatement')
|
||||
return kclManager.ast
|
||||
if (
|
||||
kclCircle3Point.program.body[0].expression.type !== 'CallExpressionKw'
|
||||
)
|
||||
return kclManager.ast
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
const kclCircle3PointArgs =
|
||||
kclCircle3Point.program.body[0].expression.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 already dealing with a PipeExpression.
|
||||
// Modify the current one.
|
||||
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 === 'PipeExpression' &&
|
||||
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(
|
||||
@ -1397,8 +1522,18 @@ export class SceneEntities {
|
||||
// The user was off their mark! Missed the object to select.
|
||||
if (!target) return
|
||||
|
||||
target.position.copy(args.intersectionPoint.threeD)
|
||||
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
|
||||
@ -1407,45 +1542,19 @@ export class SceneEntities {
|
||||
if (points.size >= 3) return
|
||||
if (!args.intersectionPoint) return
|
||||
|
||||
const id = createPoint(args.intersectionPoint.threeD)
|
||||
points.set(id, args.intersectionPoint.twoD)
|
||||
|
||||
if (points.size < 2) return
|
||||
|
||||
// We've now got 3 points, let's create our circle!
|
||||
const astSnapshot = structuredClone(kclManager.ast)
|
||||
let nodeQueryResult
|
||||
nodeQueryResult = getNodeFromPath<VariableDeclaration>(
|
||||
astSnapshot,
|
||||
startSketchOnASTNodePath,
|
||||
'VariableDeclaration'
|
||||
const pointMesh = createPoint(
|
||||
new Vector3(
|
||||
args.intersectionPoint.twoD.x,
|
||||
args.intersectionPoint.twoD.y,
|
||||
0
|
||||
)
|
||||
)
|
||||
if (err(nodeQueryResult)) return Promise.reject(nodeQueryResult)
|
||||
const startSketchOnASTNode = nodeQueryResult
|
||||
groupOfDrafts.add(pointMesh)
|
||||
points.set(pointMesh.id, args.intersectionPoint.twoD)
|
||||
|
||||
const circleParams = circle3Point(Array.from(points.values()))
|
||||
if (points.size <= 2) return
|
||||
|
||||
if (!circleParams) return
|
||||
|
||||
const kclCircle3Point = parse(`circle({
|
||||
center = [${circleParams.center.x}, ${circleParams.center.y}],
|
||||
radius = ${circleParams.radius},
|
||||
}, %)`)
|
||||
|
||||
if (err(kclCircle3Point) || kclCircle3Point.program === null) return
|
||||
if (kclCircle3Point.program.body[0].type !== 'ExpressionStatement')
|
||||
return
|
||||
|
||||
const clonedStartSketchOnASTNode = structuredClone(startSketchOnASTNode)
|
||||
startSketchOnASTNode.node.declaration.init = createPipeExpression([
|
||||
clonedStartSketchOnASTNode.node.declaration.init,
|
||||
kclCircle3Point.program.body[0].expression,
|
||||
])
|
||||
|
||||
await kclManager.executeAstMock(astSnapshot)
|
||||
await codeManager.updateEditorWithAstAndWriteToFile(astSnapshot)
|
||||
|
||||
done()
|
||||
await updateCircle3Point()
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@ -9,6 +9,9 @@ import {
|
||||
ExtrudeGeometry,
|
||||
Group,
|
||||
LineCurve3,
|
||||
LineBasicMaterial,
|
||||
LineDashedMaterial,
|
||||
Line,
|
||||
Mesh,
|
||||
MeshBasicMaterial,
|
||||
NormalBufferAttributes,
|
||||
@ -1003,6 +1006,49 @@ export function createArcGeometry({
|
||||
return geo
|
||||
}
|
||||
|
||||
// (lee) The above is much more complex than necessary.
|
||||
// I've derived the new code from:
|
||||
// https://threejs.org/docs/#api/en/extras/curves/EllipseCurve
|
||||
// I'm not sure why it wasn't done like this in the first place?
|
||||
// I don't touch the code above because it may break something else.
|
||||
export function createCircleGeometry({
|
||||
center,
|
||||
radius,
|
||||
color,
|
||||
isDashed = false,
|
||||
scale = 1,
|
||||
}: {
|
||||
center: Coords2d
|
||||
radius: number
|
||||
color: number
|
||||
isDashed?: boolean
|
||||
scale?: number
|
||||
}): Line {
|
||||
const circle = new EllipseCurve(
|
||||
center[0],
|
||||
center[1],
|
||||
radius,
|
||||
radius,
|
||||
0,
|
||||
Math.PI * 2,
|
||||
true,
|
||||
scale
|
||||
)
|
||||
const points = circle.getPoints(75) // just enough points to not see edges.
|
||||
const geometry = new BufferGeometry().setFromPoints(points)
|
||||
const material = !isDashed
|
||||
? new LineBasicMaterial({ color })
|
||||
: new LineDashedMaterial({
|
||||
color,
|
||||
scale,
|
||||
dashSize: 6,
|
||||
gapSize: 6,
|
||||
})
|
||||
const line = new Line(geometry, material)
|
||||
line.computeLineDistances()
|
||||
return line
|
||||
}
|
||||
|
||||
export function dashedStraight(
|
||||
from: Coords2d,
|
||||
to: Coords2d,
|
||||
|
||||
@ -5,6 +5,7 @@ import { Command } from 'lib/commandTypes'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { CustomIcon } from './CustomIcon'
|
||||
import { getActorNextEvents } from 'lib/utils'
|
||||
import { sortCommands } from 'lib/commandUtils'
|
||||
|
||||
function CommandComboBox({
|
||||
options,
|
||||
@ -19,8 +20,16 @@ function CommandComboBox({
|
||||
|
||||
const defaultOption =
|
||||
options.find((o) => 'isCurrent' in o && o.isCurrent) || null
|
||||
// sort disabled commands to the bottom
|
||||
const sortedOptions = options
|
||||
.map((command) => ({
|
||||
command,
|
||||
disabled: optionIsDisabled(command),
|
||||
}))
|
||||
.sort(sortCommands)
|
||||
.map(({ command }) => command)
|
||||
|
||||
const fuse = new Fuse(options, {
|
||||
const fuse = new Fuse(sortedOptions, {
|
||||
keys: ['displayName', 'name', 'description'],
|
||||
threshold: 0.3,
|
||||
ignoreLocation: true,
|
||||
@ -28,7 +37,7 @@ function CommandComboBox({
|
||||
|
||||
useEffect(() => {
|
||||
const results = fuse.search(query).map((result) => result.item)
|
||||
setFilteredOptions(query.length > 0 ? results : options)
|
||||
setFilteredOptions(query.length > 0 ? results : sortedOptions)
|
||||
}, [query])
|
||||
|
||||
function handleSelection(command: Command) {
|
||||
|
||||
@ -111,7 +111,7 @@ export const ModelingMachineProvider = ({
|
||||
auth,
|
||||
settings: {
|
||||
context: {
|
||||
app: { theme, enableSSAO },
|
||||
app: { theme, enableSSAO, allowOrbitInSketchMode },
|
||||
modeling: {
|
||||
defaultUnit,
|
||||
cameraProjection,
|
||||
@ -121,6 +121,7 @@ export const ModelingMachineProvider = ({
|
||||
},
|
||||
},
|
||||
} = useSettingsAuthContext()
|
||||
const previousAllowOrbitInSketchMode = useRef(allowOrbitInSketchMode.current)
|
||||
const navigate = useNavigate()
|
||||
const { context, send: fileMachineSend } = useFileContext()
|
||||
const { file } = useLoaderData() as IndexLoaderData
|
||||
@ -634,7 +635,8 @@ export const ModelingMachineProvider = ({
|
||||
input.plane
|
||||
)
|
||||
await kclManager.updateAst(modifiedAst, false)
|
||||
sceneInfra.camControls.enableRotate = false
|
||||
sceneInfra.camControls.enableRotate =
|
||||
sceneInfra.camControls._setting_allowOrbitInSketchMode
|
||||
sceneInfra.camControls.syncDirection = 'clientToEngine'
|
||||
|
||||
await letEngineAnimateAndSyncCamAfter(
|
||||
@ -647,6 +649,7 @@ export const ModelingMachineProvider = ({
|
||||
zAxis: input.zAxis,
|
||||
yAxis: input.yAxis,
|
||||
origin: [0, 0, 0],
|
||||
animateTargetId: input.planeId,
|
||||
}
|
||||
}),
|
||||
'animate-to-sketch': fromPromise(
|
||||
@ -671,6 +674,7 @@ export const ModelingMachineProvider = ({
|
||||
origin: info.sketchDetails.origin.map(
|
||||
(a) => a / sceneInfra._baseUnitMultiplier
|
||||
) as [number, number, number],
|
||||
animateTargetId: info?.sketchDetails?.faceId || '',
|
||||
}
|
||||
}
|
||||
),
|
||||
@ -1188,6 +1192,41 @@ export const ModelingMachineProvider = ({
|
||||
}
|
||||
}, [engineCommandManager.engineConnection, modelingSend])
|
||||
|
||||
useEffect(() => {
|
||||
// Only trigger this if the state actually changes, if it stays the same do not reload the camera
|
||||
if (
|
||||
previousAllowOrbitInSketchMode.current === allowOrbitInSketchMode.current
|
||||
) {
|
||||
//no op
|
||||
previousAllowOrbitInSketchMode.current = allowOrbitInSketchMode.current
|
||||
return
|
||||
}
|
||||
const inSketchMode = modelingState.matches('Sketch')
|
||||
|
||||
// If you are in sketch mode and you disable the orbit, return back to the normal view to the target
|
||||
if (!allowOrbitInSketchMode.current) {
|
||||
const targetId = modelingState.context.sketchDetails?.animateTargetId
|
||||
if (inSketchMode && targetId) {
|
||||
letEngineAnimateAndSyncCamAfter(engineCommandManager, targetId)
|
||||
.then(() => {})
|
||||
.catch((e) => {
|
||||
console.error(
|
||||
'failed to sync engine and client scene after disabling allow orbit in sketch mode'
|
||||
)
|
||||
console.error(e)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// While you are in sketch mode you should be able to control the enable rotate
|
||||
// Once you exit it goes back to normal
|
||||
if (inSketchMode) {
|
||||
sceneInfra.camControls.enableRotate = allowOrbitInSketchMode.current
|
||||
}
|
||||
|
||||
previousAllowOrbitInSketchMode.current = allowOrbitInSketchMode.current
|
||||
}, [allowOrbitInSketchMode])
|
||||
|
||||
// Allow using the delete key to delete solids
|
||||
useHotkeys(['backspace', 'delete', 'del'], () => {
|
||||
modelingSend({ type: 'Delete selection' })
|
||||
|
||||
@ -137,6 +137,11 @@ export const SettingsAuthProviderBase = ({
|
||||
sceneInfra.theme = opposingTheme
|
||||
sceneEntitiesManager.updateSegmentBaseColor(opposingTheme)
|
||||
},
|
||||
setAllowOrbitInSketchMode: ({ context }) => {
|
||||
sceneInfra.camControls._setting_allowOrbitInSketchMode =
|
||||
context.app.allowOrbitInSketchMode.current
|
||||
// ModelingMachineProvider will do a use effect to trigger the camera engine sync
|
||||
},
|
||||
toastSuccess: ({ event }) => {
|
||||
if (!('data' in event)) return
|
||||
const eventParts = event.type.replace(/^set./, '').split('.') as [
|
||||
|
||||
49
src/lib/commandUtils.test.ts
Normal file
49
src/lib/commandUtils.test.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { CommandWithDisabledState, sortCommands } from './commandUtils'
|
||||
|
||||
function commandWithDisabled(
|
||||
name: string,
|
||||
disabled: boolean,
|
||||
groupId = 'modeling'
|
||||
): CommandWithDisabledState {
|
||||
return {
|
||||
command: {
|
||||
name,
|
||||
groupId,
|
||||
needsReview: false,
|
||||
onSubmit: () => {},
|
||||
},
|
||||
disabled,
|
||||
}
|
||||
}
|
||||
|
||||
describe('Command sorting', () => {
|
||||
it(`Puts modeling commands first`, () => {
|
||||
const initial = [
|
||||
commandWithDisabled('a', false, 'settings'),
|
||||
commandWithDisabled('b', false, 'modeling'),
|
||||
commandWithDisabled('c', false, 'settings'),
|
||||
]
|
||||
const sorted = initial.sort(sortCommands)
|
||||
expect(sorted[0].command.groupId).toBe('modeling')
|
||||
})
|
||||
|
||||
it(`Puts disabled commands last`, () => {
|
||||
const initial = [
|
||||
commandWithDisabled('a', true, 'modeling'),
|
||||
commandWithDisabled('z', false, 'modeling'),
|
||||
commandWithDisabled('a', false, 'settings'),
|
||||
]
|
||||
const sorted = initial.sort(sortCommands)
|
||||
expect(sorted[sorted.length - 1].disabled).toBe(true)
|
||||
})
|
||||
|
||||
it(`Puts settings commands second to last`, () => {
|
||||
const initial = [
|
||||
commandWithDisabled('a', true, 'modeling'),
|
||||
commandWithDisabled('z', false, 'modeling'),
|
||||
commandWithDisabled('a', false, 'settings'),
|
||||
]
|
||||
const sorted = initial.sort(sortCommands)
|
||||
expect(sorted[1].command.groupId).toBe('settings')
|
||||
})
|
||||
})
|
||||
@ -2,6 +2,9 @@
|
||||
// That object also contains some metadata about what to do with the KCL expression,
|
||||
// such as whether we need to create a new variable for it.
|
||||
// This function extracts the value field from those arg payloads and returns
|
||||
|
||||
import { Command } from './commandTypes'
|
||||
|
||||
// The arg object with all its field as natural values that the command to be executed will expect.
|
||||
export function getCommandArgumentKclValuesOnly(args: Record<string, unknown>) {
|
||||
return Object.fromEntries(
|
||||
@ -13,3 +16,42 @@ export function getCommandArgumentKclValuesOnly(args: Record<string, unknown>) {
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export interface CommandWithDisabledState {
|
||||
command: Command
|
||||
disabled: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorting logic for commands in the command combo box.
|
||||
*/
|
||||
export function sortCommands(
|
||||
a: CommandWithDisabledState,
|
||||
b: CommandWithDisabledState
|
||||
) {
|
||||
// Disabled commands should be at the bottom
|
||||
if (a.disabled && !b.disabled) {
|
||||
return 1
|
||||
}
|
||||
if (b.disabled && !a.disabled) {
|
||||
return -1
|
||||
}
|
||||
// Settings commands should be next-to-last
|
||||
if (a.command.groupId === 'settings' && b.command.groupId !== 'settings') {
|
||||
return 1
|
||||
}
|
||||
if (b.command.groupId === 'settings' && a.command.groupId !== 'settings') {
|
||||
return -1
|
||||
}
|
||||
// Modeling commands should be first
|
||||
if (a.command.groupId === 'modeling' && b.command.groupId !== 'modeling') {
|
||||
return -1
|
||||
}
|
||||
if (b.command.groupId === 'modeling' && a.command.groupId !== 'modeling') {
|
||||
return 1
|
||||
}
|
||||
// Sort alphabetically
|
||||
return (a.command.displayName || a.command.name).localeCompare(
|
||||
b.command.displayName || b.command.name
|
||||
)
|
||||
}
|
||||
|
||||
@ -190,6 +190,14 @@ export function createSettings() {
|
||||
inputType: 'boolean',
|
||||
},
|
||||
}),
|
||||
allowOrbitInSketchMode: new Setting<boolean>({
|
||||
defaultValue: false,
|
||||
description: 'Toggle free camera while in sketch mode',
|
||||
validate: (v) => typeof v === 'boolean',
|
||||
commandConfig: {
|
||||
inputType: 'boolean',
|
||||
},
|
||||
}),
|
||||
onboardingStatus: new Setting<OnboardingStatus>({
|
||||
defaultValue: '',
|
||||
// TODO: this could be better but we don't have a TS side real enum
|
||||
|
||||
@ -41,6 +41,8 @@ export function configurationToSettingsPayload(
|
||||
onboardingStatus: configuration?.settings?.app?.onboarding_status,
|
||||
dismissWebBanner: configuration?.settings?.app?.dismiss_web_banner,
|
||||
streamIdleMode: configuration?.settings?.app?.stream_idle_mode,
|
||||
allowOrbitInSketchMode:
|
||||
configuration?.settings?.app?.allow_orbit_in_sketch_mode,
|
||||
projectDirectory: configuration?.settings?.project?.directory,
|
||||
enableSSAO: configuration?.settings?.modeling?.enable_ssao,
|
||||
},
|
||||
@ -80,6 +82,8 @@ export function projectConfigurationToSettingsPayload(
|
||||
onboardingStatus: configuration?.settings?.app?.onboarding_status,
|
||||
dismissWebBanner: configuration?.settings?.app?.dismiss_web_banner,
|
||||
streamIdleMode: configuration?.settings?.app?.stream_idle_mode,
|
||||
allowOrbitInSketchMode:
|
||||
configuration?.settings?.app?.allow_orbit_in_sketch_mode,
|
||||
enableSSAO: configuration?.settings?.modeling?.enable_ssao,
|
||||
},
|
||||
modeling: {
|
||||
|
||||
@ -460,18 +460,16 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
||||
disabled: (state) =>
|
||||
state.matches('Sketch no face') ||
|
||||
(!canRectangleOrCircleTool(state.context) &&
|
||||
!state.matches({ Sketch: 'Circle tool' })),
|
||||
isActive: (state) => state.matches({ Sketch: 'Circle tool' }),
|
||||
!state.matches({ Sketch: 'Circle tool' }) &&
|
||||
!state.matches({ Sketch: 'circle3PointToolSelect' })),
|
||||
isActive: (state) =>
|
||||
state.matches({ Sketch: 'Circle tool' }) ||
|
||||
state.matches({ Sketch: 'circle3PointToolSelect' }),
|
||||
hotkey: (state) =>
|
||||
state.matches({ Sketch: 'Circle tool' }) ? ['Esc', 'C'] : 'C',
|
||||
showTitle: false,
|
||||
description: 'Start drawing a circle from its center',
|
||||
links: [
|
||||
{
|
||||
label: 'GitHub issue',
|
||||
url: 'https://github.com/KittyCAD/modeling-app/issues/1501',
|
||||
},
|
||||
],
|
||||
links: [],
|
||||
},
|
||||
{
|
||||
id: 'circle-three-points',
|
||||
@ -488,7 +486,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
||||
}),
|
||||
icon: 'circle',
|
||||
status: 'available',
|
||||
title: 'Three-point circle',
|
||||
title: '3-point circle',
|
||||
showTitle: false,
|
||||
description: 'Draw a circle defined by three points',
|
||||
links: [],
|
||||
|
||||
@ -133,6 +133,8 @@ export interface SketchDetails {
|
||||
zAxis: [number, number, number]
|
||||
yAxis: [number, number, number]
|
||||
origin: [number, number, number]
|
||||
// face id or plane id, both are strings
|
||||
animateTargetId?: string
|
||||
}
|
||||
|
||||
export interface SegmentOverlay {
|
||||
@ -422,6 +424,8 @@ export const modelingMachine = setup({
|
||||
},
|
||||
'is editing existing sketch': ({ context: { sketchDetails } }) =>
|
||||
isEditingExistingSketch({ sketchDetails }),
|
||||
'is editing 3-point circle': ({ context: { sketchDetails } }) =>
|
||||
isEditing3PointCircle({ sketchDetails }),
|
||||
'Can make selection horizontal': ({ context: { selectionRanges } }) => {
|
||||
const info = horzVertInfo(selectionRanges, 'horizontal')
|
||||
if (trap(info)) return false
|
||||
@ -2187,6 +2191,10 @@ export const modelingMachine = setup({
|
||||
target: 'SketchIdle',
|
||||
guard: 'is editing existing sketch',
|
||||
},
|
||||
{
|
||||
target: 'circle3PointToolSelect',
|
||||
guard: 'is editing 3-point circle',
|
||||
},
|
||||
'Line tool',
|
||||
],
|
||||
},
|
||||
@ -2518,13 +2526,8 @@ export const modelingMachine = setup({
|
||||
circle3PointToolSelect: {
|
||||
invoke: {
|
||||
id: 'actor-circle-3-point',
|
||||
input: function ({ context, event }) {
|
||||
// These are not really necessary but I believe they are needed
|
||||
// to satisfy TypeScript type narrowing or undefined check.
|
||||
if (event.type !== 'change tool') return
|
||||
if (event.data?.tool !== 'circle3Points') return
|
||||
input: function ({ context }) {
|
||||
if (!context.sketchDetails) return
|
||||
|
||||
return context.sketchDetails
|
||||
},
|
||||
src: 'actorCircle3Point',
|
||||
@ -2782,6 +2785,34 @@ export function isEditingExistingSketch({
|
||||
)
|
||||
return (hasStartProfileAt && pipeExpression.body.length > 2) || hasCircle
|
||||
}
|
||||
export function isEditing3PointCircle({
|
||||
sketchDetails,
|
||||
}: {
|
||||
sketchDetails: SketchDetails | null
|
||||
}): boolean {
|
||||
if (!sketchDetails?.sketchPathToNode) return false
|
||||
const variableDeclaration = getNodeFromPath<VariableDeclarator>(
|
||||
kclManager.ast,
|
||||
sketchDetails.sketchPathToNode,
|
||||
'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({
|
||||
sketchDetails,
|
||||
}: {
|
||||
@ -2802,6 +2833,27 @@ export function pipeHasCircle({
|
||||
)
|
||||
return hasCircle
|
||||
}
|
||||
export function pipeHasCircleThreePoint({
|
||||
sketchDetails,
|
||||
}: {
|
||||
sketchDetails: SketchDetails | null
|
||||
}): boolean {
|
||||
if (!sketchDetails?.sketchPathToNode) return false
|
||||
const variableDeclaration = getNodeFromPath<VariableDeclarator>(
|
||||
kclManager.ast,
|
||||
sketchDetails.sketchPathToNode,
|
||||
'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 hasCircle = pipeExpression.body.some(
|
||||
(item) =>
|
||||
item.type === 'CallExpression' && item.callee.name === 'circleThreePoint'
|
||||
)
|
||||
return hasCircle
|
||||
}
|
||||
|
||||
export function canRectangleOrCircleTool({
|
||||
sketchDetails,
|
||||
|
||||
@ -43,6 +43,7 @@ export const settingsMachine = setup({
|
||||
'Execute AST': () => {},
|
||||
toastSuccess: () => {},
|
||||
setClientSideSceneUnits: () => {},
|
||||
setAllowOrbitInSketchMode: () => {},
|
||||
persistSettings: () => {},
|
||||
resetSettings: assign(({ context, event }) => {
|
||||
if (!('level' in event)) return {}
|
||||
@ -157,6 +158,15 @@ export const settingsMachine = setup({
|
||||
actions: ['setSettingAtLevel', 'toastSuccess'],
|
||||
},
|
||||
|
||||
'set.app.allowOrbitInSketchMode': {
|
||||
target: 'persisting settings',
|
||||
actions: [
|
||||
'setSettingAtLevel',
|
||||
'toastSuccess',
|
||||
'setAllowOrbitInSketchMode',
|
||||
],
|
||||
},
|
||||
|
||||
'set.modeling.cameraProjection': {
|
||||
target: 'persisting settings',
|
||||
|
||||
@ -183,6 +193,7 @@ export const settingsMachine = setup({
|
||||
'setClientSideSceneUnits',
|
||||
'Execute AST',
|
||||
'setClientTheme',
|
||||
'setAllowOrbitInSketchMode',
|
||||
],
|
||||
},
|
||||
|
||||
@ -194,6 +205,7 @@ export const settingsMachine = setup({
|
||||
'setClientSideSceneUnits',
|
||||
'Execute AST',
|
||||
'setClientTheme',
|
||||
'setAllowOrbitInSketchMode',
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
@ -815,7 +815,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new(&ctx.settings)).await {
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", #fn_name, #index),
|
||||
|
||||
@ -14,7 +14,10 @@ mod test_examples_someFn {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "someFn", 0usize),
|
||||
|
||||
@ -14,7 +14,10 @@ mod test_examples_someFn {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "someFn", 0usize),
|
||||
|
||||
@ -15,7 +15,10 @@ mod test_examples_show {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "show", 0usize),
|
||||
@ -69,7 +72,10 @@ mod test_examples_show {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "show", 1usize),
|
||||
|
||||
@ -15,7 +15,10 @@ mod test_examples_show {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "show", 0usize),
|
||||
|
||||
@ -16,7 +16,10 @@ mod test_examples_my_func {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "my_func", 0usize),
|
||||
@ -70,7 +73,10 @@ mod test_examples_my_func {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "my_func", 1usize),
|
||||
|
||||
@ -16,7 +16,10 @@ mod test_examples_line_to {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "line_to", 0usize),
|
||||
@ -70,7 +73,10 @@ mod test_examples_line_to {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "line_to", 1usize),
|
||||
|
||||
@ -15,7 +15,10 @@ mod test_examples_min {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "min", 0usize),
|
||||
@ -69,7 +72,10 @@ mod test_examples_min {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "min", 1usize),
|
||||
|
||||
@ -15,7 +15,10 @@ mod test_examples_show {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "show", 0usize),
|
||||
|
||||
@ -15,7 +15,10 @@ mod test_examples_import {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "import", 0usize),
|
||||
|
||||
@ -15,7 +15,10 @@ mod test_examples_import {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "import", 0usize),
|
||||
|
||||
@ -15,7 +15,10 @@ mod test_examples_import {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "import", 0usize),
|
||||
|
||||
@ -15,7 +15,10 @@ mod test_examples_show {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "show", 0usize),
|
||||
|
||||
@ -14,7 +14,10 @@ mod test_examples_some_function {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "some_function", 0usize),
|
||||
|
||||
@ -164,7 +164,7 @@ async fn snapshot_endpoint(body: Bytes, state: ExecutorContext) -> Response<Body
|
||||
};
|
||||
|
||||
eprintln!("Executing {test_name}");
|
||||
let mut exec_state = ExecState::new();
|
||||
let mut exec_state = ExecState::new(&state.settings);
|
||||
// This is a shitty source range, I don't know what else to use for it though.
|
||||
// There's no actual KCL associated with this reset_scene call.
|
||||
if let Err(e) = state
|
||||
|
||||
@ -16,7 +16,7 @@ pub async fn kcl_to_engine_core(code: &str) -> Result<String> {
|
||||
let ctx = ExecutorContext::new_forwarded_mock(Arc::new(Box::new(
|
||||
crate::conn_mock_core::EngineConnection::new(ref_result).await?,
|
||||
)));
|
||||
ctx.run(program.into(), &mut ExecState::new()).await?;
|
||||
ctx.run(program.into(), &mut ExecState::new(&ctx.settings)).await?;
|
||||
|
||||
let result = result.lock().expect("mutex lock").clone();
|
||||
Ok(result)
|
||||
|
||||
@ -30,13 +30,16 @@ impl From<KclErrorWithOutputs> for ExecError {
|
||||
#[derive(Debug)]
|
||||
pub struct ExecErrorWithState {
|
||||
pub error: ExecError,
|
||||
pub exec_state: crate::ExecState,
|
||||
pub exec_state: Option<crate::ExecState>,
|
||||
}
|
||||
|
||||
impl ExecErrorWithState {
|
||||
#[cfg_attr(target_arch = "wasm32", expect(dead_code))]
|
||||
pub fn new(error: ExecError, exec_state: crate::ExecState) -> Self {
|
||||
Self { error, exec_state }
|
||||
Self {
|
||||
error,
|
||||
exec_state: Some(exec_state),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,7 +47,7 @@ impl From<ExecError> for ExecErrorWithState {
|
||||
fn from(error: ExecError) -> Self {
|
||||
Self {
|
||||
error,
|
||||
exec_state: Default::default(),
|
||||
exec_state: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -53,7 +56,7 @@ impl From<ConnectionError> for ExecErrorWithState {
|
||||
fn from(error: ConnectionError) -> Self {
|
||||
Self {
|
||||
error: error.into(),
|
||||
exec_state: Default::default(),
|
||||
exec_state: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -601,10 +601,24 @@ impl TryFrom<NumericSuffix> for UnitLen {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
|
||||
impl From<crate::UnitLength> for UnitLen {
|
||||
fn from(unit: crate::UnitLength) -> Self {
|
||||
match unit {
|
||||
crate::UnitLength::Cm => UnitLen::Cm,
|
||||
crate::UnitLength::Ft => UnitLen::Feet,
|
||||
crate::UnitLength::In => UnitLen::Inches,
|
||||
crate::UnitLength::M => UnitLen::M,
|
||||
crate::UnitLength::Mm => UnitLen::Mm,
|
||||
crate::UnitLength::Yd => UnitLen::Yards,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum UnitAngle {
|
||||
#[default]
|
||||
Degrees,
|
||||
Radians,
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ type Point2D = kcmc::shared::Point2d<f64>;
|
||||
type Point3D = kcmc::shared::Point3d<f64>;
|
||||
|
||||
pub use function_param::FunctionParam;
|
||||
pub use kcl_value::{KclObjectFields, KclValue};
|
||||
pub use kcl_value::{KclObjectFields, KclValue, UnitAngle, UnitLen};
|
||||
use uuid::Uuid;
|
||||
|
||||
mod annotations;
|
||||
@ -77,7 +77,7 @@ pub struct GlobalState {
|
||||
pub artifact_commands: Vec<ArtifactCommand>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ModuleState {
|
||||
/// Program variable bindings.
|
||||
@ -115,21 +115,15 @@ pub struct ExecOutcome {
|
||||
pub artifact_commands: Vec<ArtifactCommand>,
|
||||
}
|
||||
|
||||
impl Default for ExecState {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecState {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(exec_settings: &ExecutorSettings) -> Self {
|
||||
ExecState {
|
||||
global: GlobalState::new(),
|
||||
mod_local: ModuleState::default(),
|
||||
mod_local: ModuleState::new(exec_settings),
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
fn reset(&mut self, exec_settings: &ExecutorSettings) {
|
||||
let mut id_generator = self.global.id_generator.clone();
|
||||
// We do not pop the ids, since we want to keep the same id generator.
|
||||
// This is for the front end to keep track of the ids.
|
||||
@ -140,7 +134,7 @@ impl ExecState {
|
||||
|
||||
*self = ExecState {
|
||||
global,
|
||||
mod_local: ModuleState::default(),
|
||||
mod_local: ModuleState::new(exec_settings),
|
||||
};
|
||||
}
|
||||
|
||||
@ -204,6 +198,14 @@ impl ExecState {
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
pub fn length_unit(&self) -> UnitLen {
|
||||
self.mod_local.settings.default_length_units
|
||||
}
|
||||
|
||||
pub fn angle_unit(&self) -> UnitAngle {
|
||||
self.mod_local.settings.default_angle_units
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobalState {
|
||||
@ -232,6 +234,23 @@ impl GlobalState {
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleState {
|
||||
fn new(exec_settings: &ExecutorSettings) -> Self {
|
||||
ModuleState {
|
||||
memory: Default::default(),
|
||||
dynamic_state: Default::default(),
|
||||
pipe_value: Default::default(),
|
||||
module_exports: Default::default(),
|
||||
import_stack: Default::default(),
|
||||
operations: Default::default(),
|
||||
settings: MetaSettings {
|
||||
default_length_units: exec_settings.units.into(),
|
||||
default_angle_units: Default::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@ -240,15 +259,6 @@ pub struct MetaSettings {
|
||||
pub default_angle_units: kcl_value::UnitAngle,
|
||||
}
|
||||
|
||||
impl Default for MetaSettings {
|
||||
fn default() -> Self {
|
||||
MetaSettings {
|
||||
default_length_units: kcl_value::UnitLen::Mm,
|
||||
default_angle_units: kcl_value::UnitAngle::Degrees,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MetaSettings {
|
||||
fn update_from_annotation(&mut self, annotation: &NonCodeValue, source_range: SourceRange) -> Result<(), KclError> {
|
||||
let properties = annotations::expect_properties(annotations::SETTINGS, annotation, source_range)?;
|
||||
@ -1711,7 +1721,7 @@ pub struct ExecutorContext {
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
pub struct ExecutorSettings {
|
||||
/// The unit to use in modeling dimensions.
|
||||
/// The project-default unit to use in modeling dimensions.
|
||||
pub units: UnitLength,
|
||||
/// Highlight edges of 3D objects?
|
||||
pub highlight_edges: bool,
|
||||
@ -2214,7 +2224,7 @@ impl ExecutorContext {
|
||||
|
||||
if cache_result.clear_scene && !self.is_mock() {
|
||||
// Pop the execution state, since we are starting fresh.
|
||||
exec_state.reset();
|
||||
exec_state.reset(&self.settings);
|
||||
|
||||
// We don't do this in mock mode since there is no engine connection
|
||||
// anyways and from the TS side we override memory and don't want to clear it.
|
||||
@ -2457,7 +2467,7 @@ impl ExecutorContext {
|
||||
|
||||
let mut local_state = ModuleState {
|
||||
import_stack: exec_state.mod_local.import_stack.clone(),
|
||||
..Default::default()
|
||||
..ModuleState::new(&self.settings)
|
||||
};
|
||||
local_state.import_stack.push(info.path.clone());
|
||||
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
|
||||
@ -2830,7 +2840,7 @@ mod tests {
|
||||
settings: Default::default(),
|
||||
context_type: ContextType::Mock,
|
||||
};
|
||||
let mut exec_state = ExecState::default();
|
||||
let mut exec_state = ExecState::new(&ctx.settings);
|
||||
ctx.run(program.clone().into(), &mut exec_state).await?;
|
||||
|
||||
Ok((program, ctx, exec_state))
|
||||
@ -3302,6 +3312,25 @@ let shape = layer() |> patternTransform(10, transform, %)
|
||||
assert_eq!(7.4, mem_get_json(exec_state.memory(), "thing").as_f64().unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_unit_default() {
|
||||
let ast = r#"const inMm = 25.4 * mm()
|
||||
const inInches = 1.0 * inch()"#;
|
||||
let (_, _, exec_state) = parse_execute(ast).await.unwrap();
|
||||
assert_eq!(25.4, mem_get_json(exec_state.memory(), "inMm").as_f64().unwrap());
|
||||
assert_eq!(25.4, mem_get_json(exec_state.memory(), "inInches").as_f64().unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_unit_overriden() {
|
||||
let ast = r#"@settings(defaultLengthUnit = inch)
|
||||
const inMm = 25.4 * mm()
|
||||
const inInches = 1.0 * inch()"#;
|
||||
let (_, _, exec_state) = parse_execute(ast).await.unwrap();
|
||||
assert_eq!(1.0, mem_get_json(exec_state.memory(), "inMm").as_f64().unwrap().round());
|
||||
assert_eq!(1.0, mem_get_json(exec_state.memory(), "inInches").as_f64().unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_zero_param_fn() {
|
||||
let ast = r#"const sigmaAllow = 35000 // psi
|
||||
@ -4045,7 +4074,7 @@ shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#;
|
||||
.unwrap();
|
||||
let old_program = crate::Program::parse_no_errs(code).unwrap();
|
||||
// Execute the program.
|
||||
let mut exec_state = Default::default();
|
||||
let mut exec_state = ExecState::new(&ctx.settings);
|
||||
let cache_info = crate::CacheInformation {
|
||||
old: None,
|
||||
new_ast: old_program.ast.clone(),
|
||||
|
||||
@ -70,7 +70,7 @@ mod settings;
|
||||
#[cfg(test)]
|
||||
mod simulation_tests;
|
||||
mod source_range;
|
||||
mod std;
|
||||
pub mod std;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod test_server;
|
||||
mod thread;
|
||||
@ -84,7 +84,7 @@ pub use engine::{EngineManager, ExecutionKind};
|
||||
pub use errors::{CompilationError, ConnectionError, ExecError, KclError, KclErrorWithOutputs};
|
||||
pub use execution::{
|
||||
cache::{CacheInformation, OldAstState},
|
||||
ExecState, ExecutorContext, ExecutorSettings,
|
||||
ExecState, ExecutorContext, ExecutorSettings, Point2d,
|
||||
};
|
||||
pub use lsp::{
|
||||
copilot::Backend as CopilotLspBackend,
|
||||
|
||||
@ -49,7 +49,7 @@ use crate::{
|
||||
token::TokenStream,
|
||||
PIPE_OPERATOR,
|
||||
},
|
||||
CacheInformation, ModuleId, OldAstState, Program, SourceRange,
|
||||
CacheInformation, ExecState, ModuleId, OldAstState, Program, SourceRange,
|
||||
};
|
||||
const SEMANTIC_TOKEN_TYPES: [SemanticTokenType; 10] = [
|
||||
SemanticTokenType::NUMBER,
|
||||
@ -693,7 +693,7 @@ impl Backend {
|
||||
let mut exec_state = if let Some(last_successful_ast_state) = last_successful_ast_state.clone() {
|
||||
last_successful_ast_state.exec_state
|
||||
} else {
|
||||
Default::default()
|
||||
ExecState::new(&executor_ctx.settings)
|
||||
};
|
||||
|
||||
if let Err(err) = executor_ctx
|
||||
|
||||
@ -121,6 +121,9 @@ pub struct AppSettings {
|
||||
/// When the user is idle, and this is true, the stream will be torn down.
|
||||
#[serde(default, alias = "streamIdleMode", skip_serializing_if = "is_default")]
|
||||
stream_idle_mode: bool,
|
||||
/// When the user is idle, and this is true, the stream will be torn down.
|
||||
#[serde(default, alias = "allowOrbitInSketchMode", skip_serializing_if = "is_default")]
|
||||
allow_orbit_in_sketch_mode: bool,
|
||||
}
|
||||
|
||||
// TODO: When we remove backwards compatibility with the old settings file, we can remove this.
|
||||
@ -586,6 +589,7 @@ textWrapping = true
|
||||
dismiss_web_banner: false,
|
||||
enable_ssao: None,
|
||||
stream_idle_mode: false,
|
||||
allow_orbit_in_sketch_mode: false,
|
||||
},
|
||||
modeling: ModelingSettings {
|
||||
base_unit: UnitLength::In,
|
||||
@ -647,6 +651,7 @@ includeSettings = false
|
||||
dismiss_web_banner: false,
|
||||
enable_ssao: None,
|
||||
stream_idle_mode: false,
|
||||
allow_orbit_in_sketch_mode: false,
|
||||
},
|
||||
modeling: ModelingSettings {
|
||||
base_unit: UnitLength::Yd,
|
||||
@ -713,6 +718,7 @@ defaultProjectName = "projects-$nnn"
|
||||
dismiss_web_banner: false,
|
||||
enable_ssao: None,
|
||||
stream_idle_mode: false,
|
||||
allow_orbit_in_sketch_mode: false,
|
||||
},
|
||||
modeling: ModelingSettings {
|
||||
base_unit: UnitLength::Yd,
|
||||
@ -791,6 +797,7 @@ projectDirectory = "/Users/macinatormax/Documents/kittycad-modeling-projects""#;
|
||||
dismiss_web_banner: false,
|
||||
enable_ssao: None,
|
||||
stream_idle_mode: false,
|
||||
allow_orbit_in_sketch_mode: false,
|
||||
},
|
||||
modeling: ModelingSettings {
|
||||
base_unit: UnitLength::Mm,
|
||||
|
||||
@ -124,6 +124,7 @@ includeSettings = false
|
||||
dismiss_web_banner: false,
|
||||
enable_ssao: None,
|
||||
stream_idle_mode: false,
|
||||
allow_orbit_in_sketch_mode: false,
|
||||
},
|
||||
modeling: ModelingSettings {
|
||||
base_unit: UnitLength::Yd,
|
||||
|
||||
@ -55,6 +55,10 @@ impl KwArgs {
|
||||
pub fn len(&self) -> usize {
|
||||
self.labeled.len() + if self.unlabeled.is_some() { 1 } else { 0 }
|
||||
}
|
||||
/// Are there no arguments?
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.labeled.len() == 0 && self.unlabeled.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -92,25 +96,6 @@ impl Args {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) async fn new_test_args() -> Result<Self> {
|
||||
use std::sync::Arc;
|
||||
|
||||
Ok(Self {
|
||||
args: Vec::new(),
|
||||
kw_args: Default::default(),
|
||||
source_range: SourceRange::default(),
|
||||
ctx: ExecutorContext {
|
||||
engine: Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await?)),
|
||||
fs: Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
},
|
||||
pipe_value: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a keyword argument. If not set, returns None.
|
||||
pub(crate) fn get_kw_arg_opt<'a, T>(&'a self, label: &str) -> Option<T>
|
||||
where
|
||||
|
||||
@ -5,14 +5,13 @@ use derive_docs::stdlib;
|
||||
|
||||
use crate::{
|
||||
errors::KclError,
|
||||
execution::{ExecState, KclValue},
|
||||
settings::types::UnitLength,
|
||||
execution::{ExecState, KclValue, UnitLen},
|
||||
std::Args,
|
||||
};
|
||||
|
||||
/// Millimeters conversion factor for current projects units.
|
||||
pub async fn mm(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let result = inner_mm(&args)?;
|
||||
pub async fn mm(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let result = inner_mm(exec_state)?;
|
||||
|
||||
Ok(args.make_user_val_from_f64(result))
|
||||
}
|
||||
@ -40,20 +39,20 @@ pub async fn mm(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
|
||||
name = "mm",
|
||||
tags = ["units"],
|
||||
}]
|
||||
fn inner_mm(args: &Args) -> Result<f64, KclError> {
|
||||
match args.ctx.settings.units {
|
||||
UnitLength::Mm => Ok(1.0),
|
||||
UnitLength::In => Ok(measurements::Length::from_millimeters(1.0).as_inches()),
|
||||
UnitLength::Ft => Ok(measurements::Length::from_millimeters(1.0).as_feet()),
|
||||
UnitLength::M => Ok(measurements::Length::from_millimeters(1.0).as_meters()),
|
||||
UnitLength::Cm => Ok(measurements::Length::from_millimeters(1.0).as_centimeters()),
|
||||
UnitLength::Yd => Ok(measurements::Length::from_millimeters(1.0).as_yards()),
|
||||
fn inner_mm(exec_state: &ExecState) -> Result<f64, KclError> {
|
||||
match exec_state.length_unit() {
|
||||
UnitLen::Mm => Ok(1.0),
|
||||
UnitLen::Inches => Ok(measurements::Length::from_millimeters(1.0).as_inches()),
|
||||
UnitLen::Feet => Ok(measurements::Length::from_millimeters(1.0).as_feet()),
|
||||
UnitLen::M => Ok(measurements::Length::from_millimeters(1.0).as_meters()),
|
||||
UnitLen::Cm => Ok(measurements::Length::from_millimeters(1.0).as_centimeters()),
|
||||
UnitLen::Yards => Ok(measurements::Length::from_millimeters(1.0).as_yards()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Inches conversion factor for current projects units.
|
||||
pub async fn inch(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let result = inner_inch(&args)?;
|
||||
pub async fn inch(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let result = inner_inch(exec_state)?;
|
||||
|
||||
Ok(args.make_user_val_from_f64(result))
|
||||
}
|
||||
@ -81,20 +80,20 @@ pub async fn inch(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
name = "inch",
|
||||
tags = ["units"],
|
||||
}]
|
||||
fn inner_inch(args: &Args) -> Result<f64, KclError> {
|
||||
match args.ctx.settings.units {
|
||||
UnitLength::Mm => Ok(measurements::Length::from_inches(1.0).as_millimeters()),
|
||||
UnitLength::In => Ok(1.0),
|
||||
UnitLength::Ft => Ok(measurements::Length::from_inches(1.0).as_feet()),
|
||||
UnitLength::M => Ok(measurements::Length::from_inches(1.0).as_meters()),
|
||||
UnitLength::Cm => Ok(measurements::Length::from_inches(1.0).as_centimeters()),
|
||||
UnitLength::Yd => Ok(measurements::Length::from_inches(1.0).as_yards()),
|
||||
fn inner_inch(exec_state: &ExecState) -> Result<f64, KclError> {
|
||||
match exec_state.length_unit() {
|
||||
UnitLen::Mm => Ok(measurements::Length::from_inches(1.0).as_millimeters()),
|
||||
UnitLen::Inches => Ok(1.0),
|
||||
UnitLen::Feet => Ok(measurements::Length::from_inches(1.0).as_feet()),
|
||||
UnitLen::M => Ok(measurements::Length::from_inches(1.0).as_meters()),
|
||||
UnitLen::Cm => Ok(measurements::Length::from_inches(1.0).as_centimeters()),
|
||||
UnitLen::Yards => Ok(measurements::Length::from_inches(1.0).as_yards()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Feet conversion factor for current projects units.
|
||||
pub async fn ft(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let result = inner_ft(&args)?;
|
||||
pub async fn ft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let result = inner_ft(exec_state)?;
|
||||
|
||||
Ok(args.make_user_val_from_f64(result))
|
||||
}
|
||||
@ -123,20 +122,20 @@ pub async fn ft(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
|
||||
name = "ft",
|
||||
tags = ["units"],
|
||||
}]
|
||||
fn inner_ft(args: &Args) -> Result<f64, KclError> {
|
||||
match args.ctx.settings.units {
|
||||
UnitLength::Mm => Ok(measurements::Length::from_feet(1.0).as_millimeters()),
|
||||
UnitLength::In => Ok(measurements::Length::from_feet(1.0).as_inches()),
|
||||
UnitLength::Ft => Ok(1.0),
|
||||
UnitLength::M => Ok(measurements::Length::from_feet(1.0).as_meters()),
|
||||
UnitLength::Cm => Ok(measurements::Length::from_feet(1.0).as_centimeters()),
|
||||
UnitLength::Yd => Ok(measurements::Length::from_feet(1.0).as_yards()),
|
||||
fn inner_ft(exec_state: &ExecState) -> Result<f64, KclError> {
|
||||
match exec_state.length_unit() {
|
||||
UnitLen::Mm => Ok(measurements::Length::from_feet(1.0).as_millimeters()),
|
||||
UnitLen::Inches => Ok(measurements::Length::from_feet(1.0).as_inches()),
|
||||
UnitLen::Feet => Ok(1.0),
|
||||
UnitLen::M => Ok(measurements::Length::from_feet(1.0).as_meters()),
|
||||
UnitLen::Cm => Ok(measurements::Length::from_feet(1.0).as_centimeters()),
|
||||
UnitLen::Yards => Ok(measurements::Length::from_feet(1.0).as_yards()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Meters conversion factor for current projects units.
|
||||
pub async fn m(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let result = inner_m(&args)?;
|
||||
pub async fn m(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let result = inner_m(exec_state)?;
|
||||
|
||||
Ok(args.make_user_val_from_f64(result))
|
||||
}
|
||||
@ -165,20 +164,20 @@ pub async fn m(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclE
|
||||
name = "m",
|
||||
tags = ["units"],
|
||||
}]
|
||||
fn inner_m(args: &Args) -> Result<f64, KclError> {
|
||||
match args.ctx.settings.units {
|
||||
UnitLength::Mm => Ok(measurements::Length::from_meters(1.0).as_millimeters()),
|
||||
UnitLength::In => Ok(measurements::Length::from_meters(1.0).as_inches()),
|
||||
UnitLength::Ft => Ok(measurements::Length::from_meters(1.0).as_feet()),
|
||||
UnitLength::M => Ok(1.0),
|
||||
UnitLength::Cm => Ok(measurements::Length::from_meters(1.0).as_centimeters()),
|
||||
UnitLength::Yd => Ok(measurements::Length::from_meters(1.0).as_yards()),
|
||||
fn inner_m(exec_state: &ExecState) -> Result<f64, KclError> {
|
||||
match exec_state.length_unit() {
|
||||
UnitLen::Mm => Ok(measurements::Length::from_meters(1.0).as_millimeters()),
|
||||
UnitLen::Inches => Ok(measurements::Length::from_meters(1.0).as_inches()),
|
||||
UnitLen::Feet => Ok(measurements::Length::from_meters(1.0).as_feet()),
|
||||
UnitLen::M => Ok(1.0),
|
||||
UnitLen::Cm => Ok(measurements::Length::from_meters(1.0).as_centimeters()),
|
||||
UnitLen::Yards => Ok(measurements::Length::from_meters(1.0).as_yards()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Centimeters conversion factor for current projects units.
|
||||
pub async fn cm(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let result = inner_cm(&args)?;
|
||||
pub async fn cm(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let result = inner_cm(exec_state)?;
|
||||
|
||||
Ok(args.make_user_val_from_f64(result))
|
||||
}
|
||||
@ -207,20 +206,20 @@ pub async fn cm(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
|
||||
name = "cm",
|
||||
tags = ["units"],
|
||||
}]
|
||||
fn inner_cm(args: &Args) -> Result<f64, KclError> {
|
||||
match args.ctx.settings.units {
|
||||
UnitLength::Mm => Ok(measurements::Length::from_centimeters(1.0).as_millimeters()),
|
||||
UnitLength::In => Ok(measurements::Length::from_centimeters(1.0).as_inches()),
|
||||
UnitLength::Ft => Ok(measurements::Length::from_centimeters(1.0).as_feet()),
|
||||
UnitLength::M => Ok(measurements::Length::from_centimeters(1.0).as_meters()),
|
||||
UnitLength::Cm => Ok(1.0),
|
||||
UnitLength::Yd => Ok(measurements::Length::from_centimeters(1.0).as_yards()),
|
||||
fn inner_cm(exec_state: &ExecState) -> Result<f64, KclError> {
|
||||
match exec_state.length_unit() {
|
||||
UnitLen::Mm => Ok(measurements::Length::from_centimeters(1.0).as_millimeters()),
|
||||
UnitLen::Inches => Ok(measurements::Length::from_centimeters(1.0).as_inches()),
|
||||
UnitLen::Feet => Ok(measurements::Length::from_centimeters(1.0).as_feet()),
|
||||
UnitLen::M => Ok(measurements::Length::from_centimeters(1.0).as_meters()),
|
||||
UnitLen::Cm => Ok(1.0),
|
||||
UnitLen::Yards => Ok(measurements::Length::from_centimeters(1.0).as_yards()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Yards conversion factor for current projects units.
|
||||
pub async fn yd(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let result = inner_yd(&args)?;
|
||||
pub async fn yd(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let result = inner_yd(exec_state)?;
|
||||
|
||||
Ok(args.make_user_val_from_f64(result))
|
||||
}
|
||||
@ -249,92 +248,13 @@ pub async fn yd(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
|
||||
name = "yd",
|
||||
tags = ["units"],
|
||||
}]
|
||||
fn inner_yd(args: &Args) -> Result<f64, KclError> {
|
||||
match args.ctx.settings.units {
|
||||
UnitLength::Mm => Ok(measurements::Length::from_yards(1.0).as_millimeters()),
|
||||
UnitLength::In => Ok(measurements::Length::from_yards(1.0).as_inches()),
|
||||
UnitLength::Ft => Ok(measurements::Length::from_yards(1.0).as_feet()),
|
||||
UnitLength::M => Ok(measurements::Length::from_yards(1.0).as_meters()),
|
||||
UnitLength::Cm => Ok(measurements::Length::from_yards(1.0).as_centimeters()),
|
||||
UnitLength::Yd => Ok(1.0),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_units_inner_mm() {
|
||||
let mut args = Args::new_test_args().await.unwrap();
|
||||
args.ctx.settings.units = UnitLength::Mm;
|
||||
let result = inner_mm(&args).unwrap();
|
||||
assert_eq!(result, 1.0);
|
||||
|
||||
args.ctx.settings.units = UnitLength::In;
|
||||
let result = inner_mm(&args).unwrap();
|
||||
assert_eq!(result, 1.0 / 25.4);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_units_inner_inch() {
|
||||
let mut args = Args::new_test_args().await.unwrap();
|
||||
args.ctx.settings.units = UnitLength::In;
|
||||
let result = inner_inch(&args).unwrap();
|
||||
assert_eq!(result, 1.0);
|
||||
|
||||
args.ctx.settings.units = UnitLength::Mm;
|
||||
let result = inner_inch(&args).unwrap();
|
||||
assert_eq!(result, 25.4);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_units_inner_ft() {
|
||||
let mut args = Args::new_test_args().await.unwrap();
|
||||
args.ctx.settings.units = UnitLength::Ft;
|
||||
let result = inner_ft(&args).unwrap();
|
||||
assert_eq!(result, 1.0);
|
||||
|
||||
args.ctx.settings.units = UnitLength::Mm;
|
||||
let result = inner_ft(&args).unwrap();
|
||||
assert_eq!(result, 304.8);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_units_inner_m() {
|
||||
let mut args = Args::new_test_args().await.unwrap();
|
||||
args.ctx.settings.units = UnitLength::M;
|
||||
let result = inner_m(&args).unwrap();
|
||||
assert_eq!(result, 1.0);
|
||||
|
||||
args.ctx.settings.units = UnitLength::Mm;
|
||||
let result = inner_m(&args).unwrap();
|
||||
assert_eq!(result, 1000.0);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_units_inner_cm() {
|
||||
let mut args = Args::new_test_args().await.unwrap();
|
||||
args.ctx.settings.units = UnitLength::Cm;
|
||||
let result = inner_cm(&args).unwrap();
|
||||
assert_eq!(result, 1.0);
|
||||
|
||||
args.ctx.settings.units = UnitLength::Mm;
|
||||
let result = inner_cm(&args).unwrap();
|
||||
assert_eq!(result, 10.0);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_units_inner_yd() {
|
||||
let mut args = Args::new_test_args().await.unwrap();
|
||||
args.ctx.settings.units = UnitLength::Yd;
|
||||
let result = inner_yd(&args).unwrap();
|
||||
assert_eq!(result, 1.0);
|
||||
|
||||
args.ctx.settings.units = UnitLength::Mm;
|
||||
let result = inner_yd(&args).unwrap();
|
||||
assert_eq!(result, 914.4);
|
||||
fn inner_yd(exec_state: &ExecState) -> Result<f64, KclError> {
|
||||
match exec_state.length_unit() {
|
||||
UnitLen::Mm => Ok(measurements::Length::from_yards(1.0).as_millimeters()),
|
||||
UnitLen::Inches => Ok(measurements::Length::from_yards(1.0).as_inches()),
|
||||
UnitLen::Feet => Ok(measurements::Length::from_yards(1.0).as_feet()),
|
||||
UnitLen::M => Ok(measurements::Length::from_yards(1.0).as_meters()),
|
||||
UnitLen::Cm => Ok(measurements::Length::from_yards(1.0).as_centimeters()),
|
||||
UnitLen::Yards => Ok(1.0),
|
||||
}
|
||||
}
|
||||
|
||||
@ -270,6 +270,19 @@ pub fn calculate_circle_center(p1: [f64; 2], p2: [f64; 2], p3: [f64; 2]) -> [f64
|
||||
[x, y]
|
||||
}
|
||||
|
||||
pub struct CircleParams {
|
||||
pub center: Point2d,
|
||||
pub radius: f64,
|
||||
}
|
||||
|
||||
pub fn calculate_circle_from_3_points(points: [Point2d; 3]) -> CircleParams {
|
||||
let center: Point2d = calculate_circle_center(points[0].into(), points[1].into(), points[2].into()).into();
|
||||
CircleParams {
|
||||
center,
|
||||
radius: distance(center, points[1]),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// Here you can bring your functions into scope
|
||||
|
||||
@ -6,7 +6,7 @@ use crate::{
|
||||
errors::ExecErrorWithState,
|
||||
execution::{new_zoo_client, ArtifactCommand, ExecutorContext, ExecutorSettings, Operation, ProgramMemory},
|
||||
settings::types::UnitLength,
|
||||
ConnectionError, ExecError, KclErrorWithOutputs, Program,
|
||||
ConnectionError, ExecError, ExecState, KclErrorWithOutputs, Program,
|
||||
};
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
@ -72,7 +72,7 @@ async fn do_execute_and_snapshot(
|
||||
ctx: &ExecutorContext,
|
||||
program: Program,
|
||||
) -> Result<(crate::execution::ExecState, image::DynamicImage), ExecErrorWithState> {
|
||||
let mut exec_state = Default::default();
|
||||
let mut exec_state = ExecState::new(&ctx.settings);
|
||||
let snapshot_png_bytes = ctx
|
||||
.execute_and_prepare_snapshot(&program, &mut exec_state)
|
||||
.await
|
||||
|
||||
@ -5,7 +5,7 @@ use std::sync::Arc;
|
||||
use futures::stream::TryStreamExt;
|
||||
use gloo_utils::format::JsValueSerdeExt;
|
||||
use kcl_lib::{
|
||||
exec::IdGenerator, CacheInformation, CoreDump, EngineManager, ExecState, ModuleId, OldAstState, Program,
|
||||
exec::IdGenerator, CacheInformation, CoreDump, EngineManager, ExecState, ModuleId, OldAstState, Point2d, Program,
|
||||
};
|
||||
use tokio::sync::RwLock;
|
||||
use tower_lsp::{LspService, Server};
|
||||
@ -80,7 +80,7 @@ pub async fn execute(
|
||||
kcl_lib::ExecutorContext::new(engine_manager, fs_manager, settings.into()).await?
|
||||
};
|
||||
|
||||
let mut exec_state = ExecState::default();
|
||||
let mut exec_state = ExecState::new(&ctx.settings);
|
||||
let mut old_ast_memory = None;
|
||||
|
||||
// Populate from the old exec state if it exists.
|
||||
@ -576,3 +576,26 @@ pub fn base64_decode(input: &str) -> Result<Vec<u8>, JsValue> {
|
||||
|
||||
Err(JsValue::from_str("Invalid base64 encoding"))
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct WasmCircleParams {
|
||||
pub center_x: f64,
|
||||
pub center_y: f64,
|
||||
pub radius: f64,
|
||||
}
|
||||
|
||||
/// Calculate a circle from 3 points.
|
||||
#[wasm_bindgen]
|
||||
pub fn calculate_circle_from_3_points(ax: f64, ay: f64, bx: f64, by: f64, cx: f64, cy: f64) -> WasmCircleParams {
|
||||
let result = kcl_lib::std::utils::calculate_circle_from_3_points([
|
||||
Point2d { x: ax, y: ay },
|
||||
Point2d { x: bx, y: by },
|
||||
Point2d { x: cx, y: cy },
|
||||
]);
|
||||
|
||||
WasmCircleParams {
|
||||
center_x: result.center.x,
|
||||
center_y: result.center.y,
|
||||
radius: result.radius,
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ async fn cache_test(
|
||||
.ok_or_else(|| anyhow::anyhow!("No variations provided for test '{}'", test_name))?;
|
||||
|
||||
let mut ctx = kcl_lib::ExecutorContext::new_with_client(first.settings.clone(), None, None).await?;
|
||||
let mut exec_state = kcl_lib::ExecState::default();
|
||||
let mut exec_state = kcl_lib::ExecState::new(&ctx.settings);
|
||||
|
||||
let mut old_ast_state = None;
|
||||
let mut img_results = Vec::new();
|
||||
|
||||
@ -10,7 +10,7 @@ use pretty_assertions::assert_eq;
|
||||
async fn setup(code: &str, name: &str) -> Result<(ExecutorContext, Program, ModuleId, uuid::Uuid)> {
|
||||
let program = Program::parse_no_errs(code)?;
|
||||
let ctx = kcl_lib::ExecutorContext::new_with_default_client(Default::default()).await?;
|
||||
let mut exec_state = ExecState::default();
|
||||
let mut exec_state = ExecState::new(&ctx.settings);
|
||||
ctx.run(program.clone().into(), &mut exec_state).await?;
|
||||
|
||||
// We need to get the sketch ID.
|
||||
|
||||
Reference in New Issue
Block a user