Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3af0437
first steps of new tools: center and corner rectangles
andrewvarga Jan 12, 2026
5d28283
fix overlay icon for rectangle tools
andrewvarga Jan 12, 2026
f0f5098
Center rectangle tool adding first point (not working yet)
andrewvarga Jan 12, 2026
b4666e3
center rectangle tool now draws a line successfully
andrewvarga Jan 12, 2026
028e7ca
Merge branch 'main' into andrewvarga/9273/center-and-corner-rectangles
andrewvarga Jan 12, 2026
5d2d7f5
Merge branch 'main' into andrewvarga/9273/center-and-corner-rectangles
andrewvarga Jan 12, 2026
67ff12a
center rect tool somewhat works now by editing lines directly instead…
andrewvarga Jan 13, 2026
6bc7c72
Merge branch 'main' into andrewvarga/9273/center-and-corner-rectangles
andrewvarga Jan 13, 2026
add2739
center rectangle tool fixes
andrewvarga Jan 14, 2026
cfc8323
cleanups
andrewvarga Jan 14, 2026
cb42f95
Merge branch 'main' into andrewvarga/9273/center-and-corner-rectangles
andrewvarga Jan 14, 2026
89d81e3
clean up point editing
andrewvarga Jan 14, 2026
718b836
rectTool fixes
andrewvarga Jan 14, 2026
5dbaec3
fmt
andrewvarga Jan 14, 2026
a0365ec
Merge branch 'main' into andrewvarga/9273/center-and-corner-rectangles
andrewvarga Jan 14, 2026
8755727
small cleanups
andrewvarga Jan 14, 2026
b6126b3
Merge branch 'main' into andrewvarga/9273/center-and-corner-rectangles
andrewvarga Jan 15, 2026
f88027b
rename rectOriginMode -> toolVariant, introduce ToolInput type
andrewvarga Jan 15, 2026
2e20bb0
lint
andrewvarga Jan 15, 2026
646bcc0
Merge branch 'main' into andrewvarga/9273/center-and-corner-rectangles
andrewvarga Jan 15, 2026
ec8cfaf
fix delete_segments if both line and its points are deleted
andrewvarga Jan 15, 2026
92467e2
center rectangle icon added for center rectangle tool
andrewvarga Jan 15, 2026
688b42e
add tests in rectTool.spec.ts
andrewvarga Jan 16, 2026
f15c486
renamings in rectTool
andrewvarga Jan 16, 2026
98a5817
collapse nested ifs into &&
andrewvarga Jan 16, 2026
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
25 changes: 23 additions & 2 deletions rust/kcl-lib/src/frontend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -416,15 +416,36 @@ impl SketchApi for FrontendState {
// Deduplicate IDs.
let mut constraint_ids_set = constraint_ids.into_iter().collect::<AhashIndexSet<_>>();
let segment_ids_set = segment_ids.into_iter().collect::<AhashIndexSet<_>>();

// If a point is owned by a Line/Arc, we want to delete the owner, which will
// also delete the point, as well as other points that are owned by the owner.
let mut delete_ids = AhashIndexSet::default();

for segment_id in segment_ids_set.iter().copied() {
if let Some(segment_object) = self.scene_graph.objects.get(segment_id.0)
&& let ObjectKind::Segment { segment } = &segment_object.kind
&& let Segment::Point(point) = segment
&& let Some(owner_id) = point.owner
&& let Some(owner_object) = self.scene_graph.objects.get(owner_id.0)
&& let ObjectKind::Segment { segment: owner_segment } = &owner_object.kind
&& matches!(owner_segment, Segment::Line(_) | Segment::Arc(_))
{
// segment is owned -> delete the owner
delete_ids.insert(owner_id);
} else {
// segment is not owned by anything -> can be deleted
delete_ids.insert(segment_id);
}
}
// Find constraints that reference the segments to be deleted, and add
// those to the set to be deleted.
self.add_dependent_constraints_to_delete(sketch, &segment_ids_set, &mut constraint_ids_set)?;
self.add_dependent_constraints_to_delete(sketch, &delete_ids, &mut constraint_ids_set)?;

let mut new_ast = self.program.ast.clone();
for constraint_id in constraint_ids_set {
self.delete_constraint(&mut new_ast, sketch, constraint_id)?;
}
for segment_id in segment_ids_set {
for segment_id in delete_ids {
self.delete_segment(&mut new_ast, sketch, segment_id)?;
}
self.execute_after_edit(
Expand Down
16 changes: 16 additions & 0 deletions src/components/CustomIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1523,6 +1523,22 @@ const CustomIconMap = Object.freeze({
/>
</svg>
),
rectangleCenter: (
<svg
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-label="rectangle center"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M16 5H4V15H16V5ZM4 4H3V5V15V16H4H16H17V15V5V4H16H4Z"
fill="currentColor"
/>
<circle cx="10" cy="10" r="1.5" fill="currentColor" />
</svg>
),
refresh: (
<svg
viewBox="0 0 20 20"
Expand Down
63 changes: 59 additions & 4 deletions src/lib/toolbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1314,6 +1314,53 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
state.matches('sketchSolveMode') &&
state.context.sketchSolveToolName === 'centerArcTool',
},
{
id: 'rectangles',
array: [
{
id: 'corner-rectangle',
onClick: ({ modelingSend, isActive }) =>
isActive
? modelingSend({
type: 'unequip tool',
})
: modelingSend({
type: 'equip tool',
data: { tool: 'cornerRectTool' },
}),
icon: 'rectangle',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@franknoirot I'm thinking we could have separate icons for corner and center rectangles, like a simple dot in the center for the center rectangle and keep corner rectangle the same? It's a small thing I can do even but it would make it more obvious which one is currently selected.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a simple one in 92467e2 but let me know if you have something else in mind

status: 'available',
title: 'Corner Rectangle',
hotkey: 'Shift+R',
description: 'Start drawing a rectangle',
links: [],
isActive: (state) =>
state.matches('sketchSolveMode') &&
state.context.sketchSolveToolName === 'cornerRectTool',
},
{
id: 'center-rectangle',
onClick: ({ modelingSend, isActive }) =>
isActive
? modelingSend({
type: 'unequip tool',
})
: modelingSend({
type: 'equip tool',
data: { tool: 'centerRectTool' },
}),
icon: 'rectangleCenter',
status: 'available',
title: 'Center Rectangle',
hotkey: 'Alt+R',
description: 'Start drawing a rectangle from its center',
links: [],
isActive: (state) =>
state.matches('sketchSolveMode') &&
state.context.sketchSolveToolName === 'centerRectTool',
},
],
},
'break',
{
id: 'coincident',
Expand Down Expand Up @@ -1425,13 +1472,23 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
export function getSketchSolveToolIconMap(): Record<string, CustomIconName> {
const map: Record<string, CustomIconName> = {}
const items = toolbarConfig.sketchSolve.items
collectItems(items, map)
return map
}

function collectItems(
items: ToolbarMode['items'],
map: Record<string, CustomIconName>
) {
for (const item of items) {
// Skip 'break' strings
if (typeof item === 'string') continue

// Skip dropdowns (which don't have direct icons)
if ('array' in item) continue
// dropdowns, eg. rectangles
if ('array' in item) {
collectItems(item.array, map)
continue
}

// Now TypeScript knows item is ToolbarItem
// Only process items that have an icon and an isActive function (which indicates it's a tool)
Expand All @@ -1447,6 +1504,4 @@ export function getSketchSolveToolIconMap(): Record<string, CustomIconName> {
}
}
}

return map
}
29 changes: 20 additions & 9 deletions src/machines/sketchSolve/sketchSolveImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import type { SceneEntities } from '@src/clientSideScene/sceneEntities'
import type RustContext from '@src/lib/rustContext'
import type { KclManager } from '@src/lang/KclManager'

import { machine as centerRectTool } from '@src/machines/sketchSolve/tools/centerRectTool'
import { machine as rectTool } from '@src/machines/sketchSolve/tools/rectTool'
import { machine as dimensionTool } from '@src/machines/sketchSolve/tools/dimensionTool'
import { machine as pointTool } from '@src/machines/sketchSolve/tools/pointTool'
import { machine as lineTool } from '@src/machines/sketchSolve/tools/lineToolDiagram'
Expand Down Expand Up @@ -59,12 +59,7 @@ export type SpawnToolActor = <K extends EquipTool>(
src: K,
options?: {
id?: string
input?: {
sceneInfra: SceneInfra
rustContext: RustContext
kclManager: KclManager
sketchId: number
}
input?: ToolInput
}
) => ActorRefFrom<(typeof equipTools)[K]>

Expand Down Expand Up @@ -116,7 +111,7 @@ export type SketchSolveMachineEvent =

type ToolActorRef =
| ActorRefFrom<typeof dimensionTool>
| ActorRefFrom<typeof centerRectTool>
| ActorRefFrom<typeof rectTool>
| ActorRefFrom<typeof pointTool>
| ActorRefFrom<typeof lineTool>
| ActorRefFrom<typeof centerArcTool>
Expand Down Expand Up @@ -144,7 +139,9 @@ export type SketchSolveContext = {
kclManager: KclManager
}
export const equipTools = Object.freeze({
centerRectTool,
// both use the same tool, opened with a different flag
centerRectTool: rectTool,
cornerRectTool: rectTool,
dimensionTool,
pointTool,
lineTool,
Expand Down Expand Up @@ -902,6 +899,7 @@ export function spawnTool(
rustContext: context.rustContext,
kclManager: context.kclManager,
sketchId: context.sketchId,
toolVariant: toolVariants[nameOfToolToSpawn],
},
})

Expand All @@ -911,3 +909,16 @@ export function spawnTool(
pendingToolName: undefined, // Clear the pending tool after spawning
}
}

export type ToolInput = {
sceneInfra: SceneInfra
rustContext: RustContext
kclManager: KclManager
sketchId: number
toolVariant?: string // eg. 'corner' | 'center' for rectTool
}

const toolVariants: Record<string, string> = {
centerRectTool: 'center',
cornerRectTool: 'corner',
}
11 changes: 2 additions & 9 deletions src/machines/sketchSolve/tools/centerArcToolDiagram.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { assertEvent, assign, fromPromise, setup } from 'xstate'

import type { SceneInfra } from '@src/clientSideScene/sceneInfra'
import type RustContext from '@src/lib/rustContext'
import type { SceneGraphDelta } from '@rust/kcl-lib/bindings/FrontendApi'
import type { KclManager } from '@src/lang/KclManager'
import {
type ToolEvents,
type ToolContext,
Expand All @@ -19,6 +16,7 @@ import {
finalizeArcActor,
storeCreatedArcResult,
} from '@src/machines/sketchSolve/tools/centerArcToolImpl'
import type { ToolInput } from '@src/machines/sketchSolve/sketchSolveImpl'

// This might seem a bit redundant, but this xstate visualizer stops working
// when TOOL_ID and constants are imported directly
Expand All @@ -32,12 +30,7 @@ export const machine = setup({
types: {
context: {} as ToolContext,
events: {} as ToolEvents,
input: {} as {
sceneInfra: SceneInfra
rustContext: RustContext
kclManager: KclManager
sketchId: number
},
input: {} as ToolInput,
},
actions: {
'show radius preview listener': showRadiusPreviewListener,
Expand Down
84 changes: 0 additions & 84 deletions src/machines/sketchSolve/tools/centerRectTool.ts

This file was deleted.

9 changes: 2 additions & 7 deletions src/machines/sketchSolve/tools/lineToolDiagram.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { assertEvent, assign, fromPromise, setup } from 'xstate'

import type { SceneInfra } from '@src/clientSideScene/sceneInfra'
import type RustContext from '@src/lib/rustContext'
import { jsAppSettings } from '@src/lib/settings/settingsUtils'
import type {
Expand All @@ -23,6 +22,7 @@ import {
storePendingSketchOutcome,
sendStoredResultToParent,
} from '@src/machines/sketchSolve/tools/lineToolImpl'
import type { ToolInput } from '@src/machines/sketchSolve/sketchSolveImpl'

// This might seem a bit redundant, but this xstate visualizer stops working
// when TOOL_ID and constants are imported directly
Expand All @@ -35,12 +35,7 @@ export const machine = setup({
types: {
context: {} as ToolContext,
events: {} as ToolEvents,
input: {} as {
sceneInfra: SceneInfra
rustContext: RustContext
kclManager: KclManager
sketchId: number
},
input: {} as ToolInput,
},
actions: {
'animate draft segment listener': animateDraftSegmentListener,
Expand Down
8 changes: 2 additions & 6 deletions src/machines/sketchSolve/tools/pointTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { roundOff } from '@src/lib/utils'
import { baseUnitToNumericSuffix } from '@src/lang/wasm'
import type { KclManager } from '@src/lang/KclManager'
import type { BaseToolEvent } from '@src/machines/sketchSolve/tools/sharedToolTypes'
import type { ToolInput } from '@src/machines/sketchSolve/sketchSolveImpl'

const TOOL_ID = 'Point tool'
const CONFIRMING_DIMENSIONS = 'Confirming dimensions'
Expand All @@ -34,12 +35,7 @@ export const machine = setup({
sketchId: number
},
events: {} as ToolEvents,
input: {} as {
sceneInfra: SceneInfra
rustContext: RustContext
kclManager: KclManager
sketchId: number
},
input: {} as ToolInput,
},
actions: {
'add point listener': ({ self, context }) => {
Expand Down
Loading
Loading