Skip to content
Draft
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
7 changes: 7 additions & 0 deletions rust/kcl-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ impl SceneGraphDelta {
#[ts(export)]
pub struct SourceDelta {}

#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS)]
#[ts(export)]
// TODO not sure if this needs to be file name and content?
pub struct KclSource {
pub text: String,
Copy link
Contributor

Choose a reason for hiding this comment

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

For the TODO, this is probably fine for now. We can't code-mod or refactor in imported files yet.

But the way I see it, this type and SourceDelta are redundant. They're the same and should be merged into one. I don't really care what we name it. Since this is the kcl-api crate, it seems a little redundant to call it "KCL source". But I get that the prefix probably makes it easier in TypeScript so that you don't need to alias it.

}

#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Deserialize, Serialize, ts_rs::TS)]
#[ts(export, rename = "ApiObjectId")]
pub struct ObjectId(pub usize);
Expand Down
173 changes: 171 additions & 2 deletions rust/kcl-api/src/sketch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
sketch: ObjectId,
segment: SegmentCtor,
label: Option<String>,
) -> Result<(SourceDelta, SceneGraphDelta)>;
) -> Result<(KclSource, SketchExecOutcome)>;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This might be a temp change, if we want to stick with SceneGraphDelta?

I can see that NRC had made Sketch which is similar SketchExecOutcome

image

But is all ObjectIds, so guess I would still need a map of all of the objects?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should discuss this in our next call. I have a clearer understanding of it all now that I've implemented some of it, but I'm still on the fence about which way we should go.

Objects are analogous to runtime artifacts, i.e. those in the artifact graph. So it makes sense that if it's referencing segments, they would be ObjectIds pointing to them, not the Objects themselves. And yes, execution will build up a mapping from ObjectId to Object and provide a way to look things up.


