use super::ffi;
use crate::common::error::Error;
use crate::traits::{BSplineEnd, EdgeStruct, Transform, Wire};
use glam::DVec3;
pub struct Edge {
pub(crate) inner: cxx::UniquePtr<ffi::TopoDS_Edge>,
}
impl Edge {
pub(crate) fn try_from_ffi(inner: cxx::UniquePtr<ffi::TopoDS_Edge>, msg: String) -> Result<Self, Error> {
if inner.is_null() {
Err(Error::InvalidEdge(msg))
} else {
Ok(Edge { inner })
}
}
}
impl Clone for Edge {
fn clone(&self) -> Self {
Edge::try_from_ffi(ffi::deep_copy_edge(&self.inner), "Edge::clone: deep_copy_edge returned null".into())
.expect("Edge::clone: unexpected null from deep_copy_edge (this is a bug)")
}
}
impl EdgeStruct for Edge {
fn id(&self) -> u64 {
ffi::edge_tshape_id(&self.inner)
}
fn helix(radius: f64, pitch: f64, height: f64, axis: DVec3, x_ref: DVec3) -> Result<Self, Error> {
let inner = ffi::make_helix_edge(axis.x, axis.y, axis.z, x_ref.x, x_ref.y, x_ref.z, radius, pitch, height);
Edge::try_from_ffi(inner, format!("helix: degenerate params (radius={radius}, pitch={pitch}, height={height}, axis={axis:?}, x_ref={x_ref:?})"))
}
fn polygon<'a>(points: impl IntoIterator<Item = &'a DVec3>) -> Result<Vec<Self>, Error> {
let coords: Vec<f64> = points.into_iter().flat_map(|p| [p.x, p.y, p.z]).collect();
let cxx_vec = ffi::make_polygon_edges(&coords);
if cxx_vec.is_empty() {
return Err(Error::InvalidEdge(format!(
"polygon: construction failed (point count = {}, need ≥ 3 non-degenerate)",
coords.len() / 3
)));
}
cxx_vec
.iter()
.map(|e| {
Edge::try_from_ffi(
ffi::deep_copy_edge(e),
"polygon: deep_copy_edge returned null".into(),
)
})
.collect()
}
fn circle(radius: f64, axis: DVec3) -> Result<Self, Error> {
let inner = ffi::make_circle_edge(axis.x, axis.y, axis.z, radius);
Edge::try_from_ffi(inner, format!("circle: invalid params (radius={radius}, axis={axis:?})"))
}
fn line(a: DVec3, b: DVec3) -> Result<Self, Error> {
let inner = ffi::make_line_edge(a.x, a.y, a.z, b.x, b.y, b.z);
Edge::try_from_ffi(inner, format!("line: zero-length segment (a={a:?}, b={b:?})"))
}
fn arc_3pts(start: DVec3, mid: DVec3, end: DVec3) -> Result<Self, Error> {
let inner = ffi::make_arc_edge(start.x, start.y, start.z, mid.x, mid.y, mid.z, end.x, end.y, end.z);
Edge::try_from_ffi(inner, format!("arc_3pts: collinear or degenerate points (start={start:?}, mid={mid:?}, end={end:?})"))
}
fn bspline<'a>(points: impl IntoIterator<Item = &'a DVec3>, end: BSplineEnd) -> Result<Self, Error> {
let pts: Vec<DVec3> = points.into_iter().copied().collect();
let min_required = match end {
BSplineEnd::Periodic => 3,
BSplineEnd::NotAKnot | BSplineEnd::Clamped { .. } => 2,
};
if pts.len() < min_required {
return Err(Error::InvalidEdge(format!(
"bspline: need ≥{} points for {:?}, got {}",
min_required,
end,
pts.len()
)));
}
if matches!(end, BSplineEnd::Periodic) {
let first = pts.first().expect("checked above");
let last = pts.last().expect("checked above");
if first == last {
return Err(Error::InvalidEdge(format!(
"bspline(Periodic): first and last points coincide ({first:?}); periodicity is encoded in the basis, do not duplicate the closing point"
)));
}
}
let coords: Vec<f64> = pts.iter().flat_map(|p| [p.x, p.y, p.z]).collect();
let (kind, sx, sy, sz, ex, ey, ez) = match end {
BSplineEnd::Periodic => (0u32, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
BSplineEnd::NotAKnot => (1u32, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
BSplineEnd::Clamped { start: s, end: e } => (2u32, s.x, s.y, s.z, e.x, e.y, e.z),
};
let inner = ffi::make_bspline_edge(&coords, kind, sx, sy, sz, ex, ey, ez);
Edge::try_from_ffi(
inner,
format!("bspline: OCCT GeomAPI_Interpolate failed ({} points, end={end:?})", pts.len()),
)
}
}
impl Wire for Edge {
type Elem = Edge;
fn iter_elem(&self) -> impl Iterator<Item = &Edge> + '_ {
panic!("Cannot iter_elem on Edge, because Edge is not Wire");
#[allow(unreachable_code)] std::iter::empty()
}
fn map_elem(self, _: impl FnMut(Edge) -> Edge) -> Self {
panic!("Cannot map_elem on Edge, because Edge is not Wire");
}
fn start_point(&self) -> DVec3 {
let (mut sx, mut sy, mut sz) = (0.0_f64, 0.0_f64, 0.0_f64);
let (mut ex, mut ey, mut ez) = (0.0_f64, 0.0_f64, 0.0_f64);
ffi::edge_endpoints(&self.inner, &mut sx, &mut sy, &mut sz, &mut ex, &mut ey, &mut ez);
DVec3::new(sx, sy, sz)
}
fn end_point(&self) -> DVec3 {
let (mut sx, mut sy, mut sz) = (0.0_f64, 0.0_f64, 0.0_f64);
let (mut ex, mut ey, mut ez) = (0.0_f64, 0.0_f64, 0.0_f64);
ffi::edge_endpoints(&self.inner, &mut sx, &mut sy, &mut sz, &mut ex, &mut ey, &mut ez);
DVec3::new(ex, ey, ez)
}
fn start_tangent(&self) -> DVec3 {
let (mut sx, mut sy, mut sz) = (0.0_f64, 0.0_f64, 0.0_f64);
let (mut ex, mut ey, mut ez) = (0.0_f64, 0.0_f64, 0.0_f64);
ffi::edge_tangents(&self.inner, &mut sx, &mut sy, &mut sz, &mut ex, &mut ey, &mut ez);
DVec3::new(sx, sy, sz)
}
fn end_tangent(&self) -> DVec3 {
let (mut sx, mut sy, mut sz) = (0.0_f64, 0.0_f64, 0.0_f64);
let (mut ex, mut ey, mut ez) = (0.0_f64, 0.0_f64, 0.0_f64);
ffi::edge_tangents(&self.inner, &mut sx, &mut sy, &mut sz, &mut ex, &mut ey, &mut ez);
DVec3::new(ex, ey, ez)
}
fn is_closed(&self) -> bool {
ffi::edge_is_closed(&self.inner)
}
fn approximation_segments(&self, tolerance: f64) -> Vec<DVec3> {
ffi::edge_approximation_segments(&self.inner, tolerance, tolerance)
.chunks_exact(3)
.map(|c| DVec3::new(c[0], c[1], c[2]))
.collect()
}
fn project(&self, p: DVec3) -> (DVec3, DVec3) {
let (mut cpx, mut cpy, mut cpz) = (0.0_f64, 0.0_f64, 0.0_f64);
let (mut tx, mut ty, mut tz) = (0.0_f64, 0.0_f64, 0.0_f64);
assert!(
ffi::edge_project_point(&self.inner, p.x, p.y, p.z, &mut cpx, &mut cpy, &mut cpz, &mut tx, &mut ty, &mut tz),
"Edge::project: edge has no 3D curve or OCCT projector threw (this is a bug)"
);
(DVec3::new(cpx, cpy, cpz), DVec3::new(tx, ty, tz))
}
}
impl Transform for Edge {
fn translate(self, t: DVec3) -> Self {
Edge::try_from_ffi(ffi::translate_edge(&self.inner, t.x, t.y, t.z), "Edge::translate: null from FFI".into())
.expect("Edge::translate: unexpected null from translate_edge (this is a bug)")
}
fn rotate(self, axis_origin: DVec3, axis_direction: DVec3, angle: f64) -> Self {
Edge::try_from_ffi(
ffi::rotate_edge(&self.inner, axis_origin.x, axis_origin.y, axis_origin.z, axis_direction.x, axis_direction.y, axis_direction.z, angle),
"Edge::rotate: null from FFI".into(),
)
.expect("Edge::rotate: unexpected null from rotate_edge (this is a bug)")
}
fn scale(self, center: DVec3, factor: f64) -> Self {
Edge::try_from_ffi(ffi::scale_edge(&self.inner, center.x, center.y, center.z, factor), "Edge::scale: null from FFI".into())
.expect("Edge::scale: unexpected null from scale_edge (this is a bug)")
}
fn mirror(self, plane_origin: DVec3, plane_normal: DVec3) -> Self {
Edge::try_from_ffi(
ffi::mirror_edge(&self.inner, plane_origin.x, plane_origin.y, plane_origin.z, plane_normal.x, plane_normal.y, plane_normal.z),
"Edge::mirror: null from FFI".into(),
)
.expect("Edge::mirror: unexpected null from mirror_edge (this is a bug)")
}
}