fix(doc): designs saved before format 14 keep their piece names (#136)
Pieces a cut splits a body into are named after their faces since names follow history (lineage::order_lumps), so they keep their body names when an upstream edit moves the cut. The kernel's shell order named them before, and the two orders can differ: a slot along X through a 40x30x10 block swaps Body1 and Body1 (1). A design saved by an older build stores body names meaning the old order (fillet and chamfer bodies, targets, participants, materials, appearances, joints), so it would act on the other piece. Each feature now says which order names its pieces (Feature::shell_piece_order). The v13 to v14 step sets it on every saved feature, so an older design evaluates as it was saved; features made from now on, in new or old designs, name their pieces after their faces. The corpus gains split-body-pieces, saved by v0.3.2: a fillet, a chamfer and an appearance refer to the two pieces by body name. Without the setting the two pieces swap names (the corpus test fails); with it they stay as saved. Migration tests that compared an upgraded design with the original now expect the setting on its features. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J53sR6gEaVt56soFDpTck8
This commit is contained in:
1 parent
cc668668ce
commit
a9efae864d
11 files changed
+710
-5
No files matched your search
@@ -178,7 +178,8 @@ fn shell_planar_format13_keeps_v12_documents_and_refuses_future() {
|
||||
let mut current: serde_json::Value = serde_json::from_str(&loaded.to_json()).unwrap();
|
||||
assert_eq!(current["format"], crate::format::format_string(crate::format::FORMAT));
|
||||
current["format"] = json!("solvecraft/12");
|
||||
assert_eq!(legacy, current, "identity migration must preserve every prior field");
|
||||
crate::format::pieces_in_shell_order(&mut legacy["features"]);
|
||||
assert_eq!(legacy, current, "the migration preserves every prior field");
|
||||
legacy["format"] = json!(crate::format::format_string(crate::format::FORMAT + 1));
|
||||
assert!(crate::format::read(&legacy.to_string()).is_err());
|
||||
}
|
||||
|
||||
@@ -1567,6 +1567,11 @@ pub struct Feature {
|
||||
/// component's bodies.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub participants: Vec<String>,
|
||||
/// The pieces this feature splits a body into take its body names in the kernel's shell order,
|
||||
/// as builds before format 14 named them, instead of after their faces (`lineage::order_lumps`).
|
||||
/// Set on the features of designs saved before then, whose stored body names mean that order.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub shell_piece_order: bool,
|
||||
#[serde(flatten)]
|
||||
pub kind: FeatureKind,
|
||||
}
|
||||
@@ -1901,6 +1906,7 @@ impl Document {
|
||||
edge_names: Vec::new(),
|
||||
face_names: Vec::new(),
|
||||
participants: Vec::new(),
|
||||
shell_piece_order: false,
|
||||
kind,
|
||||
};
|
||||
match self.marker {
|
||||
|
||||
@@ -300,8 +300,11 @@ impl ModelState {
|
||||
let feature = f.id;
|
||||
let depth = before.bodies.iter().filter_map(|b| b.names.origin.as_ref().map(|o| o.depth)).max().unwrap_or(0) + 1;
|
||||
let origin = Arc::new(crate::naming::NamingOrigin { feature: f.clone(), before: before.clone(), depth, mats });
|
||||
// Pieces a cut left are named after their faces, not the kernel's shell order.
|
||||
crate::lineage::order_lumps(&mut self.bodies, &origin);
|
||||
// Pieces a cut left are named after their faces, not the kernel's shell order (unless
|
||||
// the feature was saved when they were named in that order).
|
||||
if !f.shell_piece_order {
|
||||
crate::lineage::order_lumps(&mut self.bodies, &origin);
|
||||
}
|
||||
for b in &mut self.bodies {
|
||||
if !matches!(f.kind, FeatureKind::BodyTransfer { .. })
|
||||
&& let Some(old) = before.bodies.iter().find(|old| old.name == b.name && old.transferred)
|
||||
|
||||
@@ -44,6 +44,8 @@
|
||||
//! where their centres lay (`F2:top#2`). Names stored by older formats were worked out from
|
||||
//! geometry, and a few of them name a face that history names differently; they get the
|
||||
//! legacy mark (`?F2:top#2`) and are found again as the build that saved them found them.
|
||||
//! Pieces a feature splits a body into are named after their faces; features saved before
|
||||
//! keep the kernel's shell order (`shell_piece_order`), which their stored body names mean.
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
@@ -90,6 +92,10 @@ const JSON_STEPS: [JsonStep; 13] = [
|
||||
/// `"type": "face"|"edge"`, picked `at` a point), wherever a feature holds one;
|
||||
/// - joint snaps on a face (`face_center`, `circle_center`), in joints and named origins;
|
||||
/// - face appearances, but not strict ones, whose names are certified lineage.
|
||||
///
|
||||
/// Every feature also keeps naming the pieces it splits a body into in the kernel's shell order
|
||||
/// (`Feature::shell_piece_order`): the body names stored after it (bodies, targets, participants,
|
||||
/// materials, appearances, joints) mean the pieces in that order.
|
||||
fn v13_to_v14(v: &mut Value) -> Result<()> {
|
||||
if let Some(Value::Array(features)) = v.get_mut("features") {
|
||||
for f in features.iter_mut() {
|
||||
@@ -104,6 +110,7 @@ fn v13_to_v14(v: &mut Value) -> Result<()> {
|
||||
{
|
||||
links.iter_mut().filter_map(|l| l.get_mut("face_name")).for_each(mark);
|
||||
}
|
||||
o.insert("shell_piece_order".into(), Value::Bool(true));
|
||||
references(f);
|
||||
}
|
||||
}
|
||||
@@ -163,6 +170,15 @@ fn mark_all(names: Option<&mut Value>) {
|
||||
}
|
||||
}
|
||||
|
||||
/// `features` (a design's, as JSON) as an upgrade from before format 14 leaves them: each keeps
|
||||
/// the shell order of the pieces it makes (see [`v13_to_v14`]).
|
||||
#[cfg(test)]
|
||||
pub(crate) fn pieces_in_shell_order(features: &mut Value) {
|
||||
for f in features.as_array_mut().into_iter().flatten().filter_map(Value::as_object_mut) {
|
||||
f.insert("shell_piece_order".into(), Value::Bool(true));
|
||||
}
|
||||
}
|
||||
|
||||
/// A stored name with the legacy mark (an empty name stays empty: no reference).
|
||||
fn mark(n: &mut Value) {
|
||||
if let Value::String(s) = n
|
||||
@@ -745,6 +761,8 @@ mod tests {
|
||||
assert_eq!(v["assembly"]["origins"][0]["origin"]["snap"]["face"], "?F3:side");
|
||||
assert_eq!(v["appearances"]["faces"][0]["name"], "?F2:top#1");
|
||||
assert_eq!(v["appearances"]["faces"][1]["name"], "F2:+x", "strict appearance names are certified lineage");
|
||||
// Every feature keeps naming the pieces it splits a body into as the saving build did.
|
||||
assert!(v["features"].as_array().unwrap().iter().all(|f| f["shell_piece_order"] == true));
|
||||
// The step never marks twice.
|
||||
let once = v.clone();
|
||||
v13_to_v14(&mut v).unwrap();
|
||||
@@ -768,6 +786,9 @@ mod tests {
|
||||
});
|
||||
let mut v = file.clone();
|
||||
v13_to_v14(&mut v).unwrap();
|
||||
for f in v["features"].as_array_mut().unwrap() {
|
||||
f.as_object_mut().unwrap().remove("shell_piece_order");
|
||||
}
|
||||
assert_eq!(v, file);
|
||||
}
|
||||
|
||||
@@ -1181,6 +1202,7 @@ mod tests {
|
||||
legacy["format"] = Value::from("solvecraft/6");
|
||||
let original_table = legacy["configs"].clone();
|
||||
let upgraded = read(&legacy.to_string()).unwrap();
|
||||
d.features.iter_mut().for_each(|f| f.shell_piece_order = true);
|
||||
assert_eq!(upgraded, d, "the identity migration preserves the complete design");
|
||||
assert_eq!(serde_json::to_value(&upgraded.configs).unwrap(), original_table);
|
||||
assert_eq!(upgraded.format, format_string(FORMAT));
|
||||
@@ -1232,9 +1254,11 @@ mod tests {
|
||||
d.add_feature(crate::FeatureKind::Sketch { plane: crate::PlaneRef::Origin { name: "XY".into() }, sketch }, Some("Dimensioned")).unwrap();
|
||||
let mut legacy: Value = serde_json::from_str(&d.to_json()).unwrap();
|
||||
legacy["format"] = Value::from("solvecraft/7");
|
||||
let features = legacy["features"].clone();
|
||||
let mut features = legacy["features"].clone();
|
||||
pieces_in_shell_order(&mut features);
|
||||
let params = legacy["params"].clone();
|
||||
let upgraded = read(&legacy.to_string()).unwrap();
|
||||
d.features.iter_mut().for_each(|f| f.shell_piece_order = true);
|
||||
assert_eq!(upgraded, d);
|
||||
let upgraded_json: Value = serde_json::from_str(&upgraded.to_json()).unwrap();
|
||||
assert_eq!(upgraded_json["features"], features);
|
||||
|
||||
Reference in new issue
Block a user