Skip to content
Closed
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
1 change: 1 addition & 0 deletions modeling-cmds/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ expectorate = "1.1.0"
openapi-lint = { git = "https://github.com/KittyCAD/openapi-lint", branch = "kittycad" }
openapiv3 = "2.2.0"
reqwest = "0.12.23"
serde_json = "1.0.150"
tokio = { version = "1.52.3", features = ["macros"] }

[lints]
Expand Down
44 changes: 40 additions & 4 deletions modeling-cmds/openapi/api.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,17 @@
"type": "number",
"format": "float"
},
"from_edge_reference": {
"nullable": true,
"description": "Edge reference to use to measure the dimension from If both `from_entity_id` and `from_edge_reference` are provided, `from_edge_reference` takes precedence.",
"allOf": [
{
"$ref": "#/components/schemas/EdgeSpecifier"
}
]
},
"from_entity_id": {
"nullable": true,
"description": "Entity to measure the dimension from",
"type": "string",
"format": "uuid"
Expand Down Expand Up @@ -123,7 +133,17 @@
"format": "uint32",
"minimum": 0
},
"to_edge_reference": {
"nullable": true,
"description": "Edge reference to use to measure the dimension from If both `to_entity_id` and `to_edge_reference` are provided, `to_edge_reference` takes precedence.",
"allOf": [
{
"$ref": "#/components/schemas/EdgeSpecifier"
}
]
},
"to_entity_id": {
"nullable": true,
"description": "Entity to measure the dimension to",
"type": "string",
"format": "uuid"
Expand All @@ -141,12 +161,10 @@
"dimension",
"font_point_size",
"font_scale",
"from_entity_id",
"from_entity_pos",
"offset",
"plane_id",
"precision",
"to_entity_id",
"to_entity_pos"
]
},
Expand Down Expand Up @@ -179,7 +197,17 @@
}
]
},
"edge_reference": {
"nullable": true,
"description": "Edge reference to use to place the annotation leader from If both `entity_id` and `edge_reference` are provided, `edge_reference` takes precedence.",
"allOf": [
{
"$ref": "#/components/schemas/EdgeSpecifier"
}
]
},
"entity_id": {
"nullable": true,
"description": "Entity to place the annotation leader from",
"type": "string",
"format": "uuid"
Expand Down Expand Up @@ -248,7 +276,6 @@
}
},
"required": [
"entity_id",
"entity_pos",
"font_point_size",
"font_scale",
Expand All @@ -262,7 +289,17 @@
"description": "Parameters for defining an MBD Feature Tag Annotation state",
"type": "object",
"properties": {
"edge_reference": {
"nullable": true,
"description": "Edge reference to use to place the annotation leader from If both `entity_id` and `edge_reference` are provided, `edge_reference` takes precedence.",
"allOf": [
{
"$ref": "#/components/schemas/EdgeSpecifier"
}
]
},
"entity_id": {
"nullable": true,
"description": "Entity to place the annotation leader from",
"type": "string",
"format": "uuid"
Expand Down Expand Up @@ -327,7 +364,6 @@
}
},
"required": [
"entity_id",
"entity_pos",
"font_point_size",
"font_scale",
Expand Down
168 changes: 164 additions & 4 deletions modeling-cmds/src/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -424,13 +424,15 @@ pub struct AnnotationMbdBasicDimension {
#[cfg_attr(not(feature = "unstable_exhaustive"), non_exhaustive)]
pub struct AnnotationBasicDimension {
/// Entity to measure the dimension from
pub from_entity_id: Uuid,
#[builder(into)]
pub from_entity_id: EdgeIdOrSpec,

/// Normalized position within the entity to position the dimension from
pub from_entity_pos: Point2d<f64>,

/// Entity to measure the dimension to
pub to_entity_id: Uuid,
#[builder(into)]
pub to_entity_id: EdgeIdOrSpec,

/// Normalized position within the entity to position the dimension to
pub to_entity_pos: Point2d<f64>,
Expand Down Expand Up @@ -467,7 +469,8 @@ pub struct AnnotationBasicDimension {
#[cfg_attr(not(feature = "unstable_exhaustive"), non_exhaustive)]
pub struct AnnotationFeatureControl {
/// Entity to place the annotation leader from
pub entity_id: Uuid,
#[builder(into)]
pub entity_id: EdgeIdOrSpec,

/// Normalized position within the entity to position the annotation leader from
pub entity_pos: Point2d<f64>,
Expand Down Expand Up @@ -519,7 +522,8 @@ pub struct AnnotationFeatureControl {
#[cfg_attr(not(feature = "unstable_exhaustive"), non_exhaustive)]
pub struct AnnotationFeatureTag {
/// Entity to place the annotation leader from
pub entity_id: Uuid,
#[builder(into)]
pub entity_id: EdgeIdOrSpec,

/// Normalized position within the entity to position the annotation leader from
pub entity_pos: Point2d<f64>,
Expand Down Expand Up @@ -1769,6 +1773,87 @@ impl ExtrudedFaceInfo {
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;

#[test]
fn backwards_compat_edge_spec_mbd() {
let old_json = r#"{
"from_entity_id": "f2719dee-ea58-4f34-84d7-e32afbbbd296",
"from_entity_pos": {
"x": 1.0,
"y": 1.0
},
"to_entity_id": "75d4f761-9fb1-4b0f-98bb-e7757a6bcc5d",
"to_entity_pos": {
"x": 22.0,
"y": 23.0
},
"dimension": {
"symbol": null,
"dimension": null,
"tolerance": 33.44
},
"plane_id": "58eab9b3-4027-4a39-b1be-0436864b1566",
"offset": {
"x": 0.0,
"y": 0.0
},
"precision": 1,
"font_scale": 1.0,
"font_point_size": 7,
"arrow_scale": 1.0
}"#;
// The old JSON should deserialize into the same struct as previously.
let expected = crate::shared::AnnotationBasicDimension::builder()
.from_entity_id(EdgeIdOrSpec::Entity {
id: "f2719dee-ea58-4f34-84d7-e32afbbbd296".parse().unwrap(),
})
.to_entity_id(Uuid::from_str("75d4f761-9fb1-4b0f-98bb-e7757a6bcc5d").unwrap())
.plane_id("58eab9b3-4027-4a39-b1be-0436864b1566".parse().unwrap())
.from_entity_pos(Point2d { x: 1.0, y: 1.0 })
.to_entity_pos(Point2d { x: 22.0, y: 23.0 })
.arrow_scale(1.0)
.font_point_size(7)
.dimension(AnnotationMbdBasicDimension::builder().tolerance(33.44).build())
.font_scale(1.0)
.precision(1)
.offset(Point2d::default())
.build();
let actual: crate::shared::AnnotationBasicDimension = serde_json::from_str(old_json).unwrap();
assert_eq!(actual, expected);

// Serializing the new struct should produce the same JSON as previously.
let actual_ser = serde_json::to_string_pretty(&actual).unwrap();
assert_eq!(actual_ser, old_json);
}

#[test]
fn edge_id_or_spec_serde_accepts_legacy_and_new_representations() {
let id: Uuid = "f2719dee-ea58-4f34-84d7-e32afbbbd296".parse().unwrap();
let edge = EdgeSpecifier {
side_faces: vec!["75d4f761-9fb1-4b0f-98bb-e7757a6bcc5d".parse().unwrap()],
end_faces: vec![],
index: Some(1),
};

let legacy_entity: EdgeIdOrSpec = serde_json::from_value(serde_json::json!(id)).unwrap();
assert_eq!(legacy_entity, EdgeIdOrSpec::Entity { id });

let new_entity_json = serde_json::json!({ "id": id });
let new_entity: EdgeIdOrSpec = serde_json::from_value(new_entity_json.clone()).unwrap();
assert_eq!(new_entity, EdgeIdOrSpec::Entity { id });
assert_eq!(serde_json::to_value(new_entity).unwrap(), serde_json::json!(id));

let new_edge_json = serde_json::json!({ "reference": edge.clone() });
let new_edge: EdgeIdOrSpec = serde_json::from_value(new_edge_json.clone()).unwrap();
assert_eq!(
new_edge,
EdgeIdOrSpec::Edge {
reference: edge.clone(),
}
);
assert_eq!(serde_json::to_value(new_edge).unwrap(), new_edge_json);
}

#[test]
fn test_angle_comparison() {
Expand Down Expand Up @@ -2177,3 +2262,78 @@ impl From<BodiesUpdated> for BodiesCreated {
fn one() -> f32 {
1.0
}

/// What to measure the dimension from
#[derive(Debug, Clone, PartialEq, JsonSchema)]
#[serde(untagged, rename_all = "snake_case")]
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
#[cfg_attr(not(feature = "unstable_exhaustive"), non_exhaustive)]
pub enum EdgeIdOrSpec {
/// Entity to measure the dimension from
Entity {
/// ID of the entity.
id: Uuid,
},
/// Edge reference to use to measure the dimension from
Edge {
/// Specifies the edge.
reference: EdgeSpecifier,
},
}

impl Serialize for EdgeIdOrSpec {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
#[derive(Serialize)]
#[serde(untagged)]
enum EdgeIdOrSpecSerialize<'a> {
Entity(&'a Uuid),
Edge { reference: &'a EdgeSpecifier },
}

match self {
Self::Entity { id } => EdgeIdOrSpecSerialize::Entity(id).serialize(serializer),
Self::Edge { reference } => EdgeIdOrSpecSerialize::Edge { reference }.serialize(serializer),
}
}
}

impl<'de> Deserialize<'de> for EdgeIdOrSpec {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum EdgeIdOrSpecDeserialize {
LegacyEntity(Uuid),
Entity { id: Uuid },
Edge { reference: EdgeSpecifier },
}

match EdgeIdOrSpecDeserialize::deserialize(deserializer)? {
EdgeIdOrSpecDeserialize::LegacyEntity(id) | EdgeIdOrSpecDeserialize::Entity { id } => {
Ok(Self::Entity { id })
}
EdgeIdOrSpecDeserialize::Edge { reference } => Ok(Self::Edge { reference }),
}
}
}

/// Constructor for the Entity ID case
impl From<Uuid> for EdgeIdOrSpec {
fn from(id: Uuid) -> Self {
Self::Entity { id }
}
}

/// Constructor for the Edge Specifier case.
impl From<EdgeSpecifier> for EdgeIdOrSpec {
fn from(reference: EdgeSpecifier) -> Self {
Self::Edge { reference }
}
}
Loading