#![allow(non_snake_case)]
use std::sync::{Arc, Mutex};
use ferrocad_core::{ExtensionData, ExtensionValue};
use ferrocad_geom::Shape;
use ferrocad_occt::OcctBackend;
use ferrocad_part::{ShapeProperty, TYPE_NAME};
use pyo3::exceptions::{PyIOError, PyRuntimeError, PyValueError};
use pyo3::prelude::*;
use pyo3::types::{PyBytes, PyModule};
fn register_kernel() {
ferrocad_part::register(Arc::new(OcctBackend::new()));
}
#[pyclass(name = "Shape", module = "Part")]
struct PartShape {
shape: Mutex<Option<Shape>>,
}
impl PartShape {
fn shape(&self) -> Option<Shape> {
self.shape.lock().unwrap().clone()
}
}
#[pymethods]
impl PartShape {
#[new]
fn new() -> Self {
PartShape { shape: Mutex::new(None) }
}
fn isNull(&self) -> bool {
self.shape.lock().unwrap().is_none()
}
fn isSame(&self, other: &Bound<'_, PyAny>) -> bool {
let Ok(other) = other.downcast::<PartShape>() else {
return false;
};
let a = self.shape();
let b = other.borrow().shape();
match (a, b) {
(Some(x), Some(y)) => x.is_same(&y),
_ => false,
}
}
fn exportBrep(&self, filename: &str) -> PyResult<()> {
let shape = self
.shape()
.ok_or_else(|| PyValueError::new_err("cannot export a null shape"))?;
let bytes = ferrocad_part::backend().save_shape(&shape);
std::fs::write(filename, bytes).map_err(|e| PyIOError::new_err(e.to_string()))
}
fn exportBrepToString(&self, py: Python<'_>) -> PyResult<Py<PyBytes>> {
let shape = self
.shape()
.ok_or_else(|| PyValueError::new_err("cannot export a null shape"))?;
let bytes = ferrocad_part::backend().save_shape(&shape);
Ok(PyBytes::new(py, &bytes).unbind())
}
fn __repr__(&self) -> String {
match self.shape.lock().unwrap().as_ref() {
Some(shape) => format!("Part.Shape({shape:?})"),
None => "Part.Shape()".to_string(),
}
}
}
#[pyfunction]
fn makeBox(length: f64, width: f64, height: f64) -> PyResult<PartShape> {
match ferrocad_part::make_box(length, width, height) {
Some(shape) => Ok(PartShape { shape: Mutex::new(Some(shape)) }),
None => Err(PyRuntimeError::new_err("Part.makeBox did not produce a shape")),
}
}
fn shape_to_py(py: Python<'_>, data: &dyn ExtensionData) -> Option<PyObject> {
let property = data.as_any().downcast_ref::<ShapeProperty>()?;
let shape = property.shape();
Py::new(py, PartShape { shape: Mutex::new(shape) })
.ok()
.map(Py::into_any)
}
fn shape_to_extension(value: &Bound<'_, PyAny>) -> Option<ExtensionValue> {
let shape = value.downcast::<PartShape>().ok()?;
let inner = shape.borrow().shape();
let property = match inner {
Some(shape) => ShapeProperty::new(shape),
None => ShapeProperty::empty(),
};
Some(ExtensionValue::new(Box::new(property)))
}
#[pymodule]
pub fn Part(m: &Bound<'_, PyModule>) -> PyResult<()> {
register_kernel();
ferrocad_py::register_extension_converter(TYPE_NAME, shape_to_py, shape_to_extension);
m.add_class::<PartShape>()?;
m.add_function(wrap_pyfunction!(makeBox, m)?)?;
Ok(())
}