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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
//! Pure bounded numeric preparation. No native sketch, identity, solver,
//! transaction, model-unchanged proof or SDK effect is produced by this crate.
mod model;
pub use model::*;
use serde::Serialize;
use std::{
fmt,
io::{self, Write},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Error {
Numeric,
Degenerate,
NonCoplanar,
Count,
Budget,
Serialization,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Numeric => "nonfinite, out-of-bound or unrepresentable geometry",
Self::Degenerate => "degenerate sketch geometry",
Self::NonCoplanar => "unsupported sketch-space Z coordinates",
Self::Count => "invalid item, side or fit-point count",
Self::Budget => "sketch preparation budget exceeded",
Self::Serialization => "sketch plan serialization failed",
})
}
}
impl std::error::Error for Error {}
pub type Result<T> = std::result::Result<T, Error>;
fn require(ok: bool, error: Error) -> Result<()> {
if ok {
Ok(())
} else {
Err(error)
}
}
/// Finite mm to cm conversion with an explicit bound and underflow refusal.
pub fn millimeters_to_centimeters(mm: f64) -> Result<f64> {
require(mm.is_finite() && mm.abs() <= MAX_ABS_MM, Error::Numeric)?;
let cm = mm / 10.;
require(cm.is_finite() && (mm == 0. || cm != 0.), Error::Numeric)?;
Ok(cm)
}
pub fn point_cm(point: PointMm) -> Result<[f64; 3]> {
let xyz = match point {
PointMm::Xy([x, y]) => [x, y, 0.],
PointMm::Xyz(xyz) => xyz,
};
Ok([
millimeters_to_centimeters(xyz[0])?,
millimeters_to_centimeters(xyz[1])?,
millimeters_to_centimeters(xyz[2])?,
])
}
fn point_valid(p: [f64; 3]) -> Result<[f64; 3]> {
require(
p.iter().all(|v| v.is_finite() && v.abs() <= MAX_ABS_CM),
Error::Numeric,
)?;
Ok(p)
}
fn distance(a: [f64; 3], b: [f64; 3]) -> f64 {
(a[0] - b[0]).hypot(a[1] - b[1]).hypot(a[2] - b[2])
}
fn span(a: [f64; 3], b: [f64; 3]) -> Result<()> {
require(distance(a, b) > MIN_SPAN_CM, Error::Degenerate)
}
fn radius(mm: f64) -> Result<f64> {
let cm = millimeters_to_centimeters(mm)?;
require(cm > MIN_SPAN_CM, Error::Degenerate)?;
Ok(cm)
}
fn same_z(a: [f64; 3], b: [f64; 3]) -> Result<()> {
require(a[2] == b[2], Error::NonCoplanar)
}
// Conservative full-circle envelope also bounds arcs, even when the requested
// subarc would occupy a smaller region. This avoids hidden out-of-bound geometry.
fn circle_bounds(center: [f64; 3], radius: f64) -> Result<()> {
for axis in [0, 1] {
for sign in [-1., 1.] {
let mut edge = center;
edge[axis] += sign * radius;
point_valid(edge)?;
span(center, edge)?;
}
}
Ok(())
}
fn line(a: [f64; 3], b: [f64; 3]) -> Result<Geometry> {
point_valid(a)?;
point_valid(b)?;
span(a, b)?;
Ok(Geometry::Line {
start_cm: a,
end_cm: b,
})
}
fn arc(center: [f64; 3], start: [f64; 3], sweep: f64) -> Result<Geometry> {
same_z(center, start)?;
span(center, start)?;
let dx = start[0] - center[0];
let dy = start[1] - center[1];
circle_bounds(center, dx.hypot(dy))?;
let (sin, cos) = sweep.sin_cos();
let end = point_valid([
center[0] + dx * cos - dy * sin,
center[1] + dx * sin + dy * cos,
center[2],
])?;
span(start, end)?;
Ok(Geometry::Arc {
center_cm: center,
start_cm: start,
end_cm: end,
sweep_radians: sweep,
})
}
fn cost(item: &Primitive) -> Result<(usize, usize)> {
match item {
Primitive::Point { .. } => Ok((1, 1)),
Primitive::Line { .. } => Ok((1, 2)),
Primitive::Rectangle { .. } => Ok((4, 8)),
Primitive::Circle { .. } => Ok((1, 1)),
Primitive::Arc { .. } => Ok((1, 3)),
Primitive::Polygon { sides, .. } => {
require((3..=MAX_SIDES).contains(sides), Error::Count)?;
Ok((*sides, sides * 2))
}
Primitive::Slot { .. } => Ok((4, 10)),
Primitive::Spline { points, closed, .. } => {
require(
((if *closed { 3 } else { 2 })..=MAX_FIT_POINTS).contains(&points.len()),
Error::Count,
)?;
Ok((1, points.len()))
}
}
}
fn kind(item: &Primitive) -> PrimitiveKind {
match item {
Primitive::Point { .. } => PrimitiveKind::Point,
Primitive::Line { .. } => PrimitiveKind::Line,
Primitive::Rectangle { .. } => PrimitiveKind::Rectangle,
Primitive::Circle { .. } => PrimitiveKind::Circle,
Primitive::Arc { .. } => PrimitiveKind::Arc,
Primitive::Polygon { .. } => PrimitiveKind::Polygon,
Primitive::Slot { .. } => PrimitiveKind::Slot,
Primitive::Spline { .. } => PrimitiveKind::Spline,
}
}
fn compile(item: &Primitive) -> Result<Vec<Entity>> {
let (geometries, construction) = match item {
Primitive::Point { position } => (
vec![Geometry::Point {
position_cm: point_cm(*position)?,
}],
None,
),
Primitive::Line {
start,
end,
construction,
} => (
vec![line(point_cm(*start)?, point_cm(*end)?)?],
*construction,
),
Primitive::Rectangle {
start,
end,
construction,
} => {
let a = point_cm(*start)?;
let c = point_cm(*end)?;
same_z(a, c)?;
let b = [c[0], a[1], a[2]];
let d = [a[0], c[1], a[2]];
(
vec![line(a, b)?, line(b, c)?, line(c, d)?, line(d, a)?],
*construction,
)
}
Primitive::Circle {
center,
radius: mm,
construction,
} => {
let center = point_cm(*center)?;
let radius = radius(*mm)?;
circle_bounds(center, radius)?;
(
vec![Geometry::Circle {
center_cm: center,
radius_cm: radius,
}],
*construction,
)
}
Primitive::Arc {
center,
start,
sweep,
construction,
} => {
require(
sweep.is_finite() && sweep.abs() > 0. && sweep.abs() < 360.,
Error::Numeric,
)?;
let radians = sweep.to_radians();
require(
radians.is_finite() && radians != 0. && radians.abs() < std::f64::consts::TAU,
Error::Numeric,
)?;
(
vec![arc(point_cm(*center)?, point_cm(*start)?, radians)?],
*construction,
)
}
Primitive::Polygon {
center,
radius: mm,
sides,
angle,
construction,
} => {
let center = point_cm(*center)?;
let radius = radius(*mm)?;
circle_bounds(center, radius)?;
require(
angle.is_finite() && angle.abs() <= MAX_ANGLE_DEGREES,
Error::Numeric,
)?;
// Reduce in degrees before trig. Bounded angles avoid arbitrary
// argument-reduction loss while preserving orientation modulo turns.
let angle = angle.rem_euclid(360.).to_radians();
let vertices: Result<Vec<_>> = (0..*sides)
.map(|i| {
let theta = angle + std::f64::consts::TAU * (i as f64) / (*sides as f64);
let (sin, cos) = theta.sin_cos();
point_valid([
center[0] + radius * cos,
center[1] + radius * sin,
center[2],
])
})
.collect();
let vertices = vertices?;
let edges: Result<Vec<_>> = (0..*sides)
.map(|i| line(vertices[i], vertices[(i + 1) % sides]))
.collect();
(edges?, *construction)
}
Primitive::Slot {
start,
end,
width,
construction,
} => {
let start = point_cm(*start)?;
let end = point_cm(*end)?;
require((start[2] - end[2]).abs() < 1e-9, Error::NonCoplanar)?;
let radius = radius(*width)? / 2.;
require(radius > MIN_SPAN_CM, Error::Degenerate)?;
let dx = end[0] - start[0];
let dy = end[1] - start[1];
let length = dx.hypot(dy);
require(length > MIN_SPAN_CM, Error::Degenerate)?;
// Normalize first, avoiding an unnecessary length*radius product.
let ox = -dy / length * radius;
let oy = dx / length * radius;
let offset =
|c: [f64; 3], sign: f64| point_valid([c[0] + sign * ox, c[1] + sign * oy, c[2]]);
let a = offset(start, 1.)?;
let b = offset(end, 1.)?;
let c = offset(end, -1.)?;
let d = offset(start, -1.)?;
// Explicit opposite endpoints preserve exact chain connectivity;
// computing sin(-PI) would introduce a spurious tiny endpoint gap.
circle_bounds(start, radius)?;
circle_bounds(end, radius)?;
span(a, d)?;
span(b, c)?;
(
vec![
line(a, b)?,
Geometry::Arc {
center_cm: end,
start_cm: b,
end_cm: c,
sweep_radians: -std::f64::consts::PI,
},
line(c, d)?,
Geometry::Arc {
center_cm: start,
start_cm: d,
end_cm: a,
sweep_radians: -std::f64::consts::PI,
},
],
*construction,
)
}
Primitive::Spline {
points,
closed,
construction,
} => {
let points: Result<Vec<_>> = points.iter().copied().map(point_cm).collect();
let points = points?;
for pair in points.windows(2) {
span(pair[0], pair[1])?;
}
if *closed {
span(points[0], points[points.len() - 1])?;
}
(
vec![Geometry::FittedSpline {
fit_points_cm: points,
closed: *closed,
}],
*construction,
)
}
};
Ok(geometries
.into_iter()
.map(|geometry| Entity {
geometry,
construction,
})
.collect())
}
/// Immutable, completely validated supplied-data instructions. Getters cannot
/// mutate the validated plan; cloned individual geometry is no longer this plan.
#[derive(Clone, Debug, Serialize)]
pub struct PreparedPlan {
kind: &'static str,
evidence_scope: &'static str,
units: &'static str,
items: Vec<PreparedItem>,
entity_count: usize,
stored_point_count: usize,
}
impl PreparedPlan {
pub fn items(&self) -> &[PreparedItem] {
&self.items
}
pub fn entity_count(&self) -> usize {
self.entity_count
}
pub fn stored_point_count(&self) -> usize {
self.stored_point_count
}
pub fn to_json_bytes(&self) -> Result<Vec<u8>> {
serde_json::to_vec(self).map_err(|_| Error::Serialization)
}
}
struct Counter {
bytes: usize,
limit: usize,
}
impl Write for Counter {
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
self.bytes = self
.bytes
.checked_add(data.len())
.ok_or_else(|| io::Error::other("size overflow"))?;
if self.bytes > self.limit {
return Err(io::Error::other("output limit"));
}
Ok(data.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
pub fn prepare(request: &Request) -> Result<PreparedPlan> {
prepare_with_limits(request, Limits::default())
}
pub fn prepare_with_limits(request: &Request, limits: Limits) -> Result<PreparedPlan> {
require((1..=MAX_ITEMS).contains(&request.items.len()), Error::Count)?;
require(
limits.max_entities > 0
&& limits.max_entities <= MAX_ENTITIES
&& limits.max_stored_points > 0
&& limits.max_stored_points <= MAX_STORED_POINTS
&& limits.max_output_bytes > 0
&& limits.max_output_bytes <= MAX_OUTPUT_BYTES,
Error::Budget,
)?;
let mut entities = 0usize;
let mut points = 0usize;
// Validate/count expansion before constructing any generated geometry.
for item in &request.items {
let (e, p) = cost(item)?;
entities = entities.checked_add(e).ok_or(Error::Budget)?;
points = points.checked_add(p).ok_or(Error::Budget)?;
require(
entities <= limits.max_entities && points <= limits.max_stored_points,
Error::Budget,
)?;
}
let items: Result<Vec<_>> = request
.items
.iter()
.enumerate()
.map(|(input_index, item)| {
Ok(PreparedItem {
input_index,
primitive: kind(item),
entities: compile(item)?,
})
})
.collect();
let result=PreparedPlan{kind:"SUPPLIED_SKETCH_GEOMETRY_PLAN",
evidence_scope:"Supplied numeric geometry only; no native identity, solver validity, transaction, mutation or modelUnchanged proof.",
units:"cm_and_radians",items:items?,entity_count:entities,stored_point_count:points};
let mut counter = Counter {
bytes: 0,
limit: limits.max_output_bytes,
};
serde_json::to_writer(&mut counter, &result).map_err(|_| Error::Budget)?;
Ok(result)
}