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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
use super::*;
use crate::bom::{self, Backend as BomBackend};
use fusion_bom_core::{Material, PhysicalProperties};
use std::collections::BTreeMap;
#[repr(C)]
#[derive(Default)]
struct RawDesign {
active: u32,
hybrid: u32,
configured: u32,
configuration: u32,
table_present: u32,
components: u64,
timeline_count: u64,
timeline_position: u64,
}
#[repr(C)]
#[derive(Default)]
struct RawInstance {
referenced: u32,
configured: u32,
timeline_present: u32,
suppressed: u32,
rolled_back: u32,
visible: u32,
}
#[repr(C)]
#[derive(Default)]
struct RawPhysical {
mass_kg: f64,
volume_cm3: f64,
high_accuracy: u32,
}
extern "C" {
fn af_bom_open(c: *mut c_void, d: *mut c_void, out: *mut *mut c_void) -> i32;
fn af_bom_close(v: *mut c_void) -> i32;
fn af_bom_is_offline(v: *mut c_void, out: *mut u32) -> i32;
fn af_bom_design(v: *mut c_void, out: *mut RawDesign, sink: Sink, user: *mut c_void) -> i32;
fn af_bom_error_stage(
v: *mut c_void,
sdk_error: *mut i32,
definition_id: *mut u32,
sink: Sink,
user: *mut c_void,
) -> i32;
fn af_bom_root(v: *mut c_void, out: *mut u32) -> i32;
fn af_bom_same(v: *mut c_void, a: u32, b: u32, out: *mut u32) -> i32;
fn af_bom_component(v: *mut c_void, id: u32, out: *mut u32) -> i32;
fn af_bom_definition(
v: *mut c_void,
id: u32,
meshes: *mut u64,
sink: Sink,
user: *mut c_void,
) -> i32;
fn af_bom_material(
v: *mut c_void,
id: u32,
present: *mut u32,
sink: Sink,
user: *mut c_void,
) -> i32;
fn af_bom_attribute_count(v: *mut c_void, id: u32, out: *mut u64) -> i32;
fn af_bom_attribute(v: *mut c_void, id: u32, index: u64, sink: Sink, user: *mut c_void) -> i32;
fn af_bom_instance(
v: *mut c_void,
id: u32,
out: *mut RawInstance,
sink: Sink,
user: *mut c_void,
) -> i32;
fn af_bom_child_count(v: *mut c_void, id: u32, out: *mut u64) -> i32;
fn af_bom_child(v: *mut c_void, id: u32, index: u64, out: *mut u32) -> i32;
fn af_bom_body_count(v: *mut c_void, id: u32, out: *mut u64) -> i32;
fn af_bom_body(v: *mut c_void, id: u32, index: u64, out: *mut RawPhysical) -> i32;
fn af_bom_subtree(v: *mut c_void, id: u32, out: *mut RawPhysical) -> i32;
}
fn number(value: u64) -> bom::Result<usize> {
usize::try_from(value).map_err(|_| "native_bom_count_overflow".into())
}
// Lossy decoding is confined to optional diagnostic text. Ordinary SDK data
// continues to use the strict shared collector.
unsafe extern "C" fn collect_diagnostic(
user: *mut c_void,
field: u32,
data: *const std::ffi::c_char,
len: usize,
) -> i32 {
if user.is_null() || (data.is_null() && len != 0) || len > 1024 || field > 1 {
return 0;
}
let fields = &mut *user.cast::<bom::DiagnosticFields>();
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let bytes = if len == 0 {
&[][..]
} else {
std::slice::from_raw_parts(data.cast::<u8>(), len)
};
fields.store(field, bytes).is_ok()
}))
.unwrap_or(false);
i32::from(result)
}
struct View(NonNull<c_void>);
impl View {
fn checked(&self, call: &str, code: i32) -> bom::Result<()> {
if code != 0 {
let mut diagnostic = bom::DiagnosticFields::default();
let mut sdk_error = 0;
let mut definition_id = u32::MAX;
// Out-parameters/fields are written only after the ABI owner check.
// Preserve each copied field even if optional description fails.
let _observed = unsafe {
af_bom_error_stage(
self.0.as_ptr(),
&mut sdk_error,
&mut definition_id,
collect_diagnostic,
(&mut diagnostic as *mut bom::DiagnosticFields).cast(),
)
};
let stage = diagnostic.stage;
let description = diagnostic.description;
let detail = if sdk_error != 0 {
Some((sdk_error, definition_id, description.as_deref()))
} else {
None
};
return Err(bom::sdk_diagnostic(call, code, stage.as_deref(), detail));
}
Ok(())
}
}
impl BomBackend for View {
type Handle = u32;
fn is_offline(&self) -> bom::Result<bool> {
let mut offline = 0;
unsafe {
bom::sdk_status(
"connectivity",
af_bom_is_offline(self.0.as_ptr(), &mut offline),
)?;
}
checked_bool(offline)
}
fn design(&self) -> bom::Result<bom::Design> {
let mut raw = RawDesign::default();
let mut fields = Fields::default();
let code = unsafe {
af_bom_design(
self.0.as_ptr(),
&mut raw,
collect,
(&mut fields as *mut Fields).cast(),
)
};
self.checked("design", code)?;
Ok(bom::Design {
active: checked_bool(raw.active)?,
hybrid: checked_bool(raw.hybrid)?,
configured: checked_bool(raw.configured)?,
configuration: checked_bool(raw.configuration)?,
table_present: checked_bool(raw.table_present)?,
row_id: field(&mut fields, 0)?,
components: number(raw.components)?,
timeline_count: number(raw.timeline_count)?,
timeline_position: number(raw.timeline_position)?,
})
}
fn root(&self) -> bom::Result<u32> {
let mut out = 0;
unsafe {
bom::sdk_status("root", af_bom_root(self.0.as_ptr(), &mut out))?;
}
Ok(out)
}
fn same(&self, a: u32, b: u32) -> bom::Result<bool> {
let mut out = 0;
unsafe {
bom::sdk_status("same", af_bom_same(self.0.as_ptr(), a, b, &mut out))?;
}
checked_bool(out)
}
fn component(&self, id: u32) -> bom::Result<u32> {
let mut out = 0;
unsafe {
bom::sdk_status("component", af_bom_component(self.0.as_ptr(), id, &mut out))?;
}
Ok(out)
}
fn definition(&self, id: u32) -> bom::Result<bom::Definition> {
let mut fields = Fields::default();
let mut meshes = 0;
unsafe {
self.checked(
"definition",
af_bom_definition(
self.0.as_ptr(),
id,
&mut meshes,
collect,
(&mut fields as *mut Fields).cast(),
),
)?;
}
let name = field(&mut fields, 0)?;
let part_number = field(&mut fields, 1)?;
let description = field(&mut fields, 2)?;
let revision = field(&mut fields, 3)?;
let token = field(&mut fields, 4)?;
let mut count = 0;
unsafe {
bom::sdk_status(
"attribute_count",
af_bom_attribute_count(self.0.as_ptr(), id, &mut count),
)?;
}
if count > 128 {
return Err("native_bom_metadata_bound".into());
}
let mut attributes = BTreeMap::new();
let mut attribute_bytes = 0usize;
for index in 0..count {
let mut fields = Fields::default();
unsafe {
bom::sdk_status(
"attribute",
af_bom_attribute(
self.0.as_ptr(),
id,
index,
collect,
(&mut fields as *mut Fields).cast(),
),
)?;
}
let group = field(&mut fields, 0)?;
let key = field(&mut fields, 1)?;
let value = field(&mut fields, 2)?;
if [group.len(), key.len(), value.len()]
.iter()
.any(|length| *length > 16 * 1024)
{
return Err("native_bom_text_bound".into());
}
attribute_bytes = attribute_bytes
.checked_add(group.len() + key.len() + value.len())
.ok_or("native_bom_byte_bound")?;
if attribute_bytes > bom::MAX_BYTES / 2 {
return Err("native_bom_byte_bound".into());
}
if group == "AdomMechanical" && attributes.insert(key, value).is_some() {
return Err("native_bom_duplicate_attribute".into());
}
}
let mut after = 0;
unsafe {
bom::sdk_status(
"attribute_count",
af_bom_attribute_count(self.0.as_ptr(), id, &mut after),
)?;
}
if after != count {
return Err("native_bom_changed_attributes".into());
}
let mut fields = Fields::default();
let mut present = 0;
unsafe {
bom::sdk_status(
"material",
af_bom_material(
self.0.as_ptr(),
id,
&mut present,
collect,
(&mut fields as *mut Fields).cast(),
),
)?;
}
let material = if checked_bool(present)? {
Some(Material {
id: field(&mut fields, 0)?,
name: field(&mut fields, 1)?,
})
} else {
None
};
Ok(bom::Definition {
name,
part_number,
description,
revision,
token,
attributes,
material,
mesh_bodies: number(meshes)?,
})
}
fn instance(&self, id: u32) -> bom::Result<bom::Instance> {
let mut raw = RawInstance::default();
let mut fields = Fields::default();
unsafe {
bom::sdk_status(
"instance",
af_bom_instance(
self.0.as_ptr(),
id,
&mut raw,
collect,
(&mut fields as *mut Fields).cast(),
),
)?;
}
Ok(bom::Instance {
path: field(&mut fields, 0)?,
token: field(&mut fields, 1)?,
referenced: checked_bool(raw.referenced)?,
configured: checked_bool(raw.configured)?,
timeline_present: checked_bool(raw.timeline_present)?,
suppressed: checked_bool(raw.suppressed)?,
rolled_back: checked_bool(raw.rolled_back)?,
visible: checked_bool(raw.visible)?,
})
}
fn child_count(&self, id: u32) -> bom::Result<usize> {
let mut out = 0;
unsafe {
bom::sdk_status(
"child_count",
af_bom_child_count(self.0.as_ptr(), id, &mut out),
)?;
}
number(out)
}
fn child(&self, id: u32, index: usize) -> bom::Result<u32> {
let mut out = 0;
unsafe {
bom::sdk_status(
"child",
af_bom_child(self.0.as_ptr(), id, index as u64, &mut out),
)?;
}
Ok(out)
}
fn body_count(&self, id: u32) -> bom::Result<usize> {
let mut out = 0;
unsafe {
bom::sdk_status(
"body_count",
af_bom_body_count(self.0.as_ptr(), id, &mut out),
)?;
}
number(out)
}
fn body(&self, id: u32, index: usize) -> bom::Result<PhysicalProperties> {
let mut out = RawPhysical::default();
unsafe {
bom::sdk_status(
"body",
af_bom_body(self.0.as_ptr(), id, index as u64, &mut out),
)?;
}
properties(out)
}
fn subtree_properties(&self, id: u32) -> bom::Result<PhysicalProperties> {
let mut out = RawPhysical::default();
unsafe {
bom::sdk_status("subtree", af_bom_subtree(self.0.as_ptr(), id, &mut out))?;
}
properties(out)
}
}
fn properties(value: RawPhysical) -> bom::Result<PhysicalProperties> {
if value.high_accuracy != 1 {
return Err("native_bom_accuracy_unconfirmed".into());
}
PhysicalProperties::from_fusion_units(value.mass_kg, value.volume_cm3)
.map_err(|_| "native_bom_invalid_physical".into())
}
pub(super) fn generate(
context: &Context,
document: &DocumentReference,
session: &str,
id: &str,
revision: Option<&str>,
explode: bool,
) -> bom::Result<bom::Captured> {
let lease = context.child_leases.begin()?;
let mut raw = std::ptr::null_mut();
let opened = unsafe { af_bom_open(context.ptr.as_ptr(), document.ptr.as_ptr(), &mut raw) };
let Some(raw) = NonNull::new(raw) else {
if opened == 0 {
panic!("native BOM open acknowledged without lease; parent teardown fenced");
}
// The C++ opener leaves out=null on every preacquisition failure.
lease.unopened();
return Err("native_bom_open_failed".into());
};
let result = lease.run(
View(raw),
|view| {
bom::sdk_status("open", opened)?;
bom::capture(view, session, id, revision, explode)
},
|view| unsafe { bom::sdk_status("close", af_bom_close(view.0.as_ptr())) },
);
match result {
Ok(value) => value,
Err(_) => panic!("native BOM lease release unconfirmed; parent teardown fenced"),
}
}