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