axiolid_capi/
operations.rs1use super::*;
2
3pub type AxiolidBooleanOperator = i32;
5pub const AXIOLID_BOOLEAN_UNION: AxiolidBooleanOperator = 1;
6pub const AXIOLID_BOOLEAN_INTERSECTION: AxiolidBooleanOperator = 2;
7pub const AXIOLID_BOOLEAN_DIFFERENCE: AxiolidBooleanOperator = 3;
8pub const AXIOLID_BOOLEAN_SYMMETRIC_DIFFERENCE: AxiolidBooleanOperator = 4;
9
10fn boolean_operator(value: AxiolidBooleanOperator) -> Option<axiolid::core::BooleanOperator> {
11 match value {
12 AXIOLID_BOOLEAN_UNION => Some(axiolid::core::BooleanOperator::Union),
13 AXIOLID_BOOLEAN_INTERSECTION => Some(axiolid::core::BooleanOperator::Intersection),
14 AXIOLID_BOOLEAN_DIFFERENCE => Some(axiolid::core::BooleanOperator::Difference),
15 AXIOLID_BOOLEAN_SYMMETRIC_DIFFERENCE => {
16 Some(axiolid::core::BooleanOperator::SymmetricDifference)
17 }
18 _ => None,
19 }
20}
21
22pub(crate) fn insert_result(
23 context: &mut Context,
24 result: StoredResult,
25) -> Result<u64, AxiolidStatus> {
26 if context.results.live_count() >= context.config.max_results as usize {
27 return Err(AxiolidStatus::LimitExceeded);
28 }
29 Ok(context.results.insert(result))
30}
31
32#[no_mangle]
36pub unsafe extern "C" fn axiolid_v0_4_boolean(
37 context: AxiolidContextHandle,
38 subject: AxiolidMeshHandle,
39 tool: AxiolidMeshHandle,
40 operator: AxiolidBooleanOperator,
41 tolerance_value: AxiolidTolerance,
42 out_result: *mut AxiolidResultHandle,
43) -> AxiolidStatus {
44 boundary(|| {
45 if out_result.is_null() {
46 return AxiolidStatus::NullPointer;
47 }
48 let Ok(tolerance) = tolerance(tolerance_value) else {
49 return AxiolidStatus::InvalidArgument;
50 };
51 let Some(context) = context_entry(context.0) else {
52 return AxiolidStatus::InvalidHandle;
53 };
54 let mut context = lock_unpoisoned(&context);
55 let Some(subject) = context.meshes.get(subject.0).cloned() else {
56 return AxiolidStatus::InvalidHandle;
57 };
58 let Some(tool) = context.meshes.get(tool.0).cloned() else {
59 return AxiolidStatus::InvalidHandle;
60 };
61 let Some(operator) = boolean_operator(operator) else {
62 return record_error(
63 context,
64 AxiolidStatus::InvalidArgument,
65 AxiolidOperation::MeshBoolean,
66 tolerance_value,
67 "axiolid-capi",
68 "unknown Boolean operator",
69 );
70 };
71 let options = axiolid::contracts::ExecutionOptions::new(tolerance);
72 let outcome = match context
73 .application
74 .boolean(&subject, &tool, operator, &options)
75 {
76 Ok(outcome) => outcome,
77 Err(error) => return record_application_error(context, error),
78 };
79 let Ok(handle) = insert_result(&mut context, StoredResult::Mesh(outcome.mesh)) else {
80 return AxiolidStatus::LimitExceeded;
81 };
82 unsafe { out_result.write(AxiolidResultHandle(handle)) };
84 AxiolidStatus::Ok
85 })
86}
87
88#[no_mangle]
92pub unsafe extern "C" fn axiolid_v0_4_subtract_many(
93 context: AxiolidContextHandle,
94 subject: AxiolidMeshHandle,
95 tools: *const AxiolidMeshHandle,
96 tool_count: usize,
97 tolerance_value: AxiolidTolerance,
98 out_result: *mut AxiolidResultHandle,
99) -> AxiolidStatus {
100 boundary(|| {
101 if out_result.is_null() || (tool_count != 0 && tools.is_null()) {
102 return AxiolidStatus::NullPointer;
103 }
104 let Ok(tolerance) = tolerance(tolerance_value) else {
105 return AxiolidStatus::InvalidArgument;
106 };
107 let tool_handles: &[AxiolidMeshHandle] = if tool_count == 0 {
108 &[]
109 } else {
110 unsafe { std::slice::from_raw_parts(tools, tool_count) }
112 };
113 let Some(context) = context_entry(context.0) else {
114 return AxiolidStatus::InvalidHandle;
115 };
116 let mut context = lock_unpoisoned(&context);
117 let Some(subject) = context.meshes.get(subject.0).cloned() else {
118 return AxiolidStatus::InvalidHandle;
119 };
120 let Some(tools) = tool_handles
121 .iter()
122 .map(|handle| context.meshes.get(handle.0).cloned())
123 .collect::<Option<Vec<_>>>()
124 else {
125 return AxiolidStatus::InvalidHandle;
126 };
127 let options = axiolid::contracts::ExecutionOptions::new(tolerance);
128 let outcome = match context
129 .application
130 .subtract_many(&subject, &tools, &options)
131 {
132 Ok(outcome) => outcome,
133 Err(error) => return record_application_error(context, error),
134 };
135 let Ok(handle) = insert_result(&mut context, StoredResult::Mesh(outcome.mesh)) else {
136 return AxiolidStatus::LimitExceeded;
137 };
138 unsafe { out_result.write(AxiolidResultHandle(handle)) };
140 AxiolidStatus::Ok
141 })
142}
143
144#[no_mangle]
148pub unsafe extern "C" fn axiolid_v0_4_exact_extrude_rectangle(
149 context: AxiolidContextHandle,
150 width: f64,
151 height: f64,
152 depth: f64,
153 tolerance_value: AxiolidTolerance,
154 out_result: *mut AxiolidResultHandle,
155) -> AxiolidStatus {
156 boundary(|| {
157 if out_result.is_null() {
158 return AxiolidStatus::NullPointer;
159 }
160 let Ok(tolerance) = tolerance(tolerance_value) else {
161 return AxiolidStatus::InvalidArgument;
162 };
163 if !width.is_finite()
164 || !height.is_finite()
165 || !depth.is_finite()
166 || width <= 0.0
167 || height <= 0.0
168 || depth <= 0.0
169 {
170 return AxiolidStatus::InvalidArgument;
171 }
172 let Some(context) = context_entry(context.0) else {
173 return AxiolidStatus::InvalidHandle;
174 };
175 let mut context = lock_unpoisoned(&context);
176 let profile = axiolid::profile::Profile::Rectangle(axiolid::profile::RectangleProfile {
177 x: width,
178 y: height,
179 thickness: None,
180 outer_radius: None,
181 inner_radius: None,
182 });
183 let exact = match context.application.extrude_profile_exact(
184 &profile,
185 axiolid::core::Vec3::Z,
186 depth,
187 tolerance,
188 ) {
189 Ok(exact) => exact,
190 Err(error) => return record_application_error(context, error),
191 };
192 let Ok(handle) = insert_result(&mut context, StoredResult::Exact(Box::new(exact))) else {
193 return AxiolidStatus::LimitExceeded;
194 };
195 unsafe { out_result.write(AxiolidResultHandle(handle)) };
197 AxiolidStatus::Ok
198 })
199}
200
201#[no_mangle]
205pub unsafe extern "C" fn axiolid_v0_4_exact_boolean(
206 context: AxiolidContextHandle,
207 subject: AxiolidMeshHandle,
208 tool: AxiolidMeshHandle,
209 tolerance_value: AxiolidTolerance,
210 out_result: *mut AxiolidResultHandle,
211) -> AxiolidStatus {
212 boundary(|| {
213 if out_result.is_null() {
214 return AxiolidStatus::NullPointer;
215 }
216 if tolerance(tolerance_value).is_err() {
217 return AxiolidStatus::InvalidArgument;
218 }
219 let Some(context) = context_entry(context.0) else {
220 return AxiolidStatus::InvalidHandle;
221 };
222 let context = lock_unpoisoned(&context);
223 if context.meshes.get(subject.0).is_none() || context.meshes.get(tool.0).is_none() {
224 return AxiolidStatus::InvalidHandle;
225 }
226 record_error(
227 context,
228 AxiolidStatus::UnsupportedExact,
229 AxiolidOperation::MeshBoolean,
230 tolerance_value,
231 "none",
232 "v0.4 does not provide exact Boolean; no mesh fallback was attempted",
233 )
234 })
235}
236
237#[no_mangle]
241pub unsafe extern "C" fn axiolid_v0_4_result_kind(
242 context: AxiolidContextHandle,
243 result: AxiolidResultHandle,
244 out_kind: *mut AxiolidGeometryKind,
245) -> AxiolidStatus {
246 boundary(|| {
247 if out_kind.is_null() {
248 return AxiolidStatus::NullPointer;
249 }
250 let Some(context) = context_entry(context.0) else {
251 return AxiolidStatus::InvalidHandle;
252 };
253 let context = lock_unpoisoned(&context);
254 let Some(result) = context.results.get(result.0) else {
255 return AxiolidStatus::InvalidHandle;
256 };
257 let kind = match result {
258 StoredResult::Mesh(_) => AxiolidGeometryKind::TriangleMesh,
259 StoredResult::Exact(exact) => {
260 let _ = exact.topology();
261 AxiolidGeometryKind::ExactBrep
262 }
263 StoredResult::HiddenLines(_) => AxiolidGeometryKind::HiddenLines,
264 StoredResult::VisibleFaces(_) => AxiolidGeometryKind::VisibleFaces,
265 };
266 unsafe { out_kind.write(kind) };
268 AxiolidStatus::Ok
269 })
270}
271
272#[no_mangle]
276pub unsafe extern "C" fn axiolid_v0_4_result_take_mesh(
277 context: AxiolidContextHandle,
278 result: AxiolidResultHandle,
279 out_mesh: *mut AxiolidMeshHandle,
280) -> AxiolidStatus {
281 boundary(|| {
282 if out_mesh.is_null() {
283 return AxiolidStatus::NullPointer;
284 }
285 let Some(context) = context_entry(context.0) else {
286 return AxiolidStatus::InvalidHandle;
287 };
288 let mut context = lock_unpoisoned(&context);
289 if context.meshes.live_count() >= context.config.max_meshes as usize {
290 return AxiolidStatus::LimitExceeded;
291 }
292 if !matches!(context.results.get(result.0), Some(StoredResult::Mesh(_))) {
293 return if context.results.get(result.0).is_some() {
294 AxiolidStatus::WrongResultKind
295 } else {
296 AxiolidStatus::InvalidHandle
297 };
298 }
299 let Some(StoredResult::Mesh(mesh)) = context.results.remove(result.0) else {
300 return AxiolidStatus::InvalidHandle;
301 };
302 let mesh = AxiolidMeshHandle(context.meshes.insert(mesh));
303 unsafe { out_mesh.write(mesh) };
305 AxiolidStatus::Ok
306 })
307}
308
309#[no_mangle]
311pub extern "C" fn axiolid_v0_4_result_destroy(
312 context: AxiolidContextHandle,
313 result: AxiolidResultHandle,
314) -> AxiolidStatus {
315 boundary(|| {
316 let Some(context) = context_entry(context.0) else {
317 return AxiolidStatus::InvalidHandle;
318 };
319 let mut context = lock_unpoisoned(&context);
320 match context.results.remove(result.0) {
321 Some(_) => AxiolidStatus::Ok,
322 None => AxiolidStatus::InvalidHandle,
323 }
324 })
325}