Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3-point circle interactive component #4982

Merged
merged 11 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
341 changes: 225 additions & 116 deletions src/clientSideScene/sceneEntities.ts

Large diffs are not rendered by default.

46 changes: 46 additions & 0 deletions src/clientSideScene/segments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import {
ExtrudeGeometry,
Group,
LineCurve3,
LineBasicMaterial,
LineDashedMaterial,
Line,
Mesh,
MeshBasicMaterial,
NormalBufferAttributes,
Expand Down Expand Up @@ -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,
Expand Down
16 changes: 7 additions & 9 deletions src/lib/toolbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,18 +451,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',
Expand All @@ -479,7 +477,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: [],
Expand Down
62 changes: 56 additions & 6 deletions src/machines/modelingMachine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,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
Expand Down Expand Up @@ -2187,6 +2189,10 @@ export const modelingMachine = setup({
target: 'SketchIdle',
guard: 'is editing existing sketch',
},
{
target: 'circle3PointToolSelect',
guard: 'is editing 3-point circle',
},
'Line tool',
],
},
Expand Down Expand Up @@ -2518,13 +2524,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',
Expand Down Expand Up @@ -2782,6 +2783,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,
}: {
Expand All @@ -2802,6 +2831,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,
Expand Down
4 changes: 2 additions & 2 deletions src/wasm-lib/kcl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions src/wasm-lib/kcl/src/std/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
13 changes: 13 additions & 0 deletions src/wasm-lib/kcl/src/std/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 24 additions & 1 deletion src/wasm-lib/src/wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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,
}
}
Loading