From 6347fed1c3247641eb97bc5f10f22a8d0f779952 Mon Sep 17 00:00:00 2001 From: Adam Chalmers Date: Wed, 13 Dec 2023 15:57:28 -0600 Subject: [PATCH] Working example --- Cargo.toml | 2 +- modeling-cmds/src/def_enum.rs | 2 +- modeling-cmds/src/each_cmd.rs | 4 + modeling-cmds/src/impl_traits.rs | 6 + modeling-session/Cargo.toml | 2 +- modeling-session/examples/cube_png.rs | 176 ++++++++++++-------------- 6 files changed, 94 insertions(+), 98 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 17a08aab..412604d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [workspace] resolver = "2" members = [ - "modeling-cmds", "execution-plan", + "modeling-cmds", "modeling-session", "unit-conversion-derive", ] diff --git a/modeling-cmds/src/def_enum.rs b/modeling-cmds/src/def_enum.rs index b920066d..3b38b073 100644 --- a/modeling-cmds/src/def_enum.rs +++ b/modeling-cmds/src/def_enum.rs @@ -8,7 +8,7 @@ pub use crate::each_cmd::*; #[serde(rename_all = "snake_case", tag = "type")] pub enum ModelingCmd { /// Start a path. - StartPath, + StartPath(StartPath), /// Move the path's "pen". MovePathPen(MovePathPen), /// Extend a path by adding a new segment which starts at the path's "pen". diff --git a/modeling-cmds/src/each_cmd.rs b/modeling-cmds/src/each_cmd.rs index a3c99f79..a413c368 100644 --- a/modeling-cmds/src/each_cmd.rs +++ b/modeling-cmds/src/each_cmd.rs @@ -16,6 +16,10 @@ use crate::{ units, }; +/// Start a new path. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct StartPath; + /// Move the path's "pen". #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct MovePathPen { diff --git a/modeling-cmds/src/impl_traits.rs b/modeling-cmds/src/impl_traits.rs index 3723b73c..92ba8276 100644 --- a/modeling-cmds/src/impl_traits.rs +++ b/modeling-cmds/src/impl_traits.rs @@ -1,5 +1,11 @@ use crate::{each_cmd::*, output as out, ModelingCmd, ModelingCmdVariant}; +impl<'de> ModelingCmdVariant<'de> for StartPath { + type Output = (); + fn into_enum(self) -> ModelingCmd { + ModelingCmd::StartPath(self) + } +} impl<'de> ModelingCmdVariant<'de> for MovePathPen { type Output = (); fn into_enum(self) -> ModelingCmd { diff --git a/modeling-session/Cargo.toml b/modeling-session/Cargo.toml index e104e9d3..2d7c4db5 100644 --- a/modeling-session/Cargo.toml +++ b/modeling-session/Cargo.toml @@ -17,9 +17,9 @@ kittycad-modeling-cmds = { path = "../modeling-cmds", features = ["websocket"] } reqwest = "0.11.22" serde_json = "1.0.108" thiserror = "1.0.50" +tokio = { version = "1", features = ["sync"] } tokio-tungstenite = "0.21.0" uuid = { version = "1.6.1", features = ["v4"] } -tokio = { version = "1", features = ["sync"] } [dev-dependencies] color-eyre = "0.6" diff --git a/modeling-session/examples/cube_png.rs b/modeling-session/examples/cube_png.rs index 932310c3..2be0088a 100644 --- a/modeling-session/examples/cube_png.rs +++ b/modeling-session/examples/cube_png.rs @@ -1,24 +1,21 @@ //! Use the KittyCAD modeling API to draw a cube and save it to a PNG. -use std::{env, io::Cursor, time::Duration}; +use std::{env, io::Cursor}; use color_eyre::{ - eyre::{bail, Context, Error}, + eyre::{bail, Context}, Result, }; -use futures::{ - stream::{SplitSink, SplitStream}, - SinkExt, StreamExt, -}; -use kittycad::types::{ - FailureWebSocketResponse, ModelingCmd, OkModelingCmdResponse, OkWebSocketResponseData, PathSegment, Point3D, - SuccessWebSocketResponse, WebSocketRequest, +use kittycad_modeling_cmds::{ + id::ModelingCmdId, + ok_response::OkModelingCmdResponse, + shared::{PathSegment, Point3d}, + ClosePath, ExtendPath, Extrude, MovePathPen, StartPath, TakeSnapshot, }; use kittycad_modeling_session::{Session, SessionBuilder}; -use reqwest::Upgraded; -use tokio::time::timeout; -use tokio_tungstenite::{tungstenite::Message as WsMsg, WebSocketStream}; use uuid::Uuid; +const CUBE_WIDTH: f64 = 10.0; + #[tokio::main] async fn main() -> Result<()> { // Set up the API client. @@ -34,119 +31,108 @@ async fn main() -> Result<()> { unlocked_framerate: Some(false), video_res_height: Some(720), video_res_width: Some(1280), + buffer_reqs: None, + await_response_timeout: None, }; - let session = Session::start(session_builder).await?; - - // First, send all commands to the API, to draw a cube. - // Then, read all responses from the API, to download the cube as a PNG. - // draw_cube(write, 10.0).await?; - // export_png(read, img_output_path).await - Ok(()) -} - -/// Send modeling commands to the KittyCAD API. -/// We're going to draw a cube and export it as a PNG. -async fn draw_cube(mut write_to_ws: SplitSink, WsMsg>, width: f64) -> Result<()> { - // All messages to the KittyCAD Modeling API will be sent over the WebSocket as Text. - // The text will contain JSON representing a `ModelingCmdReq`. - // This takes in a command and its ID, and makes a WebSocket message containing that command. - fn to_msg(cmd: ModelingCmd, cmd_id: Uuid) -> WsMsg { - WsMsg::Text(serde_json::to_string(&WebSocketRequest::ModelingCmdReq { cmd, cmd_id }).unwrap()) - } - - // Now the WebSocket is set up and ready to use! - // We can start sending commands. + let mut session = Session::start(session_builder) + .await + .context("could not establish session")?; // Create a new empty path. let path_id = Uuid::new_v4(); - write_to_ws.send(to_msg(ModelingCmd::StartPath {}, path_id)).await?; + let path = path_id.into(); + session + .run_command(path, StartPath {}) + .await + .context("could not create path")?; // Add four lines to the path, // in the shape of a square. // First, start the path at the first corner. - let start = Point3D { - x: -width, - y: -width, - z: -width, + let start = Point3d { + x: -CUBE_WIDTH, + y: -CUBE_WIDTH, + z: -CUBE_WIDTH, }; - write_to_ws - .send(to_msg( - ModelingCmd::MovePathPen { - path: path_id, - to: start.clone(), - }, - Uuid::new_v4(), - )) - .await?; + session + .run_command(random_id(), MovePathPen { path, to: start }) + .await + .context("could not move path pen to start")?; // Now extend the path to each corner, and back to the start. let points = [ - Point3D { - x: width, - y: -width, - z: -width, + Point3d { + x: CUBE_WIDTH, + y: -CUBE_WIDTH, + z: -CUBE_WIDTH, }, - Point3D { - x: width, - y: width, - z: -width, + Point3d { + x: CUBE_WIDTH, + y: CUBE_WIDTH, + z: -CUBE_WIDTH, }, - Point3D { - x: -width, - y: width, - z: -width, + Point3d { + x: -CUBE_WIDTH, + y: CUBE_WIDTH, + z: -CUBE_WIDTH, }, start, ]; for point in points { - write_to_ws - .send(to_msg( - ModelingCmd::ExtendPath { - path: path_id, + session + .run_command( + random_id(), + ExtendPath { + path, segment: PathSegment::Line { end: point, relative: false, }, }, - Uuid::new_v4(), - )) - .await?; + ) + .await + .context("could not draw square")?; } - // Extrude the square into a cube. - write_to_ws - .send(to_msg(ModelingCmd::ClosePath { path_id }, Uuid::new_v4())) - .await?; - write_to_ws - .send(to_msg( - ModelingCmd::Extrude { + session + .run_command(random_id(), ClosePath { path_id }) + .await + .context("could not close square path")?; + session + .run_command( + random_id(), + Extrude { cap: true, - distance: width * 2.0, - target: path_id, + distance: CUBE_WIDTH * 2.0, + target: path, }, - Uuid::new_v4(), - )) - .await?; - - // Export the model as a PNG. - write_to_ws - .send(to_msg( - ModelingCmd::TakeSnapshot { - format: kittycad::types::ImageFormat::Png, + ) + .await + .context("could not extrude square into cube")?; + // Export model as a PNG. + let snapshot_resp = session + .run_command( + random_id(), + TakeSnapshot { + format: kittycad_modeling_cmds::ImageFormat::Png, }, - Uuid::new_v4(), - )) - .await?; + ) + .await + .context("could not get PNG snapshot")?; - // Finish sending - drop(write_to_ws); + // Save the PNG to disk. + match snapshot_resp { + OkModelingCmdResponse::TakeSnapshot(snap) => { + let mut img = image::io::Reader::new(Cursor::new(snap.contents)); + img.set_format(image::ImageFormat::Png); + let img = img.decode().context("could not decode PNG bytes")?; + img.save(img_output_path).context("could not save PNG to disk")?; + } + other => bail!("Unexpected response: {other:?}"), + }; Ok(()) } -fn save_image(contents: Vec, output_path: &str) -> Result<()> { - let mut img = image::io::Reader::new(Cursor::new(contents)); - img.set_format(image::ImageFormat::Png); - let img = img.decode()?; - img.save(output_path)?; - Ok(()) +fn random_id() -> ModelingCmdId { + Uuid::new_v4().into() }