async fn edit_segment(
&self,
Expand Down Expand Up @@ -114,7 +114,7 @@
#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS)]
#[ts(export)]
pub enum SegmentCtor {
Point(Point2d<Expr>),
Point(PointCtor),
Line(LineCtor),
MidPointLine(MidPointLineCtor),
Arc(ArcCtor),
Expand All @@ -124,6 +124,12 @@
ThreePointCircle(ThreePointCircleCtor),
}

#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS)]
#[ts(export)]
pub struct PointCtor {
pub position: Point2d<Expr>,
}

#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS)]
#[ts(export, rename = "ApiPoint2d")]
pub struct Point2d<U: std::fmt::Debug + Clone + ts_rs::TS> {
Expand Down Expand Up @@ -246,3 +252,166 @@
lines: Vec<ObjectId>,
distance: Option<Number>,
}

#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS)]
#[ts(export)]
pub struct SketchExecOutcome {
// The solved segments, including their locations so that they can be drawn.
pub segments: Vec<SolveSegment>,
// The interpreted constraints.
pub constraints: Vec<SolveConstraint>,
}

#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS)]
#[ts(export)]
pub enum SolveSegment {
Point(SolvePointSegment),
}

#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS)]
#[ts(export)]
pub struct SolvePointSegment {
pub object_id: String,
pub constrained_status: ConstrainedStatus,
pub handles: Vec<PointHandle>,
pub position: Point2d<Number>,
}

#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS)]
#[ts(export)]
pub enum ConstrainedStatus {
Copy link
Contributor

Choose a reason for hiding this comment

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

This type is redundant/should be merged with Freedom in this file above.

None,
Partial,
Full,
}

// Handles, i.e. UI elements that the user can interact with.
#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS)]
#[ts(export)]
pub struct PointHandle {
pub position: Point2d<Number>,
}

#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS)]
#[ts(export)]

Check warning on line 296 in rust/kcl-api/src/sketch.rs

View workflow job for this annotation

GitHub Actions / cargo fmt

Diff in /home/runner/work/modeling-app/modeling-app/rust/kcl-api/src/sketch.rs
pub enum SolveConstraint {
// e.g. Make these things coincident.
Relation { kind: RelationKind, segment_ids: Vec<String> },
// If segment2 is given, it's the perpendicular distance between them.
Dimension {
segment1_id: String,
segment2_id: Option<String>,
value: Number,
},
Angle {
segment1_id: String,
segment2_id: Option<String>,
value: Number,
},
}

#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS)]
#[ts(export)]
pub enum RelationKind {
Coincidence,
Horizontal,
Vertical,
Tangent,
Equal,
Fixed,
}

// Stub implementation of SketchApi for testing/development
pub struct SketchApiStub;

impl SketchApi for SketchApiStub {
async fn new_sketch(
&self,
_project: ProjectId,
_file: FileId,
_version: Version,
_args: SketchArgs,
) -> Result<(SourceDelta, SceneGraphDelta, ObjectId)> {
todo!("new_sketch not implemented")
}

async fn edit_sketch(
&self,
_project: ProjectId,
_file: FileId,
_version: Version,
_sketch: ObjectId,
) -> Result<SceneGraphDelta> {
todo!("edit_sketch not implemented")
}

async fn exit_sketch(&self, _version: Version, _sketch: ObjectId) -> Result<SceneGraph> {
todo!("exit_sketch not implemented")
}

async fn add_segment(
&self,
_version: Version,
_sketch: ObjectId,
_segment: SegmentCtor,
_label: Option<String>,
) -> Result<(KclSource, SketchExecOutcome)> {

Check warning on line 358 in rust/kcl-api/src/sketch.rs

View workflow job for this annotation

GitHub Actions / cargo fmt

Diff in /home/runner/work/modeling-app/modeling-app/rust/kcl-api/src/sketch.rs
// Return empty stub data
Ok((
KclSource {
text: String::new(),
},
SketchExecOutcome {
segments: Vec::new(),
constraints: Vec::new(),
},
))
}

async fn edit_segment(
&self,
_version: Version,
_sketch: ObjectId,
_segment_id: ObjectId,
_segment: SegmentCtor,
) -> Result<(SourceDelta, SceneGraphDelta)> {
todo!("edit_segment not implemented")
}

async fn delete_segment(
&self,
_version: Version,
_sketch: ObjectId,
_segment_id: ObjectId,
) -> Result<(SourceDelta, SceneGraphDelta)> {
todo!("delete_segment not implemented")
}

async fn add_constraint(
&self,
_version: Version,
_sketch: ObjectId,
_constraint: Constraint,
) -> Result<(SourceDelta, SceneGraphDelta)> {
todo!("add_constraint not implemented")
}

async fn edit_constraint(
&self,
_version: Version,
_sketch: ObjectId,
_constraint_id: ObjectId,
_constraint: Constraint,
) -> Result<(SourceDelta, SceneGraphDelta)> {
todo!("edit_constraint not implemented")
}

async fn delete_constraint(
&self,
_version: Version,
_sketch: ObjectId,
_constraint_id: ObjectId,
) -> Result<(SourceDelta, SceneGraphDelta)> {
todo!("delete_constraint not implemented")

Check warning on line 415 in rust/kcl-api/src/sketch.rs

View workflow job for this annotation

GitHub Actions / cargo fmt

Diff in /home/runner/work/modeling-app/modeling-app/rust/kcl-api/src/sketch.rs
}
}
26 changes: 25 additions & 1 deletion rust/kcl-wasm-lib/src/api.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use gloo_utils::format::JsValueSerdeExt;

Check warning on line 1 in rust/kcl-wasm-lib/src/api.rs

View workflow job for this annotation

GitHub Actions / cargo fmt

Diff in /home/runner/work/modeling-app/modeling-app/rust/kcl-wasm-lib/src/api.rs
use kcl_api::{Error, File, FileId, LifecycleApi, ProjectId};
use kcl_api::sketch::{SegmentCtor, SketchApi, SketchApiStub, SketchExecOutcome};
use kcl_api::{Error, File, FileId, KclSource, LifecycleApi, ObjectId, ProjectId, Version};
use wasm_bindgen::prelude::*;

use crate::Context;
Expand Down Expand Up @@ -71,4 +72,27 @@
.await
.map_err(|e: Error| JsValue::from_serde(&e).unwrap())
}

