1#![deny(unsafe_op_in_unsafe_fn)]
7
8use std::collections::HashMap;
9use std::panic::{catch_unwind, AssertUnwindSafe};
10use std::sync::atomic::{AtomicU64, Ordering};
11use std::sync::{Arc, LazyLock, Mutex, MutexGuard};
12
13use axiolid::application::Application;
14use axiolid::core::Point3;
15use axiolid::mesh::TriMesh;
16
17#[non_exhaustive]
19#[repr(i32)]
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum AxiolidStatus {
22 Ok = 0,
23 NullPointer = 1,
24 InvalidArgument = 2,
25 InvalidHandle = 3,
26 LimitExceeded = 4,
27 Unsupported = 5,
28 UnsupportedExact = 6,
29 BufferTooSmall = 7,
30 OperationFailed = 8,
31 WrongResultKind = 9,
32 NoError = 10,
33 Panic = 255,
34}
35
36#[repr(C)]
38#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
39pub struct AxiolidVersion {
40 pub abi_major: u16,
41 pub abi_minor: u16,
42 pub abi_patch: u16,
43 pub crate_major: u16,
44 pub crate_minor: u16,
45 pub crate_patch: u16,
46}
47
48#[repr(transparent)]
50#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
51pub struct AxiolidContextHandle(pub u64);
52
53impl AxiolidContextHandle {
54 pub const INVALID: Self = Self(0);
55}
56
57pub type AxiolidProviderProfile = i32;
59pub const AXIOLID_PROVIDER_PORTABLE: AxiolidProviderProfile = 1;
60
61#[repr(C)]
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub struct AxiolidContextConfig {
65 pub provider_profile: AxiolidProviderProfile,
66 pub max_meshes: u32,
67 pub max_results: u32,
68 pub max_vertices_per_mesh: u32,
69 pub max_triangles_per_mesh: u32,
70}
71
72impl Default for AxiolidContextConfig {
73 fn default() -> Self {
74 Self {
75 provider_profile: AXIOLID_PROVIDER_PORTABLE,
76 max_meshes: 1_024,
77 max_results: 1_024,
78 max_vertices_per_mesh: 10_000_000,
79 max_triangles_per_mesh: 20_000_000,
80 }
81 }
82}
83
84struct Context {
85 application: Application,
86 config: AxiolidContextConfig,
87 meshes: HandleTable<TriMesh>,
88 results: HandleTable<StoredResult>,
89 last_error: Option<ErrorRecord>,
90}
91
92enum StoredResult {
93 Mesh(TriMesh),
94 Exact(Box<axiolid::brep::ExactBRep>),
95 HiddenLines(axiolid::mesh_hidden_lines::HiddenLineOutcome),
96 VisibleFaces(axiolid::mesh_hidden_lines::VisibleFaceOutcome),
97}
98
99struct ErrorRecord {
100 status: AxiolidStatus,
101 operation: AxiolidOperation,
102 tolerance: AxiolidTolerance,
103 provider: String,
104 message: String,
105 mesh: Option<u64>,
107}
108
109struct HandleTable<T> {
110 values: HashMap<u64, T>,
111}
112
113impl<T> Default for HandleTable<T> {
114 fn default() -> Self {
115 Self {
116 values: HashMap::new(),
117 }
118 }
119}
120
121static NEXT_HANDLE: AtomicU64 = AtomicU64::new(1);
122
123impl<T> HandleTable<T> {
124 fn insert(&mut self, value: T) -> u64 {
125 let handle = loop {
126 let candidate = NEXT_HANDLE.fetch_add(1, Ordering::Relaxed);
127 if candidate != 0 && !self.values.contains_key(&candidate) {
128 break candidate;
129 }
130 };
131 self.values.insert(handle, value);
132 handle
133 }
134
135 fn get(&self, handle: u64) -> Option<&T> {
136 self.values.get(&handle)
137 }
138
139 fn get_mut(&mut self, handle: u64) -> Option<&mut T> {
140 self.values.get_mut(&handle)
141 }
142
143 fn remove(&mut self, handle: u64) -> Option<T> {
144 self.values.remove(&handle)
145 }
146
147 fn live_count(&self) -> usize {
148 self.values.len()
149 }
150}
151
152static CONTEXTS: LazyLock<Mutex<HandleTable<Arc<Mutex<Context>>>>> =
153 LazyLock::new(|| Mutex::new(HandleTable::default()));
154
155fn lock_unpoisoned<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
156 mutex
157 .lock()
158 .unwrap_or_else(|poisoned| poisoned.into_inner())
159}
160
161fn contexts() -> MutexGuard<'static, HandleTable<Arc<Mutex<Context>>>> {
162 lock_unpoisoned(&CONTEXTS)
163}
164
165fn context_entry(handle: u64) -> Option<Arc<Mutex<Context>>> {
166 contexts().get(handle).cloned()
167}
168
169fn boundary(operation: impl FnOnce() -> AxiolidStatus) -> AxiolidStatus {
170 catch_unwind(AssertUnwindSafe(operation)).unwrap_or(AxiolidStatus::Panic)
171}
172
173#[no_mangle]
179pub unsafe extern "C" fn axiolid_v0_4_version(out_version: *mut AxiolidVersion) -> AxiolidStatus {
180 boundary(|| {
181 if out_version.is_null() {
182 return AxiolidStatus::NullPointer;
183 }
184 let version = AxiolidVersion {
185 abi_major: 0,
186 abi_minor: 4,
187 abi_patch: 1,
188 crate_major: env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap_or(0),
189 crate_minor: env!("CARGO_PKG_VERSION_MINOR").parse().unwrap_or(0),
190 crate_patch: env!("CARGO_PKG_VERSION_PATCH").parse().unwrap_or(0),
191 };
192 unsafe { out_version.write(version) };
194 AxiolidStatus::Ok
195 })
196}
197
198#[no_mangle]
204pub unsafe extern "C" fn axiolid_v0_4_context_create(
205 config: *const AxiolidContextConfig,
206 out_context: *mut AxiolidContextHandle,
207) -> AxiolidStatus {
208 boundary(|| {
209 if config.is_null() || out_context.is_null() {
210 return AxiolidStatus::NullPointer;
211 }
212 let config = unsafe { config.read() };
214 if config.provider_profile != AXIOLID_PROVIDER_PORTABLE
215 || config.max_meshes == 0
216 || config.max_results == 0
217 || config.max_vertices_per_mesh == 0
218 || config.max_triangles_per_mesh == 0
219 {
220 return AxiolidStatus::InvalidArgument;
221 }
222 let Ok(application) = Application::portable() else {
223 return AxiolidStatus::OperationFailed;
224 };
225 let handle = AxiolidContextHandle(contexts().insert(Arc::new(Mutex::new(Context {
226 application,
227 config,
228 meshes: HandleTable::default(),
229 results: HandleTable::default(),
230 last_error: None,
231 }))));
232 unsafe { out_context.write(handle) };
234 AxiolidStatus::Ok
235 })
236}
237
238#[no_mangle]
242pub extern "C" fn axiolid_v0_4_context_destroy(context: AxiolidContextHandle) -> AxiolidStatus {
243 boundary(|| {
244 if contexts().remove(context.0).is_some() {
245 AxiolidStatus::Ok
246 } else {
247 AxiolidStatus::InvalidHandle
248 }
249 })
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255
256 #[test]
257 fn panic_is_contained() {
258 assert_eq!(boundary(|| panic!("contained")), AxiolidStatus::Panic);
259 }
260}
261
262#[repr(transparent)]
264#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
265pub struct AxiolidMeshHandle(pub u64);
266
267impl AxiolidMeshHandle {
268 pub const INVALID: Self = Self(0);
269}
270
271#[non_exhaustive]
273#[repr(i32)]
274#[derive(Debug, Clone, Copy, PartialEq, Eq)]
275pub enum AxiolidOperation {
276 CurveEvaluation = 1,
277 SurfaceEvaluation = 2,
278 ProfileTriangulation = 3,
279 Sweep = 4,
280 Tessellation = 5,
281 MeshBoolean = 6,
282 MeshPlaneSection = 7,
283 SpatialQuery = 8,
284 Measurement = 9,
285 Healing = 10,
286 GraphCompilation = 11,
287 MeshHiddenLines = 12,
288 MeshVisibleFaces = 13,
289 Unknown = 255,
290}
291
292#[non_exhaustive]
294#[repr(i32)]
295#[derive(Debug, Clone, Copy, PartialEq, Eq)]
296pub enum AxiolidRepresentation {
297 Scalar = 1,
298 Linear = 2,
299 Profile2d = 3,
300 AnalyticCurve = 4,
301 AnalyticSurface = 5,
302 Topology = 6,
303 ExactBrep = 7,
304 TriangleMesh = 8,
305 MeshHealth = 9,
306 Measurements = 10,
307 RayHit = 11,
308 ModelGraph = 12,
309 SampledField = 13,
310 Unknown = 255,
311}
312
313#[non_exhaustive]
315#[repr(i32)]
316#[derive(Debug, Clone, Copy, PartialEq, Eq)]
317pub enum AxiolidExactness {
318 Exact = 1,
319 ToleranceBounded = 2,
320}
321
322#[repr(C)]
324#[derive(Debug, Clone, Copy)]
325pub struct AxiolidCapabilityDescriptor {
326 pub operation: AxiolidOperation,
327 pub representation: AxiolidRepresentation,
328 pub exactness: AxiolidExactness,
329 pub id: [u8; 64],
330 pub provider: [u8; 64],
331 pub required_feature: [u8; 32],
332 pub id_len: u8,
333 pub provider_len: u8,
334 pub required_feature_len: u8,
335 pub deterministic: u8,
336}
337
338impl Default for AxiolidCapabilityDescriptor {
339 fn default() -> Self {
340 Self {
341 id: [0; 64],
342 id_len: 0,
343 provider: [0; 64],
344 provider_len: 0,
345 required_feature: [0; 32],
346 required_feature_len: 0,
347 operation: AxiolidOperation::Unknown,
348 representation: AxiolidRepresentation::Unknown,
349 exactness: AxiolidExactness::ToleranceBounded,
350 deterministic: 0,
351 }
352 }
353}
354
355fn copy_text<const N: usize>(text: &str, destination: &mut [u8; N]) -> u8 {
356 let count = text.len().min(N);
357 destination[..count].copy_from_slice(&text.as_bytes()[..count]);
358 count as u8
359}
360
361fn operation(value: axiolid::contracts::Operation) -> AxiolidOperation {
362 use axiolid::contracts::Operation;
363 match value {
364 Operation::CurveEvaluation => AxiolidOperation::CurveEvaluation,
365 Operation::SurfaceEvaluation => AxiolidOperation::SurfaceEvaluation,
366 Operation::ProfileTriangulation => AxiolidOperation::ProfileTriangulation,
367 Operation::Sweep => AxiolidOperation::Sweep,
368 Operation::Tessellation => AxiolidOperation::Tessellation,
369 Operation::MeshBoolean => AxiolidOperation::MeshBoolean,
370 Operation::MeshPlaneSection => AxiolidOperation::MeshPlaneSection,
371 Operation::SpatialQuery => AxiolidOperation::SpatialQuery,
372 Operation::Measurement => AxiolidOperation::Measurement,
373 Operation::Healing => AxiolidOperation::Healing,
374 Operation::GraphCompilation => AxiolidOperation::GraphCompilation,
375 Operation::MeshHiddenLines => AxiolidOperation::MeshHiddenLines,
376 Operation::MeshVisibleFaces => AxiolidOperation::MeshVisibleFaces,
377 _ => AxiolidOperation::Unknown,
378 }
379}
380
381fn representation(value: axiolid::contracts::Representation) -> AxiolidRepresentation {
382 use axiolid::contracts::Representation;
383 match value {
384 Representation::Scalar => AxiolidRepresentation::Scalar,
385 Representation::Linear => AxiolidRepresentation::Linear,
386 Representation::Profile2d => AxiolidRepresentation::Profile2d,
387 Representation::AnalyticCurve => AxiolidRepresentation::AnalyticCurve,
388 Representation::AnalyticSurface => AxiolidRepresentation::AnalyticSurface,
389 Representation::Topology => AxiolidRepresentation::Topology,
390 Representation::ExactBrep => AxiolidRepresentation::ExactBrep,
391 Representation::TriangleMesh => AxiolidRepresentation::TriangleMesh,
392 Representation::MeshHealth => AxiolidRepresentation::MeshHealth,
393 Representation::Measurements => AxiolidRepresentation::Measurements,
394 Representation::RayHit => AxiolidRepresentation::RayHit,
395 Representation::ModelGraph => AxiolidRepresentation::ModelGraph,
396 Representation::SampledField => AxiolidRepresentation::SampledField,
397 }
398}
399
400fn exactness(value: axiolid::contracts::Exactness) -> AxiolidExactness {
401 match value {
402 axiolid::contracts::Exactness::Exact => AxiolidExactness::Exact,
403 axiolid::contracts::Exactness::ToleranceBounded => AxiolidExactness::ToleranceBounded,
404 }
405}
406
407fn capability(value: &axiolid::contracts::CapabilityDescriptor) -> AxiolidCapabilityDescriptor {
408 let mut result = AxiolidCapabilityDescriptor::default();
409 result.id_len = copy_text(value.id.as_str(), &mut result.id);
410 result.provider_len = copy_text(value.provider.as_str(), &mut result.provider);
411 result.required_feature_len = copy_text(value.required_feature, &mut result.required_feature);
412 result.operation = operation(value.operation);
413 result.representation = representation(value.output);
414 result.exactness = exactness(value.exactness);
415 result.deterministic = u8::from(value.deterministic);
416 result
417}
418
419fn c_capabilities(
420 context: &Context,
421) -> impl Iterator<Item = &axiolid::contracts::CapabilityDescriptor> {
422 context
423 .application
424 .descriptor()
425 .capabilities
426 .iter()
427 .filter(|descriptor| {
428 matches!(
429 descriptor.operation,
430 axiolid::contracts::Operation::Healing
431 | axiolid::contracts::Operation::Measurement
432 | axiolid::contracts::Operation::MeshBoolean
433 | axiolid::contracts::Operation::MeshHiddenLines
434 | axiolid::contracts::Operation::MeshVisibleFaces
435 | axiolid::contracts::Operation::Sweep
436 )
437 })
438}
439
440#[no_mangle]
444pub unsafe extern "C" fn axiolid_v0_4_capability_count(
445 context: AxiolidContextHandle,
446 out_count: *mut usize,
447) -> AxiolidStatus {
448 boundary(|| {
449 if out_count.is_null() {
450 return AxiolidStatus::NullPointer;
451 }
452 let Some(context) = context_entry(context.0) else {
453 return AxiolidStatus::InvalidHandle;
454 };
455 let context = lock_unpoisoned(&context);
456 let count = c_capabilities(&context).count();
457 unsafe { out_count.write(count) };
459 AxiolidStatus::Ok
460 })
461}
462
463#[no_mangle]
467pub unsafe extern "C" fn axiolid_v0_4_capability_get(
468 context: AxiolidContextHandle,
469 index: usize,
470 out_descriptor: *mut AxiolidCapabilityDescriptor,
471) -> AxiolidStatus {
472 boundary(|| {
473 if out_descriptor.is_null() {
474 return AxiolidStatus::NullPointer;
475 }
476 let Some(context) = context_entry(context.0) else {
477 return AxiolidStatus::InvalidHandle;
478 };
479 let context = lock_unpoisoned(&context);
480 let Some(value) = c_capabilities(&context).nth(index) else {
481 return AxiolidStatus::InvalidArgument;
482 };
483 unsafe { out_descriptor.write(capability(value)) };
485 AxiolidStatus::Ok
486 })
487}
488
489fn tolerance(value: AxiolidTolerance) -> Result<axiolid::core::Tolerance, AxiolidStatus> {
490 axiolid::core::Tolerance::new(value.linear, value.angular)
491 .map_err(|_| AxiolidStatus::InvalidArgument)
492}
493
494fn record_error(
495 mut context: MutexGuard<'_, Context>,
496 status: AxiolidStatus,
497 operation: AxiolidOperation,
498 tolerance: AxiolidTolerance,
499 provider: impl Into<String>,
500 message: impl Into<String>,
501) -> AxiolidStatus {
502 context.last_error = Some(ErrorRecord {
503 status,
504 operation,
505 tolerance,
506 provider: provider.into(),
507 message: message.into(),
508 mesh: None,
509 });
510 status
511}
512
513fn record_application_error(
514 context: MutexGuard<'_, Context>,
515 error: axiolid::application::ApplicationError,
516) -> AxiolidStatus {
517 let operation = operation(error.context.operation);
518 let tolerance = AxiolidTolerance {
519 linear: error.context.tolerance.linear(),
520 angular: error.context.tolerance.angular(),
521 };
522 record_error(
523 context,
524 AxiolidStatus::OperationFailed,
525 operation,
526 tolerance,
527 error.context.provider.as_str(),
528 error.to_string(),
529 )
530}
531
532#[repr(C)]
534#[derive(Debug, Clone, Copy)]
535pub struct AxiolidErrorInfo {
536 pub status: AxiolidStatus,
537 pub operation: AxiolidOperation,
538 pub tolerance: AxiolidTolerance,
539 pub provider: [u8; 64],
540 pub message_len: usize,
541 pub provider_len: u8,
542 pub reserved: [u8; 7],
544}
545
546impl Default for AxiolidErrorInfo {
547 fn default() -> Self {
548 Self {
549 status: AxiolidStatus::NoError,
550 operation: AxiolidOperation::Unknown,
551 tolerance: AxiolidTolerance {
552 linear: 0.0,
553 angular: 0.0,
554 },
555 provider: [0; 64],
556 message_len: 0,
557 provider_len: 0,
558 reserved: [0; 7],
559 }
560 }
561}
562
563mod diagnostics;
564mod drawing;
565mod faces;
566mod mesh;
567mod operations;
568
569pub use diagnostics::*;
570pub use drawing::*;
571pub use faces::*;
572pub use mesh::*;
573pub use operations::*;