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
26d ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
use crate::{Error, Result};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct Target {
pub document_id: String,
pub revision: String,
}
/// Native entity tokens are opaque references, never definition identities.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct EntityRef {
pub document_id: String,
pub revision: String,
pub token: String,
pub object_type: String,
pub occurrence_token: Option<String>,
pub occurrence_path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub root_component_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct Material {
pub id: String,
pub name: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct Metadata {
pub name: String,
pub part_number: String,
pub description: String,
pub attributes: BTreeMap<String, String>,
pub material: Option<Material>,
#[serde(skip_serializing_if = "Option::is_none")]
pub supplier: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub supplier_part_number: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source_file: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sha256: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub as_purchased: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub exclude_from_bom: Option<bool>,
}
impl Metadata {
/// `attributes` contains only the native AdomMechanical group. Recognized
/// attributes are JSON-encoded strings/booleans, as in the legacy add-in.
pub fn from_attributes(
name: String,
part_number: String,
description: String,
attributes: BTreeMap<String, String>,
material: Option<Material>,
) -> Result<Self> {
fn read<T: serde::de::DeserializeOwned>(
attrs: &BTreeMap<String, String>,
key: &str,
) -> Result<Option<T>> {
attrs
.get(key)
.map(|raw| {
serde_json::from_str(raw).map_err(|error| {
Error::new(
"invalid_metadata",
format!("Attribute {key} has an invalid type or JSON: {error}"),
)
})
})
.transpose()
}
Ok(Self {
name,
part_number,
description,
material,
supplier: read(&attributes, "supplier")?,
supplier_part_number: read(&attributes, "supplierPartNumber")?,
source_url: read(&attributes, "sourceUrl")?,
source_file: read(&attributes, "sourceFile")?,
sha256: read(&attributes, "sha256")?,
as_purchased: read(&attributes, "asPurchased")?,
exclude_from_bom: read(&attributes, "excludeFromBom")?,
attributes,
})
}
pub(crate) fn validate(&self) -> Result<()> {
let decoded = Self::from_attributes(
self.name.clone(),
self.part_number.clone(),
self.description.clone(),
self.attributes.clone(),
self.material.clone(),
)?;
if &decoded != self {
return Err(Error::new(
"invalid_metadata",
"Decoded metadata disagrees with native attribute values",
));
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PhysicalProperties {
pub mass_kg: f64,
pub volume_mm3: f64,
}
impl PhysicalProperties {
pub fn from_fusion_units(mass_kg: f64, volume_cm3: f64) -> Result<Self> {
let value = Self {
mass_kg,
volume_mm3: volume_cm3 * 1000.0,
};
value.validate()?;
Ok(value)
}
pub(crate) fn validate(self) -> Result<()> {
if !self.mass_kg.is_finite()
|| !self.volume_mm3.is_finite()
|| self.mass_kg < 0.0
|| self.volume_mm3 < 0.0
{
return Err(Error::new(
"invalid_physical_properties",
"Mass and volume must be finite and nonnegative",
));
}
Ok(())
}
pub(crate) fn add(&mut self, other: Self) -> Result<()> {
self.mass_kg += other.mass_kg;
self.volume_mm3 += other.volume_mm3;
self.validate()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct Component {
/// Session-local SDK equality identity, never a name, token or part number.
pub id: String,
/// Alternate wrapper identities already proven equal by the native adapter.
pub aliases: Vec<String>,
pub metadata: Metadata,
pub reference: EntityRef,
pub direct_bodies: Vec<PhysicalProperties>,
/// Native component.getPhysicalProperties(High), required for a collapsed
/// purchased row. This is not inferred from purchasing child rows.
pub subtree_properties: Option<PhysicalProperties>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct SourceLink {
pub entity_id: String,
pub object_type: String,
pub owner_id: String,
pub owner_scope: OwnerScope,
pub occurrence_id: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum OwnerScope {
Component,
Design,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct Occurrence {
pub id: String,
pub path: String,
pub component_id: String,
pub reference: EntityRef,
pub source: SourceLink,
/// None means the adapter successfully observed there is no timeline object.
/// A failed suppression read must instead make the witness incomplete.
pub timeline_suppressed: Option<bool>,
/// Visibility has no effect on purchasing or physical inclusion.
pub visible: bool,
/// Context-specific occurrence.bRepBodies physical observations.
pub direct_bodies: Vec<PhysicalProperties>,
pub children: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct Root {
pub component_id: String,
pub children: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum CalculationAccuracy {
Low,
Medium,
High,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct SnapshotWitness {
pub session_id: String,
/// SDK identity of the Design object, distinct from document identity.
pub design_id: String,
pub target: Target,
pub complete: bool,
pub read_errors: Vec<String>,
pub calculation_accuracy: CalculationAccuracy,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct AssemblySnapshot {
pub target: Target,
pub witness: SnapshotWitness,
pub configuration: crate::configuration::ConfigurationWitness,
pub root: Root,
pub components: Vec<Component>,
pub occurrences: Vec<Occurrence>,
}