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
Port occurrence translation and contract authoring to Rust; publish verified R21 source
a06dfd7
26d ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
//! Read-only finite assembly witness using independently acquired public SDK objects.
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::collections::BTreeMap;
pub type Result<T> = std::result::Result<T, String>;
#[cfg(all(
windows,
any(feature = "native-sdk", feature = "native-bindings-check")
))]
mod native;
#[cfg_attr(not(windows), allow(dead_code))]
mod storage;
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct Authority {
pub schema_version: u32,
pub purpose: String,
pub nonce: String,
pub pid: u32,
pub process_creation_time100ns: String,
pub document_creation_id: String,
pub expected_fixture: String,
pub allow_read_only_stability_observation: bool,
}
impl Authority {
pub fn validate(&self, pid: u32, creation: &str) -> Result<()> {
if self.schema_version != 1
|| self.purpose != "r21-native-placement-observation"
|| pid == 0
|| self.pid != pid
|| self.process_creation_time100ns != creation
|| creation.parse::<u64>().ok().filter(|v| *v > 0).is_none()
|| self.expected_fixture != "two-definitions-three-occurrences-two-translations-v1"
|| !self.allow_read_only_stability_observation
|| self.nonce.len() != 32
|| !self
.nonce
.bytes()
.all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))
|| self.document_creation_id.len() != 36
|| !self.document_creation_id.bytes().enumerate().all(|(i, b)| {
if [8, 13, 18, 23].contains(&i) {
b == b'-'
} else {
b.is_ascii_digit() || (b'a'..=b'f').contains(&b)
}
})
{
return Err("observer_authority_refused".into());
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Document {
pub name: String,
pub creation_id: String,
pub saved: bool,
pub modified: bool,
pub active: bool,
pub has_data_file: bool,
}
pub fn unique_document(rows: &[Document], creation: &str) -> Result<usize> {
if rows.is_empty() || rows.len() > 16 {
return Err("bounded_document_roster_required".into());
}
let m: Vec<_> = rows
.iter()
.enumerate()
.filter(|(_, r)| r.creation_id == creation)
.map(|(i, _)| i)
.collect();
if m.len() != 1 {
return Err("creation_correlation_ambiguous_or_missing".into());
}
Ok(m[0])
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
pub enum Kind {
Revisions,
Assembly,
}
pub const SEQUENCE: [Kind; 5] = [
Kind::Revisions,
Kind::Assembly,
Kind::Revisions,
Kind::Assembly,
Kind::Revisions,
];
#[repr(C)]
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct AssemblyFacts {
/// components,occurrences,timeline,marker,bodies,sketches,meshes,joints,as-built,
/// rigid,assembly-constraints,motion-links,joint-origins,features,graphics,all/user/model parameters.
pub root_counts: [u64; 18],
/// bodies,sketches,occurrences,meshes,attributes,joints,as-built,rigid,
/// assembly-constraints,motion-links,joint-origins,features,model-parameters.
pub component_counts: [[u64; 13]; 2],
/// parametric,hybrid,configured,active,pending-position-snapshot,offline.
pub flags: [u32; 6],
/// grounded,grounded-to-parent,external,configured,suppressed,rolled-back,native-context.
pub occurrence_flags: [[u32; 7]; 3],
pub children: [u64; 3],
pub component_slots: [u32; 3],
pub same_component: [u32; 9],
/// Explicit getCell(row,column) order, no flattened-storage convention assumption.
pub matrices: [[f64; 16]; 3],
pub snapshot_count: u64,
pub snapshot_indexes: [i64; 2],
pub snapshot_health: [u32; 2],
/// Suppressed, rolled back. Parent timeline and exact row identity are checked in ABI.
pub snapshot_flags: [[u32; 2]; 2],
}
impl AssemblyFacts {
pub fn numbers(&self) -> Vec<(String, f64)> {
self.matrices
.iter()
.enumerate()
.flat_map(|(i, a)| {
a.iter()
.enumerate()
.map(move |(j, v)| (format!("matrix/{i}/{j}"), *v))
})
.collect()
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Attribute {
pub group: String,
pub name: String,
pub value: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Capture {
pub raw: AssemblyFacts,
pub texts: Vec<(u32, String)>,
pub attributes: [Vec<Attribute>; 2],
}
fn text_ok(s: &str) -> bool {
s.len() <= 4096 && !s.contains('\0')
}
pub fn assess(c: &Capture) -> Value {
let f = &c.raw;
let counts = f.root_counts[0] == 3
&& f.root_counts[1] == 3
&& f.root_counts[2] == 5
&& f.root_counts[2] == f.root_counts[3]
&& f.root_counts[4..].iter().all(|n| *n == 0)
&& f.component_counts.iter().all(|a| {
a.iter()
.enumerate()
.all(|(i, n)| if i == 4 { *n <= 128 } else { *n == 0 })
});
let flags = f.flags[..5] == [1, 1, 0, 1, 0]
&& f.flags[5] <= 1
&& f.occurrence_flags
.iter()
.all(|a| a[..] == [0, 0, 0, 0, 0, 0, 1]);
let identities = f.component_slots == [0, 1, 0]
&& f.same_component == [1, 0, 1, 0, 1, 0, 1, 0, 1]
&& f.children == [0; 3];
let matrices = f.matrices.iter().enumerate().all(|(i, a)| {
a.iter().enumerate().all(|(j, x)| {
let wanted = match (i, j) {
(2, 3) => -0.5,
(2, 7) => 2.,
(2, 11) => 1.,
_ => {
if j % 5 == 0 {
1.
} else {
0.
}
}
};
x.is_finite() && (*x - wanted).abs() <= 1e-10
})
});
let snapshots = f.snapshot_count == 2
&& f.snapshot_indexes == [3, 4]
&& f.snapshot_health == [0, 0]
&& f.snapshot_flags == [[0, 0], [0, 0]];
let texts = c.texts.len() == 9
&& c.texts
.iter()
.enumerate()
.all(|(i, (field, s))| *field == i as u32 && !s.is_empty() && text_ok(s))
&& c.texts
.get(1)
.is_some_and(|(_, s)| s == "Assembly fixture A revised")
&& c.texts
.get(2)
.is_some_and(|(_, s)| s == "Assembly fixture B");
let expected = [
json!({"supplier":"Vendor \"A\" \\ depot","supplierPartNumber":"=TEST,43","sourceUrl":"https://example.invalid/A","asPurchased":false,"excludeFromBom":false}),
json!({"supplier":"Other vendor","supplierPartNumber":"=TEST,42","sourceUrl":"https://example.invalid/B-revised","asPurchased":true,"excludeFromBom":true}),
];
let metadata = c.attributes.iter().enumerate().all(|(i, attrs)| {
if attrs.len() > 128 || attrs.len() as u64 != f.component_counts[i][4] {
return false;
}
let mut all = std::collections::BTreeSet::new();
let mut observed = serde_json::Map::new();
for a in attrs {
if !text_ok(&a.group)
|| !text_ok(&a.name)
|| !text_ok(&a.value)
|| a.group.is_empty()
|| a.name.is_empty()
|| !all.insert((&a.group, &a.name))
{
return false;
}
if a.group == "AdomMechanical" {
let Ok(value) = serde_json::from_str::<Value>(&a.value) else {
return false;
};
observed.insert(a.name.clone(), value);
}
}
Value::Object(observed) == expected[i]
});
json!({"qualified":counts&&flags&&identities&&matrices&&snapshots&&texts&&metadata,"emptyFiniteGraph":counts,"localNativeState":flags,"sharedDefinitionIdentityAndQuantities":identities,"exactFinalOccurrencePoses":matrices,"twoHealthyCapturedPositions":snapshots,"namesAndBoundedTexts":texts,"localMetadata":metadata,"definitionQuantities":[2,1],"selectedOccurrence":2,"selectedPositionMm":[-5,20,10],"onlineNativeMetadataQualified":false,"bomTraversalQualified":false,"physicalAssemblyQualified":false,"historicalIntermediatePoseQualified":false,"archivePersistenceQualified":false})
}
#[derive(Clone, Debug, Serialize)]
pub struct Observation {
pub kind: Kind,
pub abi_status: i32,
pub data: Value,
pub float_bits: BTreeMap<String, String>,
pub non_finite: bool,
pub diagnostic: Value,
pub elapsed_micros: u128,
}
impl Observation {
pub fn succeeded(&self) -> bool {
self.abi_status == 0 && !self.non_finite
}
}
pub fn float_bits(
values: impl IntoIterator<Item = (String, f64)>,
) -> (BTreeMap<String, String>, bool) {
let mut bad = false;
let v = values
.into_iter()
.map(|(k, v)| {
bad |= !v.is_finite();
(k, format!("{:016x}", v.to_bits()))
})
.collect();
(v, bad)
}
pub trait Backend {
fn observe(&mut self, kind: Kind) -> Observation;
}
pub fn collect(b: &mut impl Backend) -> Vec<Observation> {
let mut out = vec![];
for kind in SEQUENCE {
let s = b.observe(kind);
let ok = s.kind == kind && s.succeeded();
out.push(s);
if !ok {
break;
}
}
out
}
pub fn comparisons(v: &[Observation]) -> Value {
let cs: Vec<_> = v.iter().filter(|s| s.kind == Kind::Assembly).collect();
let rs: Vec<_> = v.iter().filter(|s| s.kind == Kind::Revisions).collect();
let assessments: Vec<_> = cs
.iter()
.map(|s| {
serde_json::from_value::<Capture>(s.data.clone())
.map(|c| assess(&c))
.unwrap_or_else(|_| json!({"qualified":false,"error":"capture_shape"}))
})
.collect();
let revision_shape = rs.iter().all(|s| {
s.data
.get("values")
.and_then(Value::as_array)
.is_some_and(|a| {
a.len() == 3
&& a.iter().enumerate().all(|(i, v)| {
v.as_array().is_some_and(|row| {
row.len() == 2
&& row[0].as_u64() == Some(i as u64)
&& row[1].as_str().is_some_and(|s| !s.is_empty() && text_ok(s))
})
})
})
});
let stable = cs.len() == 2
&& cs
.iter()
.all(|s| s.succeeded() && s.data == cs[0].data && s.float_bits == cs[0].float_bits)
&& rs.len() == 3
&& rs.iter().all(|s| s.succeeded() && s.data == rs[0].data)
&& revision_shape;
let sequence = v.len() == SEQUENCE.len() && v.iter().zip(SEQUENCE).all(|(a, b)| a.kind == b);
json!({"twoAssemblySamplesEqualAndRevisionsStable":stable,"sequenceComplete":sequence,"assessments":assessments,"finitePlacementTruthQualified":stable&&sequence&&assessments.len()==2&&assessments.iter().all(|a|a["qualified"]==true),"operationJournalReconciled":false,"fullBridgeParity":false})
}
pub fn qualified(v: &[Observation]) -> bool {
comparisons(v)["finitePlacementTruthQualified"] == true
}
#[cfg(test)]
mod tests;