#[wasm_bindgen]
pub async fn add_segment(
&self,
version: usize,
sketch: usize,
segment: &str,
label: Option<String>,
) -> Result<JsValue, JsValue> {
console_error_panic_hook::set_once();

let segment: SegmentCtor = serde_json::from_str(segment)
.map_err(|e| JsValue::from_serde(&Error::deserialize("segment", e)).unwrap())?;

Check warning on line 87 in rust/kcl-wasm-lib/src/api.rs

View workflow job for this annotation

GitHub Actions / semgrep-oss/scan

panic-in-function-returning-result

expect or unwrap called in function returning a Result

// For now, use the stub implementation
let sketch_api = SketchApiStub;
let result: (KclSource, SketchExecOutcome) = sketch_api
.add_segment(Version(version), ObjectId(sketch), segment, label)
.await
.map_err(|e: Error| JsValue::from_serde(&e).unwrap())?;

Check warning on line 94 in rust/kcl-wasm-lib/src/api.rs

View workflow job for this annotation

GitHub Actions / semgrep-oss/scan

panic-in-function-returning-result

expect or unwrap called in function returning a Result

Ok(JsValue::from_str(&serde_json::to_string(&result).unwrap()))

Check warning on line 96 in rust/kcl-wasm-lib/src/api.rs

View workflow job for this annotation

GitHub Actions / semgrep-oss/scan

panic-in-function-returning-result

expect or unwrap called in function returning a Result
}
}
2 changes: 2 additions & 0 deletions scripts/build-wasm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ set -euo pipefail
rm -rf rust/kcl-wasm-lib/pkg
mkdir -p rust/kcl-wasm-lib/pkg
rm -rf rust/kcl-lib/bindings
rm -rf rust/kcl-api/bindings

cd rust
export RUSTFLAGS='--cfg getrandom_backend="wasm_js"'
wasm-pack build kcl-wasm-lib --release --target web --out-dir pkg
export RUSTFLAGS=''
cargo test -p kcl-lib --features artifact-graph export_bindings
cargo test -p kcl-api export_bindings
Copy link
Contributor

@jtran jtran Oct 17, 2025

Choose a reason for hiding this comment

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

Do we need to do this for kcl-error also? I guess I'm confused how things are working at all now on main if we do need it.

cd ..

cp rust/kcl-wasm-lib/pkg/kcl_wasm_lib_bg.wasm public
Expand Down
29 changes: 29 additions & 0 deletions src/lib/rustContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import type { OutputFormat3d } from '@rust/kcl-lib/bindings/ModelingCmd'
import type { Node } from '@rust/kcl-lib/bindings/Node'
import type { Program } from '@rust/kcl-lib/bindings/Program'
import type { SegmentCtor } from '@rust/kcl-lib/bindings/SegmentCtor'
import type { SketchExecOutcome } from '@rust/kcl-api/bindings/SketchExecOutcome'

Check failure on line 11 in src/lib/rustContext.ts

View workflow job for this annotation

GitHub Actions / npm-tsc

Cannot find module '@rust/kcl-api/bindings/SketchExecOutcome' or its corresponding type declarations.
import type { KclSource } from '@rust/kcl-api/bindings/KclSource'

Check failure on line 12 in src/lib/rustContext.ts

View workflow job for this annotation

GitHub Actions / npm-tsc

Cannot find module '@rust/kcl-api/bindings/KclSource' or its corresponding type declarations.
import { type Context } from '@rust/kcl-wasm-lib/pkg/kcl_wasm_lib'
import { BSON } from 'bson'

Expand Down Expand Up @@ -239,6 +242,32 @@
}
}

/** Add a segment to a sketch. */
async addSegment(
version: number,
sketch: number,
segment: SegmentCtor,
label?: string
): Promise<{
kclSource: KclSource
sketchExecOutcome: SketchExecOutcome
}> {
const instance = this._checkInstance()

try {
const result = await instance.add_segment(
version,
sketch,
JSON.stringify(segment),
label
)
return JSON.parse(result)
} catch (e: any) {
const err = errFromErrWithOutputs(e)
return Promise.reject(err)
}
}

