use pyo3::exceptions::PyTypeError;
use pyo3::prelude::*;
use pyo3::types::{PyList, PyModule};
fn boot() {
static BOOT: std::sync::Once = std::sync::Once::new();
BOOT.call_once(|| {
use ferrocad_part_py::Part as part_module;
use ferrocad_py::ferrocad as ferrocad_module;
pyo3::append_to_inittab!(ferrocad_module);
pyo3::append_to_inittab!(part_module);
});
}
fn is_type_error(py: Python<'_>, error: &PyErr) -> bool {
error.is_instance_of::<PyTypeError>(py)
}
#[test]
fn shape_fix_wraps_each_occt_fixer() {
boot();
Python::with_gil(|py| {
let part = PyModule::import(py, "Part").expect("import Part");
let shape_fix = part.getattr("ShapeFix").expect("Part.ShapeFix");
for name in ["Root", "Shape", "Solid", "Shell", "Face", "Wire", "Edge"] {
assert!(shape_fix.hasattr(name).unwrap(), "Part.ShapeFix.{name}");
}
let root = shape_fix.getattr("Root").unwrap().call0().unwrap();
root.setattr("Precision", 0.0).unwrap();
assert_eq!(root.getattr("Precision").unwrap().extract::<f64>().unwrap(), 0.0);
root.setattr("MinTolerance", 0.0).unwrap();
assert_eq!(
root.getattr("MinTolerance").unwrap().extract::<f64>().unwrap(),
0.0
);
root.setattr("MaxTolerance", 0.5).unwrap();
assert_eq!(
root.getattr("MaxTolerance").unwrap().extract::<f64>().unwrap(),
0.5
);
let limited = root
.call_method1("limitTolerance", (0.25,))
.unwrap()
.extract::<f64>()
.unwrap();
assert!((limited - 0.25).abs() < 1e-12);
assert!(is_type_error(py, &shape_fix.getattr("Root").unwrap().call1((PyList::empty(py),)).unwrap_err()));
assert!(is_type_error(py, &shape_fix.getattr("Wire").unwrap().call1((PyList::empty(py),)).unwrap_err()));
assert!(is_type_error(py, &shape_fix.getattr("Edge").unwrap().call1((PyList::empty(py),)).unwrap_err()));
let plane = part.getattr("Plane").unwrap().call0().unwrap();
let face = plane
.call_method1("toShape", (-1.0, 1.0, -1.0, 1.0))
.unwrap();
let fixer = shape_fix.getattr("Shape").unwrap().call1((&face,)).unwrap();
fixer.call_method1("init", (&face,)).unwrap();
fixer.call_method0("perform").unwrap();
let fixed = fixer.call_method0("shape").unwrap();
assert!(
fixed
.is_instance(&part.getattr("Shape").unwrap())
.unwrap(),
"ShapeFix.Shape.shape() returns a Part.Shape"
);
assert_eq!(
fixer.call_method0("fixFaceTool").unwrap().get_type().name().unwrap(),
"Face"
);
assert_eq!(
fixer.call_method0("fixWireTool").unwrap().get_type().name().unwrap(),
"Wire"
);
assert_eq!(
fixer.call_method0("fixEdgeTool").unwrap().get_type().name().unwrap(),
"Edge"
);
let outer = face.getattr("OuterWire").unwrap();
let wire_fixer = shape_fix
.getattr("Wire")
.unwrap()
.call1((&outer, &face, 1e-7))
.unwrap();
assert_eq!(
wire_fixer
.call_method0("numberOfEdges")
.unwrap()
.extract::<i32>()
.unwrap(),
4
);
assert!(wire_fixer.call_method0("isLoaded").unwrap().extract::<bool>().unwrap());
assert!(wire_fixer.call_method0("isReady").unwrap().extract::<bool>().unwrap());
let edge_tool = wire_fixer.call_method0("fixEdgeTool").unwrap();
let edge1 = face.getattr("Edge1").unwrap();
edge_tool
.call_method1("fixRemovePCurve", (&edge1, &face))
.unwrap();
edge_tool
.call_method1(
"fixRemovePCurve",
(&edge1, face.getattr("Surface").unwrap(), face.getattr("Placement").unwrap()),
)
.unwrap();
assert!(is_type_error(
py,
&edge_tool.call_method1("fixRemovePCurve", (&face,)).unwrap_err()
));
});
}
#[test]
fn chfi2d_fillets_and_chamfers() {
boot();
Python::with_gil(|py| {
let part = PyModule::import(py, "Part").expect("import Part");
let ferrocad = PyModule::import(py, "ferrocad").expect("import ferrocad");
let vector = ferrocad.getattr("Vector").expect("ferrocad.Vector");
let point = |x: f64, y: f64, z: f64| {
vector.call1((x, y, z)).expect("a Vector").unbind()
};
let edge1 = part
.call_method1("makeLine", (point(0.0, 0.0, 0.0), point(0.0, 10.0, 0.0)))
.unwrap();
let edge2 = part
.call_method1("makeLine", (point(0.0, 10.0, 0.0), point(10.0, 10.0, 0.0)))
.unwrap();
let wire = part
.call_method1("Wire", (PyList::new(py, [&edge1, &edge2]).unwrap(),))
.unwrap();
let plane = part.getattr("Plane").unwrap().call0().unwrap();
let chfi2d = part.getattr("ChFi2d").expect("Part.ChFi2d");
let fillet = chfi2d
.getattr("FilletAlgo")
.unwrap()
.call1((&edge1, &edge2, &plane))
.unwrap();
assert!(fillet
.call_method1("perform", (1.0,))
.unwrap()
.extract::<bool>()
.unwrap());
assert_eq!(
fillet
.call_method1("numberOfResults", (point(0.0, 10.0, 0.0),))
.unwrap()
.extract::<i32>()
.unwrap(),
1
);
let result = fillet
.call_method1("result", (point(0.0, 10.0, 0.0),))
.unwrap();
assert_eq!(result.len().unwrap(), 3);
let curve = result.get_item(0).unwrap().getattr("Curve").unwrap();
let radius: f64 = curve.getattr("Radius").unwrap().extract().unwrap();
assert!((radius - 1.0).abs() < 1e-9, "fillet radius is 1.0");
let ana = chfi2d
.getattr("AnaFilletAlgo")
.unwrap()
.call1((&edge1, &edge2, &plane))
.unwrap();
assert!(ana
.call_method1("perform", (1.0,))
.unwrap()
.extract::<bool>()
.unwrap());
let ana_result = ana.call_method0("result").unwrap();
let ana_radius: f64 = ana_result
.get_item(0)
.unwrap()
.getattr("Curve")
.unwrap()
.getattr("Radius")
.unwrap()
.extract()
.unwrap();
assert!((ana_radius - 1.0).abs() < 1e-9, "analytic fillet radius is 1.0");
let chamfer = chfi2d
.getattr("ChamferAPI")
.unwrap()
.call1((&wire,))
.unwrap();
assert!(chamfer.call_method0("perform").unwrap().extract::<bool>().unwrap());
let chamfer_result = chamfer.call_method1("result", (1.0, 1.0)).unwrap();
let curve = chamfer_result
.get_item(0)
.unwrap()
.getattr("Curve")
.unwrap();
assert_eq!(curve.get_type().name().unwrap(), "Line");
assert!(is_type_error(
py,
&chfi2d.getattr("ChamferAPI").unwrap().call1((&edge1,)).unwrap_err()
));
assert!(is_type_error(py, &fillet.call_method0("perform").unwrap_err()));
let empty = chfi2d.getattr("FilletAlgo").unwrap().call0().unwrap();
assert!(empty.call_method1("perform", (1.0,)).is_err());
});
}