forked from 1b-agent/opencadstudio-upstream
Merge pull request #1688 from YsDirector/pr/d3-spline-api
Create and serialize splines through automation # Conflicts: # src/app/control/entities.rs
This commit is contained in:
commit
cdf8e60fbf
3 files changed
+191
-12
No files matched your search
@@ -284,6 +284,27 @@ pub(crate) fn entity_json(e: &codec::EntityType, detail: &str) -> Value {
|
|||||||
map.insert("start_angle".into(), json!(a.start_angle));
|
map.insert("start_angle".into(), json!(a.start_angle));
|
||||||
map.insert("end_angle".into(), json!(a.end_angle));
|
map.insert("end_angle".into(), json!(a.end_angle));
|
||||||
}
|
}
|
||||||
|
E::Spline(spline) => {
|
||||||
|
map.insert("degree".into(), json!(spline.degree));
|
||||||
|
map.insert("knots".into(), json!(spline.knots));
|
||||||
|
map.insert(
|
||||||
|
"control_points".into(),
|
||||||
|
json!(spline
|
||||||
|
.control_points
|
||||||
|
.iter()
|
||||||
|
.map(|point| v3(*point))
|
||||||
|
.collect::<Vec<_>>()),
|
||||||
|
);
|
||||||
|
map.insert(
|
||||||
|
"fit_points".into(),
|
||||||
|
json!(spline
|
||||||
|
.fit_points
|
||||||
|
.iter()
|
||||||
|
.map(|point| v3(*point))
|
||||||
|
.collect::<Vec<_>>()),
|
||||||
|
);
|
||||||
|
map.insert("weights".into(), json!(spline.weights));
|
||||||
|
}
|
||||||
E::Point(p) => {
|
E::Point(p) => {
|
||||||
map.insert("location".into(), v3(p.location));
|
map.insert("location".into(), v3(p.location));
|
||||||
}
|
}
|
||||||
|
|||||||
+139
-12
@@ -8,23 +8,84 @@ use crate::command::EntityTransform;
|
|||||||
|
|
||||||
type Parsed<T> = Result<T, Value>;
|
type Parsed<T> = Result<T, Value>;
|
||||||
|
|
||||||
/// `[x,y]` or `[x,y,z]` (missing z = 0) from `req[key]`.
|
/// Parse `[x,y]` or `[x,y,z]` without silently turning a malformed
|
||||||
fn point_field(req: &Value, key: &str) -> Parsed<codec::types::Vector3> {
|
/// coordinate into zero.
|
||||||
let values = req[key]
|
fn point_value(value: Option<&Value>, label: &str, code: &str) -> Parsed<codec::types::Vector3> {
|
||||||
.as_array()
|
let values = value
|
||||||
|
.and_then(Value::as_array)
|
||||||
.filter(|v| (2..=3).contains(&v.len()))
|
.filter(|v| (2..=3).contains(&v.len()))
|
||||||
.ok_or_else(|| failure("invalid_point", format!("{key} must be [x,y] or [x,y,z]")))?;
|
.ok_or_else(|| failure(code, format!("{label} must be [x,y] or [x,y,z]")))?;
|
||||||
let coord = |i: usize| {
|
let coord = |i: usize| {
|
||||||
values[i]
|
values[i]
|
||||||
.as_f64()
|
.as_f64()
|
||||||
.filter(|v| v.is_finite())
|
.filter(|v| v.is_finite())
|
||||||
.ok_or_else(|| failure("invalid_point", format!("{key} needs finite coordinates")))
|
.ok_or_else(|| failure(code, format!("{label} needs finite coordinates")))
|
||||||
};
|
};
|
||||||
Ok(codec::types::Vector3::new(
|
let z = values
|
||||||
coord(0)?,
|
.get(2)
|
||||||
coord(1)?,
|
.map(|value| {
|
||||||
values.get(2).and_then(Value::as_f64).unwrap_or(0.0),
|
value
|
||||||
))
|
.as_f64()
|
||||||
|
.filter(|v| v.is_finite())
|
||||||
|
.ok_or_else(|| failure(code, format!("{label} needs finite coordinates")))
|
||||||
|
})
|
||||||
|
.transpose()?
|
||||||
|
.unwrap_or(0.0);
|
||||||
|
Ok(codec::types::Vector3::new(coord(0)?, coord(1)?, z))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `[x,y]` or `[x,y,z]` (missing z = 0) from `req[key]`.
|
||||||
|
fn point_field(req: &Value, key: &str) -> Parsed<codec::types::Vector3> {
|
||||||
|
point_value(req.get(key), key, "invalid_point")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn point_list_field(
|
||||||
|
spec: &Value,
|
||||||
|
key: &str,
|
||||||
|
minimum: usize,
|
||||||
|
) -> Parsed<Vec<codec::types::Vector3>> {
|
||||||
|
let points = spec[key]
|
||||||
|
.as_array()
|
||||||
|
.filter(|points| points.len() >= minimum)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
failure(
|
||||||
|
"invalid_spline_points",
|
||||||
|
format!("{key} needs at least {minimum} points"),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
points
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, point)| {
|
||||||
|
point_value(
|
||||||
|
Some(point),
|
||||||
|
&format!("{key}[{index}]"),
|
||||||
|
"invalid_spline_points",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finite_number_list_field(spec: &Value, key: &str) -> Parsed<Vec<f64>> {
|
||||||
|
let values = spec[key]
|
||||||
|
.as_array()
|
||||||
|
.filter(|values| !values.is_empty())
|
||||||
|
.ok_or_else(|| failure("invalid_spline_values", format!("{key} needs a non-empty array")))?;
|
||||||
|
values
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, value)| {
|
||||||
|
value
|
||||||
|
.as_f64()
|
||||||
|
.filter(|value| value.is_finite())
|
||||||
|
.ok_or_else(|| {
|
||||||
|
failure(
|
||||||
|
"invalid_spline_values",
|
||||||
|
format!("{key}[{index}] must be finite"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deg_field(req: &Value, key: &str) -> f64 {
|
fn deg_field(req: &Value, key: &str) -> f64 {
|
||||||
@@ -132,6 +193,72 @@ fn build_entity(spec: &Value) -> Parsed<codec::EntityType> {
|
|||||||
deg_field(spec, "end_angle_deg"),
|
deg_field(spec, "end_angle_deg"),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
"SPLINE" => {
|
||||||
|
let control_points = if spec.get("control_points").is_some() {
|
||||||
|
point_list_field(spec, "control_points", 2)?
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
let fit_points = if spec.get("fit_points").is_some() {
|
||||||
|
point_list_field(spec, "fit_points", 2)?
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
if control_points.is_empty() && fit_points.is_empty() {
|
||||||
|
return Err(failure(
|
||||||
|
"invalid_spline_points",
|
||||||
|
"spline needs control_points or fit_points (at least 2 points)",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let degree = match spec.get("degree") {
|
||||||
|
None | Some(Value::Null) => 3,
|
||||||
|
Some(value) => value.as_i64().unwrap_or(3),
|
||||||
|
};
|
||||||
|
let mut spline = if control_points.is_empty() {
|
||||||
|
Spline::from_fit_points(fit_points.clone())
|
||||||
|
} else {
|
||||||
|
Spline::from_control_points(degree as i32, control_points)
|
||||||
|
};
|
||||||
|
spline.degree = degree as i32;
|
||||||
|
if !fit_points.is_empty() {
|
||||||
|
spline.fit_points = fit_points;
|
||||||
|
}
|
||||||
|
if spec.get("knots").is_some() {
|
||||||
|
if spline.control_points.is_empty() {
|
||||||
|
return Err(failure(
|
||||||
|
"invalid_spline_values",
|
||||||
|
"spline knots require control_points",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
spline.knots = finite_number_list_field(spec, "knots")?;
|
||||||
|
}
|
||||||
|
if spec.get("weights").is_some() {
|
||||||
|
if spline.control_points.is_empty() {
|
||||||
|
return Err(failure(
|
||||||
|
"invalid_spline_values",
|
||||||
|
"spline weights require control_points",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
spline.weights = finite_number_list_field(spec, "weights")?;
|
||||||
|
spline.flags.rational = true;
|
||||||
|
}
|
||||||
|
if let Some(value) = spec["closed"].as_bool() {
|
||||||
|
spline.flags.closed = value;
|
||||||
|
}
|
||||||
|
if let Some(value) = spec["periodic"].as_bool() {
|
||||||
|
spline.flags.periodic = value;
|
||||||
|
}
|
||||||
|
if let Some(value) = spec["rational"].as_bool() {
|
||||||
|
spline.flags.rational = value;
|
||||||
|
}
|
||||||
|
if let Some(value) = spec["planar"].as_bool() {
|
||||||
|
spline.flags.planar = value;
|
||||||
|
}
|
||||||
|
if let Some(value) = spec["linear"].as_bool() {
|
||||||
|
spline.flags.linear = value;
|
||||||
|
}
|
||||||
|
EntityType::Spline(spline)
|
||||||
|
}
|
||||||
"LWPOLYLINE" | "POLYLINE" => {
|
"LWPOLYLINE" | "POLYLINE" => {
|
||||||
let vertices = spec["vertices"]
|
let vertices = spec["vertices"]
|
||||||
.as_array()
|
.as_array()
|
||||||
@@ -264,7 +391,7 @@ fn build_entity(spec: &Value) -> Parsed<codec::EntityType> {
|
|||||||
return Err(failure(
|
return Err(failure(
|
||||||
"unknown_entity_type",
|
"unknown_entity_type",
|
||||||
format!(
|
format!(
|
||||||
"Unknown entity type '{other}'. Supported: Line, Circle, Arc, LwPolyline, Point, Text, MText, Insert, Solid, Hatch"
|
"Unknown entity type '{other}'. Supported: Line, Circle, Arc, Spline, LwPolyline, Point, Text, MText, Insert, Solid, Hatch"
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,6 +432,37 @@ fn plot_per_page_writes_one_pdf_per_layout() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn entities_create_supports_control_and_fit_point_splines() {
|
||||||
|
let mut app = OpenCADStudio::new_for_test();
|
||||||
|
app.automation_op(r#"{"op":"new"}"#);
|
||||||
|
let control = mutate(
|
||||||
|
&mut app,
|
||||||
|
r#"{"protocol":1,"op":"entities_create","request_id":"spline-control","document_id":{doc},"entities":[
|
||||||
|
{"type":"Spline","degree":2,"knots":[0,0,0,1,1,1],"control_points":[[0,0,0],[5,5,0],[10,0,0]],"weights":[1,2,1],"layer":"CURVES"}
|
||||||
|
]}"#,
|
||||||
|
);
|
||||||
|
assert_eq!(control["ok"], true, "{control}");
|
||||||
|
let fit = mutate(
|
||||||
|
&mut app,
|
||||||
|
r#"{"protocol":1,"op":"entities_create","request_id":"spline-fit","document_id":{doc},"entities":[
|
||||||
|
{"type":"Spline","degree":3,"fit_points":[[20,0],[25,5],[30,0]]}
|
||||||
|
]}"#,
|
||||||
|
);
|
||||||
|
assert_eq!(fit["ok"], true, "{fit}");
|
||||||
|
let query = app.automation_op(r#"{"op":"query","type":"Spline","detail":"geometry"}"#);
|
||||||
|
assert_eq!(query["count"], 2, "{query}");
|
||||||
|
let control_entity = query["entities"]
|
||||||
|
.as_array()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.find(|entity| entity["layer"] == "CURVES")
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(control_entity["degree"], 2, "{control_entity}");
|
||||||
|
assert_eq!(control_entity["control_points"].as_array().unwrap().len(), 3);
|
||||||
|
assert_eq!(control_entity["weights"], json!([1.0, 2.0, 1.0]));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hatch_boundary_is_flattened_in_query() {
|
fn hatch_boundary_is_flattened_in_query() {
|
||||||
let mut app = OpenCADStudio::new_for_test();
|
let mut app = OpenCADStudio::new_for_test();
|
||||||
|
|||||||
Reference in new issue
Block a user