/** Helper to check if context instance exists */
private _checkInstance(): Context {
if (!this.ctxInstance) {
Expand Down
35 changes: 26 additions & 9 deletions src/machines/sketchSolve/sketchSolveMode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@
setup,
} from 'xstate'
import type { ActorRefFrom } from 'xstate'
import { modelingMachineDefaultContext } from '@src/machines/modelingSharedContext'
import type {
ModelingMachineContext,
SetSelections,
} from '@src/machines/modelingSharedTypes'
import type { SetSelections } from '@src/machines/modelingSharedTypes'
import { machine as centerRectTool } from '@src/machines/sketchSolve/tools/centerRectTool'
import { machine as dimensionTool } from '@src/machines/sketchSolve/tools/dimensionTool'
import { machine as pointTool } from '@src/machines/sketchSolve/tools/pointTool'
import type { SketchExecOutcome } from '@rust/kcl-api/bindings/SketchExecOutcome'

Check failure on line 14 in src/machines/sketchSolve/sketchSolveMode.ts

View workflow job for this annotation

GitHub Actions / npm-tsc

Cannot find module '@rust/kcl-api/bindings/SketchExecOutcome' or its corresponding type declarations.
import type { KclSource } from '@rust/kcl-api/bindings/KclSource'

Check failure on line 15 in src/machines/sketchSolve/sketchSolveMode.ts

View workflow job for this annotation

GitHub Actions / npm-tsc

Cannot find module '@rust/kcl-api/bindings/KclSource' or its corresponding type declarations.

const equipTools = Object.freeze({
centerRectTool,
Expand All @@ -41,10 +39,21 @@
| { type: 'unequip tool' }
| { type: 'equip tool'; data: { tool: EquipTool } }
| { type: typeof CHILD_TOOL_DONE_EVENT }
| {
type: 'update sketch outcome'
data: {
kclSource: KclSource
sketchExecOutcome: SketchExecOutcome
}
}

type SketchSolveContext = ModelingMachineContext & {
type SketchSolveContext = {
sketchSolveToolName: EquipTool | null
pendingToolName?: EquipTool
sketchExecOutcome?: {
kclSource: KclSource
sketchExecOutcome: SketchExecOutcome
}
}

export const sketchSolveMachine = setup({
Expand Down Expand Up @@ -72,6 +81,10 @@
type: 'sketch solve tool changed',
data: { tool: null },
}),
'update sketch outcome': assign(({ event }) => {
assertEvent(event, 'update sketch outcome')
return { sketchExecOutcome: event.data }
}),
'spawn tool': assign(({ event, spawn, context }) => {
// Determine which tool to spawn based on event type
let nameOfToolToSpawn: EquipTool
Expand Down Expand Up @@ -105,9 +118,8 @@
...equipTools,
},
}).createMachine({
/** @xstate-layout N4IgpgJg5mDOIC5QGUDWYAuBjAFgAmQHsAbANzDwFlCIwBiMADwEsMBtABgF1FQAHQrFbNCAO14hGiAIwB2ABwBWAHTyAbAGZFAFiXyOAJg0BOADQgAnolkrpHQwe1rFz59I0BfD+bSZcBEnIqGnoAVz4IAEMMClgwYjAsDBFRTh4kEAEhZLEJKQRZY1lVWWk1YzV5A3ltDg1tcysEAFoDI2UDWQ4lNWk5A0Uqz28QX2x8IjIKalo6UNEwAEdQ5j48DEISNIks4VyM-O1FFRqB2Sd6jlk1BstEVo1pZQ1e+Rt5eWlB0u0vH3RxgEpsFaMoALaEIKRUQQPBxBJJOgQMRgZTMUSkQjocGQsAAFU2xG2GV2OXEBxkBmMTw0L06ig42jkZjuBTUykGBjUN0G3Q0jL+owB-kmQRmqIhUJhcPiiQwDAATgrCArlHxiNEAGYqsE48gErbcHaCPbk0D5aRUgzKexVV6yc7aAaNSnaVRcz7lakuDiWwVjEWBaYhPUUaGw+FyhjLVbrQnE-gmsl5GR2G2FRTGeQaapFY7yF0IAzuVSDYx1SptRRc-3CiZBkGo0JCURQOMkOYLGNrDaG9KJ7IpFMIOzcm19NTFqnSYwaT6FjTnZS6XqyAxXLpz5y1vz14Hi5TN9Ft3vEaMrHvxo0kpNDikIWfFTo1IpGF-OQsMt05xwcTTdQpZGGf5dyBMUQ1gAB3VhcGPdsz0YWAMGiVFkQWZRIiSFVlFPBNMlvfZzRkR4OUfRRrgZQZ1ELbQumUZxtEY7QTHXZjgKFUDRWDUF5iWC84NPOhEOQmJlDQ1FMI2VVcOvAdTWHOwmRtQYmX5IwgKAws5Dda5LV0P9pB6LQvBGUQQngDIAz3cDaGNQdCMkRBJ3TYxM2zXMbEGQtmktZ5jCKB0uRcAYqR3QEuMbUM8HDGUEQwOz5PvRcn2MTpix9NpnVZOxrQUKkHH5BlZDCwN9xDI9W3ghLk3vWoOGeNQ1w4HQ7EURdFAXGcOXURdaRMMoc3YqywO41EoJgnABMJaq7yIotjDdTMuXKdxaWpAtWUnYpGLeep6nXa4DBK6zRuUJhhFbGaHItFdl2uc4TAW7kikLfz3UqRlPIWxx5GOkbIt47sppIK6zUch8BmUQpqkeaQnRUjamk6J56Q+c4QocEyPCAA */
context: ({ input }): SketchSolveContext => ({
...modelingMachineDefaultContext,
/** @xstate-layout N4IgpgJg5mDOIC5QGUDWYAuBjAFgAmQHsAbANzDwFlCIwBiMADwEsMBtABgF1FQAHQrFbNCAO14hGiAIwB2ABwBWAHTyAbAGZFAFiXyOAJg0BOADQgAnolkrpHQwe1rFz59I0BfD+bSZcBEnIqGnoAVz4IAEMMClgwYjAsDBFRTh4kEAEhZLEJKQRZY1lVWWk1YzV5A3ltDg1tcysEAFoDI2UDWQ4lNWk5A0Uqz28QX2x8IjIKalo6cKiYvFh0cbxCUOxCAFswNIks4VyM-OkBg2VjU40a6-ljF0VGxFbpY2Uug0ubTp07rx8Vv5JkEZmFRGAAI6hZh8PAYQgkPYZA45cTHRDaRQqGoDWROeocWRqBqWZ5GaTKDS9eQ2eTyaSDUraf6jQETQLTELKLaEIKRUQQJbxRIYOgQMRgZTMUSkQjobm8sAAFQRxCR-EEhzRoBOnwpGipPw42jkZlJBTUykGBjUxMG3Q0xpZYyBHOCtAVfIFQoSSQYACd-YR-co+MRogAzYNbT3K1XqzKa1F5GSfc72KrU2R47QDJ4IU7aVQ2+nlS4uDinZ1sgJTd2SnlewVxX2iyHQ2HwxHcfZJlIpgt2ZSE+7GeQaapFLHyfMGdyqQbGOqVNqKG3Vvzsuug5ShISiKBw1VzcFQmFH7vpDXZfvogvG+SUsp49RdA3E-NaItY4y-3Mm20nRGF0txBLk92lQ8u2IBgz07eMe2RPsjh1RAigpT46m0XQTFkG01HzbQFHeLFZA0BQ6kUeppA3VZgU5D1YAAd1YXBIIvGDGFgDBoklcVwWUSIkmDZQExRW9UIQIpzjuCp9ExJdygI81iUfBk8InKi1AUOdaNdbdwNPDs+HY6C6C4niYmUfjJSE+EQzE5DtUkRAXEfFw83NBRimMXQyO0eoDEJNQDC8EZRBCeAMhA2swNoXsbxQlyEBC4dCkUMcJzuGxBnzZpTkpX9s0KXRn06PTQIYhtFTwflm2FJIEq1AdyOKNM8IZNQHE8po7HOHSlzaR1FEJCrYqq3d9yg1UmuTO8TBUapyXUaodEec1HXczpNC0NbFDG+j62UZjWJwUyZqQxLnPyT5v2MfDLgNExpBnFTOmUbCaXqQLgtC4Ca0OncmGEA9Zok5LpF0S0iO0gK-1tIp81-YtKmNHLfMceQDrdHdQiMmETIPDiwaS3VyOUV5ekqDKurKfMsSLUobAZRRCiqRQaLCoA */
context: (): SketchSolveContext => ({
sketchSolveToolName: null,
}),
id: 'Sketch Solve Mode',
Expand All @@ -127,6 +139,11 @@
description:
'sketch mode consumes the current selection from its source of truth (currently modelingMachine). Whenever it receives',
},
'update sketch outcome': {
actions: 'update sketch outcome',
description:
'Updates the sketch execution outcome in the context when tools complete operations',
},
'unequip tool': {
actions: 'send unequip to tool',
},
Expand Down
Loading
Loading