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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
use super::*;
use crate::assembly::{
self, Attempt, Backend, Change, Component, Occurrence, Open, Projection, Root,
};
use std::collections::BTreeMap;
#[repr(C)]
#[derive(Default)]
struct RawRoot {
counts: [u64; 13],
flags: [u32; 6],
}
#[repr(C)]
#[derive(Default)]
struct RawComponent {
counts: [u64; 10],
}
#[repr(C)]
#[derive(Default)]
struct RawOccurrence {
component: u64,
children: u64,
matrix: [f64; 16],
flags: [u32; 7],
}
#[repr(C)]
struct RawPositions {
count: u64,
timeline: u64,
timeline_position: u64,
pending: u32,
reserved: u32,
indexes: [i64; 64],
health: [u32; 64],
}
impl Default for RawPositions {
fn default() -> Self {
Self {
count: 0,
timeline: 0,
timeline_position: 0,
pending: 0,
reserved: 0,
indexes: [0; 64],
health: [0; 64],
}
}
}
#[repr(C)]
struct Diagnostic {
sdk_error: i32,
stage: u32,
length: u32,
truncated: u32,
text: [u8; 4096],
}
impl Default for Diagnostic {
fn default() -> Self {
Self {
sdk_error: -1,
stage: 0,
length: 0,
truncated: 0,
text: [0; 4096],
}
}
}
const _: () = assert!(
std::mem::size_of::<RawPositions>() == 800
&& std::mem::offset_of!(RawPositions, indexes) == 32
&& std::mem::offset_of!(RawPositions, health) == 544
&& std::mem::size_of::<RawRoot>() == 128
&& std::mem::size_of::<RawComponent>() == 80
&& std::mem::size_of::<RawOccurrence>() == 176
&& std::mem::offset_of!(RawOccurrence, matrix) == 16
&& std::mem::offset_of!(RawOccurrence, flags) == 144
&& std::mem::size_of::<Diagnostic>() == 4112
);
extern "C" {
fn af_assembly_positions(
c: *mut c_void,
d: *mut c_void,
v: *mut c_void,
out: *mut RawPositions,
diag: *mut Diagnostic,
) -> i32;
fn af_assembly_translate(
c: *mut c_void,
d: *mut c_void,
v: *mut c_void,
oi: u64,
matrix: *const f64,
invoked: *mut u32,
component: *mut u64,
diag: *mut Diagnostic,
) -> i32;
fn af_assembly_capture_position(
c: *mut c_void,
d: *mut c_void,
v: *mut c_void,
invoked: *mut u32,
slot: *mut u64,
diag: *mut Diagnostic,
) -> i32;
fn af_assembly_open(
c: *mut c_void,
d: *mut c_void,
out: *mut *mut c_void,
diag: *mut Diagnostic,
) -> i32;
fn af_assembly_close(c: *mut c_void, v: *mut c_void) -> i32;
fn af_assembly_root(
c: *mut c_void,
d: *mut c_void,
v: *mut c_void,
out: *mut RawRoot,
sink: Sink,
user: *mut c_void,
diag: *mut Diagnostic,
) -> i32;
fn af_assembly_component(
c: *mut c_void,
d: *mut c_void,
v: *mut c_void,
index: u64,
cloud: u32,
out: *mut RawComponent,
sink: Sink,
user: *mut c_void,
diag: *mut Diagnostic,
) -> i32;
fn af_assembly_attribute(
c: *mut c_void,
d: *mut c_void,
v: *mut c_void,
ci: u64,
ai: u64,
sink: Sink,
user: *mut c_void,
diag: *mut Diagnostic,
) -> i32;
fn af_assembly_occurrence(
c: *mut c_void,
d: *mut c_void,
v: *mut c_void,
index: u64,
out: *mut RawOccurrence,
sink: Sink,
user: *mut c_void,
diag: *mut Diagnostic,
) -> i32;
fn af_assembly_add(
c: *mut c_void,
d: *mut c_void,
v: *mut c_void,
existing: i64,
invoked: *mut u32,
ci: *mut u64,
oi: *mut u64,
diag: *mut Diagnostic,
) -> i32;
fn af_assembly_change(
c: *mut c_void,
d: *mut c_void,
v: *mut c_void,
ci: u64,
kind: u32,
key: *const c_char,
keylen: usize,
value: *const c_char,
valuelen: usize,
invoked: *mut u32,
diag: *mut Diagnostic,
) -> i32;
}
// No implicit SDK release. Book owns this exact view until explicit shutdown.
pub(super) struct Handle(NonNull<c_void>);
pub(super) struct Adapter<'a> {
pub context: &'a Context,
pub document: &'a DocumentReference,
}
impl Adapter<'_> {
fn checked(&self, status: i32, diag: Diagnostic) -> Result<(), String> {
if status == 0 {
return Ok(());
}
let n = (diag.length as usize).min(diag.text.len());
self.context.extrusion_diagnostic.replace(json!({"abiStatus":status,"stage":diag.stage,"sdkError":diag.sdk_error,"description":String::from_utf8_lossy(&diag.text[..n]),"truncated":diag.truncated!=0}));
if status == 8 {
return Err("native_assembly_native_metadata_requires_online".into());
}
Err(format!("native_assembly_sdk_status_{status}"))
}
fn root(&self, h: &Handle) -> Result<Root, String> {
let mut out = RawRoot::default();
let mut fields = Fields::default();
let mut diag = Diagnostic::default();
let status = unsafe {
af_assembly_root(
self.context.ptr.as_ptr(),
self.document.ptr.as_ptr(),
h.0.as_ptr(),
&mut out,
collect,
(&mut fields as *mut Fields).cast(),
&mut diag,
)
};
self.checked(status, diag)?;
let f = out.flags;
let n = out.counts;
Ok(Root {
revision: field(&mut fields, 0)?,
name: field(&mut fields, 1)?,
active: flag(f[0])?,
parametric: flag(f[1])?,
hybrid: flag(f[2])?,
configured: flag(f[3])?,
offline: flag(f[4])?,
pending_snapshot: flag(f[5])?,
components: n[0],
occurrences: n[1],
timeline: n[2],
timeline_position: n[3],
bodies: n[4],
sketches: n[5],
mesh_bodies: n[6],
joints: n[7],
as_built_joints: n[8],
rigid_groups: n[9],
assembly_constraints: n[10],
motion_links: n[11],
joint_origins: n[12],
})
}
}
impl Backend for Adapter<'_> {
type Handle = Handle;
fn open(&self) -> Open<Handle> {
let Ok(fence) = self.context.child_leases.begin() else {
return Open {
handle: None,
acknowledged: false,
};
};
let mut out = std::ptr::null_mut();
let mut diag = Diagnostic::default();
let status = unsafe {
af_assembly_open(
self.context.ptr.as_ptr(),
self.document.ptr.as_ptr(),
&mut out,
&mut diag,
)
};
let acknowledged = self.checked(status, diag).is_ok();
let result = Open {
handle: NonNull::new(out).map(Handle),
acknowledged,
};
// No fallible work between relinquishing the acquisition fence and the Book
// storing the already-built Open value. A panic above keeps teardown fenced.
if result.handle.is_some() || status != 0 {
fence.unopened();
}
result
}
fn command(&self) -> Result<String, String> {
self.context.active_command()
}
fn snapshot(&self, h: &Handle, cloud: bool) -> Result<Projection, String> {
let captured = self.capture(h, cloud)?;
if let Some(diagnostic) =
assembly::root_capture_diagnostic(&captured.projection.root, &captured.root_after)
{
self.context.extrusion_diagnostic.replace(diagnostic);
return Err("native_assembly_root_changed_during_capture".into());
}
Ok(captured.projection)
}
fn capture(&self, h: &Handle, cloud: bool) -> Result<assembly::Capture, String> {
self.context.close.ensure_open()?;
self.context.child_leases.ensure_clear()?;
let root = self.root(h)?;
if cloud && root.offline {
return Err("native_assembly_native_metadata_requires_online".into());
}
if root.components == 0
|| root.components > assembly::MAX_COMPONENTS as u64 + 1
|| root.occurrences > assembly::MAX_OCCURRENCES as u64
{
return Err("native_assembly_projection_bound".into());
}
let mut components = Vec::with_capacity(root.components as usize - 1);
for ci in 0..root.components - 1 {
let mut out = RawComponent::default();
let mut fields = Fields::default();
let mut diag = Diagnostic::default();
let status = unsafe {
af_assembly_component(
self.context.ptr.as_ptr(),
self.document.ptr.as_ptr(),
h.0.as_ptr(),
ci,
u32::from(cloud),
&mut out,
collect,
(&mut fields as *mut Fields).cast(),
&mut diag,
)
};
self.checked(status, diag)?;
let n = out.counts;
if n[4] > 128 {
return Err("native_assembly_metadata_bound".into());
}
let mut attributes = BTreeMap::new();
for ai in 0..n[4] {
let mut a = Fields::default();
let mut diag = Diagnostic::default();
let status = unsafe {
af_assembly_attribute(
self.context.ptr.as_ptr(),
self.document.ptr.as_ptr(),
h.0.as_ptr(),
ci,
ai,
collect,
(&mut a as *mut Fields).cast(),
&mut diag,
)
};
self.checked(status, diag)?;
let group = field(&mut a, 0)?;
let key = field(&mut a, 1)?;
let value = field(&mut a, 2)?;
if group == "AdomMechanical" && attributes.insert(key, value).is_some() {
return Err("native_assembly_duplicate_attribute".into());
}
}
components.push(Component {
name: field(&mut fields, 0)?,
revision: field(&mut fields, 1)?,
part_number: if cloud {
Some(field(&mut fields, 2)?)
} else {
None
},
description: if cloud {
Some(field(&mut fields, 3)?)
} else {
None
},
attributes,
bodies: n[0],
sketches: n[1],
occurrences: n[2],
mesh_bodies: n[3],
joints: n[5],
as_built_joints: n[6],
rigid_groups: n[7],
assembly_constraints: n[8],
motion_links: n[9],
});
}
let mut occurrences = Vec::with_capacity(root.occurrences as usize);
for i in 0..root.occurrences {
let mut out = RawOccurrence::default();
let mut fields = Fields::default();
let mut diag = Diagnostic::default();
let status = unsafe {
af_assembly_occurrence(
self.context.ptr.as_ptr(),
self.document.ptr.as_ptr(),
h.0.as_ptr(),
i,
&mut out,
collect,
(&mut fields as *mut Fields).cast(),
&mut diag,
)
};
self.checked(status, diag)?;
let f = out.flags;
occurrences.push(Occurrence {
component: usize::try_from(out.component)
.map_err(|_| "native_assembly_index_bound")?,
children: out.children,
matrix: out.matrix,
grounded: flag(f[0])?,
grounded_to_parent: flag(f[1])?,
referenced: flag(f[2])?,
configured: flag(f[3])?,
suppressed: flag(f[4])?,
rolled_back: flag(f[5])?,
native: flag(f[6])?,
name: field(&mut fields, 0)?,
path: field(&mut fields, 1)?,
});
}
let after = self.root(h)?;
Ok(assembly::Capture {
root_after: after,
projection: Projection {
root,
components,
occurrences,
cloud_metadata: cloud,
},
})
}
fn add(&self, h: &Handle, existing: Option<usize>) -> Attempt {
let mut invoked = 0;
let mut ci = u64::MAX;
let mut oi = u64::MAX;
let mut diag = Diagnostic::default();
let status = unsafe {
af_assembly_add(
self.context.ptr.as_ptr(),
self.document.ptr.as_ptr(),
h.0.as_ptr(),
existing.map_or(-1, |i| i as i64),
&mut invoked,
&mut ci,
&mut oi,
&mut diag,
)
};
let acknowledged = self.checked(status, diag).is_ok() && invoked == 1;
Attempt {
invoked: invoked != 0,
acknowledged,
component: (ci != u64::MAX).then_some(ci as usize),
occurrence: (oi != u64::MAX).then_some(oi as usize),
}
}
fn change(&self, h: &Handle, ci: usize, c: &Change) -> Attempt {
let mut invoked = 0;
let mut diag = Diagnostic::default();
let status = unsafe {
af_assembly_change(
self.context.ptr.as_ptr(),
self.document.ptr.as_ptr(),
h.0.as_ptr(),
ci as u64,
u32::from(c.native),
c.key.as_ptr().cast(),
c.key.len(),
c.value.as_ptr().cast(),
c.value.len(),
&mut invoked,
&mut diag,
)
};
let acknowledged = self.checked(status, diag).is_ok() && invoked == 1;
Attempt {
invoked: invoked != 0,
acknowledged,
component: Some(ci),
occurrence: None,
}
}
fn positions(&self, h: &Handle) -> Result<assembly::placement::PositionState, String> {
let mut out = RawPositions::default();
let mut diag = Diagnostic::default();
let status = unsafe {
af_assembly_positions(
self.context.ptr.as_ptr(),
self.document.ptr.as_ptr(),
h.0.as_ptr(),
&mut out,
&mut diag,
)
};
self.checked(status, diag)?;
if out.count > 64 || out.reserved != 0 {
return Err("native_placement_position_state_bound".into());
}
let pending = flag(out.pending)?;
let mut snapshots = Vec::with_capacity(out.count as usize);
for i in 0..out.count as usize {
snapshots.push(assembly::placement::SnapshotFact {
slot: i,
timeline_index: u64::try_from(out.indexes[i])
.map_err(|_| "native_placement_snapshot_index")?,
health: out.health[i],
});
}
Ok(assembly::placement::PositionState {
pending,
timeline: out.timeline,
timeline_position: out.timeline_position,
snapshots,
})
}
fn translate(&self, h: &Handle, oi: usize, matrix: &[f64; 16]) -> Attempt {
let mut invoked = 0;
let mut ci = u64::MAX;
let mut diag = Diagnostic::default();
let status = unsafe {
af_assembly_translate(
self.context.ptr.as_ptr(),
self.document.ptr.as_ptr(),
h.0.as_ptr(),
oi as u64,
matrix.as_ptr(),
&mut invoked,
&mut ci,
&mut diag,
)
};
let acknowledged = self.checked(status, diag).is_ok() && invoked == 1;
Attempt {
invoked: invoked != 0,
acknowledged,
component: (ci != u64::MAX).then_some(ci as usize),
occurrence: Some(oi),
}
}
fn capture_position(&self, h: &Handle) -> assembly::placement::SnapshotAttempt {
let mut invoked = 0;
let mut slot = u64::MAX;
let mut diag = Diagnostic::default();
let status = unsafe {
af_assembly_capture_position(
self.context.ptr.as_ptr(),
self.document.ptr.as_ptr(),
h.0.as_ptr(),
&mut invoked,
&mut slot,
&mut diag,
)
};
let acknowledged = self.checked(status, diag).is_ok() && invoked == 1;
assembly::placement::SnapshotAttempt {
attempt: Attempt {
invoked: invoked != 0,
acknowledged,
component: None,
occurrence: None,
},
snapshot_slot: (slot != u64::MAX).then_some(slot as usize),
}
}
fn record_observation_diagnostic(&self, value: Value) {
self.context.extrusion_diagnostic.replace(value);
}
fn diagnostic(&self) -> Value {
self.context.extrusion_diagnostic.borrow().clone()
}
}
pub(super) fn release(c: &Context, h: Handle) -> Result<(), String> {
unsafe { checked(af_assembly_close(c.ptr.as_ptr(), h.0.as_ptr())) }
}