refactor(doc): format 14 marks the fields that store names, by schema place
The v13 to v14 step walked the whole design and marked any JSON key named face, face_name or face_names, so any future field with one of those names would be marked too. It now marks the fields that store a face or edge name where the schema puts them: each feature's face_names and fillets' and chamfers' edge_names, a sketch feature's links, joint snaps on a face in joints and named origins, and face appearances. Face and edge references (PlaneRef::Face, GeoRef::Face/Edge) can sit in many features, so they are still found anywhere in a feature, by their tag and their picked point. The test fixtures now have the shape real files have, and a new test checks that same-named keys elsewhere are left alone. 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
cfdd8a533b
commit
cc668668ce
1 file changed
+99
-27
+99
-27
@@ -81,32 +81,42 @@ const JSON_STEPS: [JsonStep; 13] = [
|
|||||||
|
|
||||||
/// Stored face and edge names were worked out from geometry before format 14; they get the
|
/// Stored face and edge names were worked out from geometry before format 14; they get the
|
||||||
/// legacy mark (`?`, see `naming::LEGACY_MARK`) so they are found again as those builds found
|
/// legacy mark (`?`, see `naming::LEGACY_MARK`) so they are found again as those builds found
|
||||||
/// them, and an older design evaluates as it was saved. Every place a design stores such a name
|
/// them, and an older design evaluates as it was saved. The step marks the fields that store
|
||||||
/// is marked: fillets' and chamfers' `edge_names`, features' `face_names`, face and edge
|
/// such a name, by their place in the schema:
|
||||||
/// references (`"type": "face"|"edge"` with a `name`), sketch links' `face_name`, joint snaps'
|
/// - each feature's `face_names`, and fillets' and chamfers' `edge_names` (other features' edge
|
||||||
/// `face`, and face appearances (but not strict ones, whose names are certified lineage).
|
/// names are sheet metal's own);
|
||||||
|
/// - a sketch feature's links' `face_name`;
|
||||||
|
/// - face and edge references (`PlaneRef::Face`, `GeoRef::Face`, `GeoRef::Edge`: tagged
|
||||||
|
/// `"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.
|
||||||
fn v13_to_v14(v: &mut Value) -> Result<()> {
|
fn v13_to_v14(v: &mut Value) -> Result<()> {
|
||||||
fn walk(v: &mut Value) {
|
if let Some(Value::Array(features)) = v.get_mut("features") {
|
||||||
match v {
|
for f in features.iter_mut() {
|
||||||
Value::Object(o) => {
|
let Value::Object(o) = f else { continue };
|
||||||
let kind = o.get("type").and_then(Value::as_str).map(str::to_string);
|
let kind = o.get("type").and_then(Value::as_str).map(str::to_string);
|
||||||
let blend = matches!(kind.as_deref(), Some("fillet" | "chamfer"));
|
if matches!(kind.as_deref(), Some("fillet" | "chamfer")) {
|
||||||
for (key, x) in o.iter_mut() {
|
mark_all(o.get_mut("edge_names"));
|
||||||
match (key.as_str(), x) {
|
|
||||||
// Other features' edge names are sheet metal's own, not these.
|
|
||||||
("edge_names", Value::Array(names)) if blend => names.iter_mut().for_each(mark),
|
|
||||||
("face_names", Value::Array(names)) => names.iter_mut().for_each(mark),
|
|
||||||
("face_name" | "face", n @ Value::String(_)) => mark(n),
|
|
||||||
("name", n @ Value::String(_)) if matches!(kind.as_deref(), Some("face" | "edge")) => mark(n),
|
|
||||||
(_, x) => walk(x),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Value::Array(a) => a.iter_mut().for_each(walk),
|
mark_all(o.get_mut("face_names"));
|
||||||
_ => {}
|
if kind.as_deref() == Some("sketch")
|
||||||
|
&& let Some(Value::Array(links)) = o.get_mut("sketch").and_then(|s| s.get_mut("links"))
|
||||||
|
{
|
||||||
|
links.iter_mut().filter_map(|l| l.get_mut("face_name")).for_each(mark);
|
||||||
|
}
|
||||||
|
references(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
walk(v);
|
if let Some(Value::Array(joints)) = v.get_mut("assembly").and_then(|a| a.get_mut("joints")) {
|
||||||
|
for j in joints.iter_mut() {
|
||||||
|
for side in ["a", "b"] {
|
||||||
|
snap(j.get_mut(side));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(Value::Array(origins)) = v.get_mut("assembly").and_then(|a| a.get_mut("origins")) {
|
||||||
|
origins.iter_mut().for_each(|o| snap(o.get_mut("origin")));
|
||||||
|
}
|
||||||
// Face appearances carry the face's name without a type tag.
|
// Face appearances carry the face's name without a type tag.
|
||||||
if let Some(Value::Array(faces)) = v.get_mut("appearances").and_then(|a| a.get_mut("faces")) {
|
if let Some(Value::Array(faces)) = v.get_mut("appearances").and_then(|a| a.get_mut("faces")) {
|
||||||
for face in faces.iter_mut().filter(|f| f.get("strict_name").and_then(Value::as_bool) != Some(true)) {
|
for face in faces.iter_mut().filter(|f| f.get("strict_name").and_then(Value::as_bool) != Some(true)) {
|
||||||
@@ -118,6 +128,41 @@ fn v13_to_v14(v: &mut Value) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mark the `name` of every face or edge reference in `v` (see [`v13_to_v14`]).
|
||||||
|
fn references(v: &mut Value) {
|
||||||
|
match v {
|
||||||
|
Value::Object(o) => {
|
||||||
|
let reference = matches!(o.get("type").and_then(Value::as_str), Some("face" | "edge")) && o.contains_key("at");
|
||||||
|
for (key, x) in o.iter_mut() {
|
||||||
|
if reference && key == "name" {
|
||||||
|
mark(x);
|
||||||
|
} else {
|
||||||
|
references(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Array(a) => a.iter_mut().for_each(references),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark the face name of a joint origin's snap on a face.
|
||||||
|
fn snap(origin: Option<&mut Value>) {
|
||||||
|
if let Some(snap) = origin.and_then(|o| o.get_mut("snap"))
|
||||||
|
&& matches!(snap.get("type").and_then(Value::as_str), Some("face_center" | "circle_center"))
|
||||||
|
&& let Some(n) = snap.get_mut("face")
|
||||||
|
{
|
||||||
|
mark(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark every name in a list of stored names.
|
||||||
|
fn mark_all(names: Option<&mut Value>) {
|
||||||
|
if let Some(Value::Array(names)) = names {
|
||||||
|
names.iter_mut().for_each(mark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A stored name with the legacy mark (an empty name stays empty: no reference).
|
/// A stored name with the legacy mark (an empty name stays empty: no reference).
|
||||||
fn mark(n: &mut Value) {
|
fn mark(n: &mut Value) {
|
||||||
if let Value::String(s) = n
|
if let Value::String(s) = n
|
||||||
@@ -670,13 +715,19 @@ mod tests {
|
|||||||
"features": [
|
"features": [
|
||||||
{"type": "fillet", "edge_names": ["F2:side:l2|F2:top#2~1", ""], "face_names": ["F2:top#1", ""]},
|
{"type": "fillet", "edge_names": ["F2:side:l2|F2:top#2~1", ""], "face_names": ["F2:top#1", ""]},
|
||||||
{"type": "sheet_flange", "edge_names": ["base:l1:top"]},
|
{"type": "sheet_flange", "edge_names": ["base:l1:top"]},
|
||||||
{"type": "sketch", "plane": {"type": "face", "name": "F2:top#1", "at": [0, 0, 0]},
|
{"type": "sketch", "plane": {"type": "face", "plane": {}, "at": [0, 0, 0], "name": "F2:top#1"},
|
||||||
"sketch": {"links": [{"face_name": "F2:top#2"}]}},
|
"sketch": {"links": [{"id": "l1", "face_name": "F2:top#2"}]}},
|
||||||
{"type": "construction_axis", "def": {"a": {"type": "edge", "name": "F2:start|F2:side:l1"}, "b": {"type": "face", "name": "F2:start"}}},
|
{"type": "construction_axis", "def": {"type": "two_points",
|
||||||
|
"a": {"type": "edge", "at": [0, 0, 0], "name": "F2:start|F2:side:l1"},
|
||||||
|
"b": {"type": "face", "at": [0, 0, 0], "name": "F2:start"}}},
|
||||||
{"name": "Hole #2", "type": "extrude"},
|
{"name": "Hole #2", "type": "extrude"},
|
||||||
{"type": "thread", "face": [1, 2, 3]}
|
{"type": "thread", "face": [1, 2, 3]}
|
||||||
],
|
],
|
||||||
"assembly": {"joints": [{"snap": {"type": "face_center", "face": "F2:top#2"}}]},
|
"assembly": {
|
||||||
|
"joints": [{"a": {"occurrence": 0, "snap": {"type": "face_center", "pick": [0, 0, 0], "face": "F2:top#2"}},
|
||||||
|
"b": {"occurrence": 1, "snap": {"type": "point", "point": [0, 0, 0]}}}],
|
||||||
|
"origins": [{"name": "O1", "origin": {"occurrence": 0, "snap": {"type": "circle_center", "pick": [0, 0, 0], "face": "F3:side"}}}]
|
||||||
|
},
|
||||||
"appearances": {"faces": [{"body": "Body1", "name": "F2:top#1"}, {"body": "Body1", "name": "F2:+x", "strict_name": true}]}
|
"appearances": {"faces": [{"body": "Body1", "name": "F2:top#1"}, {"body": "Body1", "name": "F2:+x", "strict_name": true}]}
|
||||||
});
|
});
|
||||||
let mut v = file.clone();
|
let mut v = file.clone();
|
||||||
@@ -690,7 +741,8 @@ mod tests {
|
|||||||
assert_eq!(v["features"][3]["def"]["b"]["name"], "?F2:start");
|
assert_eq!(v["features"][3]["def"]["b"]["name"], "?F2:start");
|
||||||
assert_eq!(v["features"][4]["name"], "Hole #2", "a feature's own name is not a face name");
|
assert_eq!(v["features"][4]["name"], "Hole #2", "a feature's own name is not a face name");
|
||||||
assert_eq!(v["features"][5]["face"], serde_json::json!([1, 2, 3]));
|
assert_eq!(v["features"][5]["face"], serde_json::json!([1, 2, 3]));
|
||||||
assert_eq!(v["assembly"]["joints"][0]["snap"]["face"], "?F2:top#2");
|
assert_eq!(v["assembly"]["joints"][0]["a"]["snap"]["face"], "?F2:top#2");
|
||||||
|
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"][0]["name"], "?F2:top#1");
|
||||||
assert_eq!(v["appearances"]["faces"][1]["name"], "F2:+x", "strict appearance names are certified lineage");
|
assert_eq!(v["appearances"]["faces"][1]["name"], "F2:+x", "strict appearance names are certified lineage");
|
||||||
// The step never marks twice.
|
// The step never marks twice.
|
||||||
@@ -699,6 +751,26 @@ mod tests {
|
|||||||
assert_eq!(v, once);
|
assert_eq!(v, once);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Only the fields that store a face or edge name are marked: other fields with the same key
|
||||||
|
/// names, and objects tagged `face` or `edge` that are not references, are left alone.
|
||||||
|
#[test]
|
||||||
|
fn version_thirteen_marking_targets_name_fields_not_key_names() {
|
||||||
|
let file = serde_json::json!({
|
||||||
|
"format": "solvecraft/13",
|
||||||
|
"features": [
|
||||||
|
{"type": "extrude", "options": {"face": "flat", "face_name": "label", "face_names": ["a"]}},
|
||||||
|
{"type": "fillet", "settings": {"edge_names": ["kept"]}},
|
||||||
|
{"type": "sketch", "sketch": {"style": {"face_name": "kept"}}},
|
||||||
|
{"type": "split_body", "tool": {"type": "face", "name": "not a reference"}}
|
||||||
|
],
|
||||||
|
"assembly": {"joints": [{"a": {"occurrence": 0, "snap": {"type": "frame", "face": "kept"}}}]},
|
||||||
|
"notes": {"face": "F2:top", "face_name": "F2:top", "face_names": ["F2:top"]}
|
||||||
|
});
|
||||||
|
let mut v = file.clone();
|
||||||
|
v13_to_v14(&mut v).unwrap();
|
||||||
|
assert_eq!(v, file);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn version_nine_wire_spaces_preserve_saved_points_and_explicit_markers() {
|
fn version_nine_wire_spaces_preserve_saved_points_and_explicit_markers() {
|
||||||
use solvecraft_geom::Vec3;
|
use solvecraft_geom::Vec3;
|
||||||
|
|||||||
Reference in new issue
Block a user