app
Fusion - the Fusion 360 Bridge
Public Made by Adomby adom
Drive Autodesk Fusion 360 from the cloud via Adom Bridge: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.
master
Drew Owens
Share frozen native Rust mechanical development checkpoint and qualification scope
b2d7cdb
27d ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
use super::*;
use std::cell::{Cell, RefCell};
const DOC: &str = "d:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:0000000000000001";
const ID: &str = "s:d:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:0000000000000001:0000000000000001";
#[derive(Default)]
pub(crate) struct Fake {
pub(crate) values: RefCell<Vec<ObservedEntity>>,
pub(crate) calls: Cell<usize>,
pub(crate) fail_at: Cell<usize>,
pub(crate) mode: Cell<u32>,
pub(crate) changed: Cell<bool>,
reads: Cell<usize>,
}
impl Backend for Fake {
type Handle = u64;
fn root(&self) -> Result<RootState, String> {
if self.mode.get() == 11 && !self.values.borrow().is_empty() {
panic!("root getter after effect");
}
Ok(RootState {
active: true,
parametric: true,
hybrid: true,
configured: false,
root_revision: format!("root-{}", self.values.borrow().len()),
sketch_count: 1,
timeline_count: 1,
timeline_position: 1,
})
}
fn command(&self) -> Result<String, String> {
if self.mode.get() == 12 && !self.values.borrow().is_empty() {
panic!("command getter after prefix");
}
Ok(if self.mode.get() == 8 {
"BusyCommand"
} else {
"SelectCommand"
}
.into())
}
fn create(&self, _: u32) -> CreateAttempt<u64> {
panic!("geometry must never create another sketch")
}
fn read(&self, handle: &u64) -> Result<SketchState, String> {
if self.mode.get() == 13 && !self.values.borrow().is_empty() {
panic!("sketch getter after effect");
}
assert_eq!(*handle, 42);
self.reads.set(self.reads.get() + 1);
let values = self.values.borrow();
Ok(SketchState {
name: "Sketch1".into(),
sdk_revision: format!("rev-{}-{}", values.len(), self.changed.get()),
plane: 1,
transform: [0.; 16],
curves: values
.iter()
.filter(|v| !matches!(v.geometry, Geometry::Point { .. }))
.count() as u64,
points: 1 + values.len() as u64,
profiles: if values.is_empty() { 0 } else { 2 },
dimensions: 0,
constraints: 0,
health: if self.mode.get() == 7 && !values.is_empty() {
1
} else {
0
},
deferred: false,
})
}
}
impl GeometryBackend for Fake {
fn empty_origin_only(&self, handle: &u64) -> Result<bool, String> {
assert_eq!(*handle, 42);
Ok(self.values.borrow().is_empty() && self.mode.get() != 9)
}
fn insert(&self, handle: &u64, entity: &Entity) -> InsertAttempt {
assert_eq!(*handle, 42);
let call = self.calls.get() + 1;
self.calls.set(call);
if self.mode.get() == 1 && call == self.fail_at.get() {
return InsertAttempt {
lease: None,
invoked: false,
acknowledged: false,
stage: "prepare",
};
}
let mut geometry = entity.geometry.clone();
if let Geometry::Arc {
start_cm,
end_cm,
sweep_radians,
..
} = &mut geometry
{
if *sweep_radians < 0. {
std::mem::swap(start_cm, end_cm);
*sweep_radians = -*sweep_radians;
}
}
let index = self.values.borrow().len() as u64;
self.values.borrow_mut().push(ObservedEntity {
construction: if matches!(geometry, Geometry::Point { .. }) {
None
} else {
Some(entity.construction.unwrap_or(false))
},
geometry,
reference: false,
});
if self.mode.get() == 3 && call == self.fail_at.get() {
panic!("panic after SDK effect");
}
InsertAttempt {
lease: Some(if self.mode.get() == 10 { 0 } else { index }),
invoked: true,
acknowledged: !(self.mode.get() == 2 && call == self.fail_at.get()),
stage: if self.mode.get() == 2 {
"construction"
} else {
"complete"
},
}
}
fn entity_equalities(&self, _: &u64, leases: &[u64]) -> Result<Vec<Vec<bool>>, String> {
Ok(leases
.iter()
.map(|a| leases.iter().map(|b| a == b).collect())
.collect())
}
fn entity(&self, _: &u64, lease: u64) -> Result<ObservedEntity, String> {
if self.mode.get() == 14 && lease == 1 {
panic!("entity getter after prefix");
}
if self.mode.get() == 4 {
return Err("readback lost".into());
}
let mut v = self.values.borrow()[lease as usize].clone();
if self.mode.get() == 5 {
v.reference = true;
}
if self.mode.get() == 6 && self.values.borrow().len() > 1 && lease == 0 {
if let Geometry::Point { position_cm } = &mut v.geometry {
position_cm[0] += 1.;
}
}
Ok(v)
}
}
pub(crate) fn book() -> Book<u64> {
let mut b = Book::default();
b.entries.push(Entry {
id: ID.into(),
document_id: DOC.into(),
handle: Some(42),
retired: Cell::new(false),
profile_published: Cell::new(false),
profile_retired: Cell::new(false),
geometry_leases: None,
entities: RefCell::new(None),
entities_retired: Cell::new(false),
dimension_endpoints_published: Cell::new(false),
});
b
}
pub(crate) fn request(b: &Book<u64>, sdk: &Fake, items: Value, operation: &str) -> PreparedRequest {
let data = b.inspect(sdk, DOC, Some(ID)).unwrap();
fusion_mechanical_core::prepare_request(json!({"op":"sketch_geometry","sessionId":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","operationId":operation,"target":data["target"],"sketch":{"documentId":DOC,"revision":data["target"]["revision"],"objectType":"Sketch","sketchId":ID},"items":items}),"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap()
}
fn points() -> Value {
json!([{"type":"point","position":[10,20]},{"type":"point","position":[30,40]}])
}
#[test]
fn all_eight_primitives_have_actual_readback_and_explicit_non_atomic_scope() {
let mut book = book();
let sdk = Fake::default();
let items = json!([
{"type":"point","position":[10,20]},
{"type":"line","start":[10,0],"end":[20,0],"construction":true},
{"type":"rectangle","start":[30,0],"end":[50,10]},
{"type":"circle","center":[70,0],"radius":10},
{"type":"arc","center":[100,0],"start":[110,0],"sweep":-90},
{"type":"polygon","center":[140,0],"radius":10,"sides":3},
{"type":"slot","start":[160,0],"end":[180,0],"width":10},
{"type":"spline","points":[[210,0],[220,10],[230,0]],"closed":true}
]);
let request = request(&book, &sdk, items, "all");
let plan = book.geometry_preflight(&sdk, &request).unwrap();
assert_eq!(sdk.calls.get(), 0);
let out = book.execute_geometry(&sdk, plan);
assert_eq!(out["success"], true, "{out}");
assert_eq!(sdk.calls.get(), 16);
assert_eq!(out["atomic"], false);
assert_eq!(out["geometry"]["itemsCompleted"], 8);
assert_eq!(out["geometry"]["entitiesCompleted"], 16);
assert_eq!(out["data"]["sketch"]["profiles"], 2);
assert_eq!(
out["geometry"]["observed"][0]["observed"]["geometry"]["position_cm"],
json!([1., 2., 0.])
);
assert_eq!(
out["geometry"]["observed"][1]["observed"]["construction"],
true
);
assert_eq!(
out["geometry"]["observed"][6]["observed"]["geometry"]["radius_cm"],
1.
);
let arc = &out["geometry"]["observed"][7]["observed"]["geometry"];
assert!(near(arc["start_cm"][0].as_f64().unwrap(), 10.));
assert!(near(arc["start_cm"][1].as_f64().unwrap(), -1.));
assert_eq!(arc["end_cm"], json!([11., 0., 0.]));
assert!(near(
arc["sweep_radians"].as_f64().unwrap(),
std::f64::consts::FRAC_PI_2
));
assert!(book.inspect(&sdk, DOC, Some(ID)).is_ok());
}
#[test]
fn whole_plan_rejects_late_invalid_geometry_before_any_sdk_effect() {
let b = book();
let sdk = Fake::default();
for items in [
json!([{"type":"point","position":[1,2]},{"type":"circle","center":[1,2],"radius":0.000001}]),
json!([{"type":"point","position":[1,2,1]}]),
json!([{"type":"arc","center":[0,0],"start":[10,0],"sweep":360}]),
json!([{"type":"line","start":[0,0],"end":[0.000001,0]}]),
] {
assert!(b
.geometry_preflight(&sdk, &request(&b, &sdk, items, "invalid"))
.is_err());
}
assert_eq!(sdk.calls.get(), 0);
}
#[test]
fn capacity_rejects_expansion_and_item_count_before_effect() {
let b = book();
let sdk = Fake::default();
for items in [
json!([{"type":"polygon","center":[0,0],"radius":10,"sides":65}]),
json!(vec![json!({"type":"point","position":[1,2]}); 33]),
] {
assert!(prepare_arguments(&request(&b, &sdk, items, "bound")).is_err());
}
assert_eq!(sdk.calls.get(), 0);
}
#[test]
fn exact_ids_and_both_revision_witnesses_are_required() {
let b = book();
let sdk = Fake::default();
let original = request(&b, &sdk, points(), "identity");
for case in 0..5 {
let mut a = original.arguments().clone();
match case {
0 => a["sketch"]["sketchId"] = json!("same-name-other-object"),
1 => a["sketch"]["documentId"] = json!("foreign"),
2 => a["sketch"]["revision"] = json!("stale"),
3 => a["sketch"]["entityToken"] = json!("unsupported"),
_ => a["target"]["revision"] = json!("sketch-projection-v1:stale"),
};
let r =
fusion_mechanical_core::prepare_request(a, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap();
assert!(b.geometry_preflight(&sdk, &r).is_err());
}
assert_eq!(sdk.calls.get(), 0);
}
#[test]
fn stale_after_preflight_refuses_without_insertion() {
let mut b = book();
let sdk = Fake::default();
let p = b
.geometry_preflight(&sdk, &request(&b, &sdk, points(), "stale"))
.unwrap();
sdk.changed.set(true);
let out = b.execute_geometry(&sdk, p);
assert_eq!(out["nativeExecutionStarted"], false);
assert_eq!(sdk.calls.get(), 0);
}
#[test]
fn origin_only_guard_refuses_non_curve_geometry_and_text() {
let b = book();
let sdk = Fake::default();
sdk.mode.set(9);
assert!(b
.geometry_preflight(&sdk, &request(&b, &sdk, points(), "hidden-content"))
.is_err());
assert_eq!(sdk.calls.get(), 0);
}
#[test]
fn pre_invoke_failure_has_no_observed_effect_and_releases_fault_latch() {
let mut b = book();
let sdk = Fake::default();
let p = b
.geometry_preflight(&sdk, &request(&b, &sdk, points(), "refuse"))
.unwrap();
sdk.mode.set(1);
sdk.fail_at.set(1);
let out = b.execute_geometry(&sdk, p);
assert_eq!(out["nativeExecutionStarted"], false);
assert!(sdk.values.borrow().is_empty());
assert!(b.inspect(&sdk, DOC, Some(ID)).is_ok());
}
#[test]
fn failed_second_effect_preserves_prefix_and_blocks_new_attempts() {
for mode in [1, 2] {
let mut b = book();
let sdk = Fake::default();
let r = request(&b, &sdk, points(), "partial");
let p = b.geometry_preflight(&sdk, &r).unwrap();
sdk.mode.set(mode);
sdk.fail_at.set(2);
let out = b.execute_geometry(&sdk, p);
assert_eq!(out["outcome"], "unknown");
assert_eq!(out["completedEntities"].as_array().unwrap().len(), 1);
assert_eq!(out["failedAttempt"]["inputIndex"], 1);
assert_eq!(out["rollbackAttempted"], false);
assert!(b.geometry_preflight(&sdk, &r).is_err());
assert_eq!(sdk.calls.get(), 2);
}
}
#[test]
fn lost_readback_reference_and_final_solver_changes_never_report_success() {
for mode in [4, 5, 6, 7, 10] {
let mut b = book();
let sdk = Fake::default();
let p = b
.geometry_preflight(&sdk, &request(&b, &sdk, points(), "fault"))
.unwrap();
sdk.mode.set(mode);
let out = b.execute_geometry(&sdk, p);
assert_eq!(out["outcome"], "unknown", "mode {mode}: {out}");
assert!(b.inspect(&sdk, DOC, Some(ID)).is_err());
}
}
#[test]
fn panic_after_effect_latches_book_and_releases_only_exact_parent_once() {
let mut b = book();
let sdk = Fake::default();
let r = request(&b, &sdk, points(), "panic");
let p = b.geometry_preflight(&sdk, &r).unwrap();
sdk.mode.set(3);
sdk.fail_at.set(2);
let out = b.execute_geometry(&sdk, p);
assert_eq!(out["outcome"], "unknown");
assert_eq!(out["errorCode"], "native_geometry_insert_panicked");
assert_eq!(out["completedEntities"].as_array().unwrap().len(), 1);
assert_eq!(sdk.values.borrow().len(), 2);
assert!(b.geometry_preflight(&sdk, &r).is_err());
let mut releases = vec![];
b.close(|h| {
releases.push(h);
Ok(())
})
.unwrap();
b.close(|_| panic!("release twice")).unwrap();
assert_eq!(releases, vec![42]);
}
#[test]
fn uncertain_entity_parent_release_blocks_unload_and_retry() {
let mut b = book();
let sdk = Fake::default();
let p = b
.geometry_preflight(&sdk, &request(&b, &sdk, points(), "created"))
.unwrap();
assert_eq!(b.execute_geometry(&sdk, p)["success"], true);
assert!(b.close(|_| Err("release uncertain".into())).is_err());
assert!(b.ensure_released().is_err());
assert!(b.close(|_| panic!("must not retry release")).is_err());
}
#[test]
fn negative_arc_verifier_rejects_wrong_orientation_and_unrelated_equal_endpoints() {
let expected = Geometry::Arc {
center_cm: [0., 0., 0.],
start_cm: [1., 0., 0.],
end_cm: [0., -1., 0.],
sweep_radians: -std::f64::consts::FRAC_PI_2,
};
let correct = Geometry::Arc {
center_cm: [0., 0., 0.],
start_cm: [0., -1., 0.],
end_cm: [1., 0., 0.],
sweep_radians: std::f64::consts::FRAC_PI_2,
};
assert!(matches_geometry(&expected, &correct));
assert!(!matches_geometry(&expected, &expected));
let mut wrong = correct;
if let Geometry::Arc { sweep_radians, .. } = &mut wrong {
*sweep_radians = 3. * std::f64::consts::FRAC_PI_2;
}
assert!(!matches_geometry(&expected, &wrong));
}
#[test]
fn every_post_prefix_sdk_getter_panic_preserves_observations_and_unknown() {
for mode in [11, 12, 13, 14] {
let mut b = book();
let sdk = Fake::default();
let r = request(&b, &sdk, points(), "getter-panic");
let p = b.geometry_preflight(&sdk, &r).unwrap();
sdk.mode.set(mode);
let out = b.execute_geometry(&sdk, p);
assert_eq!(out["outcome"], "unknown", "mode {mode}: {out}");
assert!(!out["completedEntities"].as_array().unwrap().is_empty());
assert!(b.geometry_preflight(&sdk, &r).is_err());
}
}
#[test]
fn maximum_stored_points_keep_full_observation_within_reserved_result_budget() {
let mut b = book();
let sdk = Fake::default();
let items:Vec<Value>=(0..4).map(|s|json!({"type":"spline","points":(0..128).map(|i|vec![i as f64*1.123456789,s as f64*10.+(i%2) as f64]).collect::<Vec<_>>()})).collect();
let request = request(&b, &sdk, json!(items), "max-points");
let plan = b.geometry_preflight(&sdk, &request).unwrap();
assert_eq!(plan.prepared.stored_point_count(), 512);
let out = b.execute_geometry(&sdk, plan);
assert_eq!(out["success"], true);
assert!(serde_json::to_vec(&out).unwrap().len() < 128 * 1024);
assert_eq!(out["geometry"]["observed"].as_array().unwrap().len(), 4);
}