use brepkit_math::nurbs::surface::NurbsSurface;
use brepkit_math::tolerance::Tolerance;
pub const DEFAULT_DEFLECTION: f64 = 0.1;
use brepkit_math::vec::{Point3, Vec3};
use brepkit_topology::Topology;
use brepkit_topology::edge::{Edge, EdgeCurve, EdgeId};
use brepkit_topology::face::{Face, FaceId, FaceSurface};
use brepkit_topology::shell::Shell;
use brepkit_topology::solid::{Solid, SolidId};
use brepkit_topology::vertex::{Vertex, VertexId};
use brepkit_topology::wire::WireId;
use brepkit_topology::wire::{OrientedEdge, Wire};
use crate::dot_normal_point;
struct InnerWireData {
positions: Vec<Point3>,
oriented: Vec<OrientedEdge>,
edge_ids: Vec<EdgeId>,
top_edge_ids: Vec<EdgeId>,
vertical_edge_ids: Vec<EdgeId>,
}
pub fn maybe_split_closed_wire(
topo: &mut Topology,
oriented: &[OrientedEdge],
tol: f64,
deflection: f64,
) -> Result<Vec<OrientedEdge>, crate::OperationsError> {
maybe_split_closed_wire_with(
topo, oriented, tol, deflection, false,
)
}
pub(crate) fn maybe_split_closed_wire_with(
topo: &mut Topology,
oriented: &[OrientedEdge],
tol: f64,
deflection: f64,
pass_through_circles: bool,
) -> Result<Vec<OrientedEdge>, crate::OperationsError> {
let mut result = Vec::with_capacity(oriented.len() * 4);
for oe in oriented {
let edge = topo.edge(oe.edge())?;
if edge.start() == edge.end()
&& !(pass_through_circles && curve_is_analytic_circle(edge.curve()))
{
let n = closed_edge_segments(edge.curve(), deflection);
let split_edges = split_closed_edge(topo, oe.edge(), n, tol)?;
for se in split_edges {
result.push(OrientedEdge::new(se, oe.is_forward()));
}
} else {
result.push(*oe);
}
}
Ok(result)
}
fn curve_is_analytic_circle(curve: &EdgeCurve) -> bool {
let tol = Tolerance::new().linear;
match curve {
EdgeCurve::Line => false,
EdgeCurve::Circle(_) | EdgeCurve::Ellipse(_) => true,
EdgeCurve::NurbsCurve(nc) => matches!(
brepkit_geometry::convert::recognize_curve(nc, tol * 100.0),
brepkit_geometry::convert::RecognizedCurve::Circle { .. }
| brepkit_geometry::convert::RecognizedCurve::Ellipse { .. }
),
}
}
fn inner_wire_is_single_circle(topo: &Topology, wire_id: WireId) -> bool {
let Ok(wire) = topo.wire(wire_id) else {
return false;
};
let edges = wire.edges();
if edges.len() != 1 {
return false;
}
let Ok(edge) = topo.edge(edges[0].edge()) else {
return false;
};
if edge.start() != edge.end() {
return false; }
let tol = Tolerance::new().linear;
match edge.curve() {
EdgeCurve::Circle(_) => true,
EdgeCurve::NurbsCurve(nc) => matches!(
brepkit_geometry::convert::recognize_curve(nc, tol * 100.0),
brepkit_geometry::convert::RecognizedCurve::Circle { .. }
),
_ => false,
}
}
fn closed_edge_segments(curve: &EdgeCurve, deflection: f64) -> usize {
use std::f64::consts::TAU;
match curve {
EdgeCurve::Circle(c) => {
brepkit_math::chord::segments_for_chord_deviation_with_angle(
c.radius(),
TAU,
deflection,
brepkit_math::chord::DEFAULT_ANGULAR_TOL,
0.0,
false,
)
}
EdgeCurve::Ellipse(e) => {
brepkit_math::chord::segments_for_chord_deviation(e.semi_major(), TAU, deflection)
}
EdgeCurve::NurbsCurve(nc) => {
let pts = nc.control_points();
if pts.len() < 2 {
return 8;
}
let (mut min_x, mut min_y, mut min_z) = (f64::MAX, f64::MAX, f64::MAX);
let (mut max_x, mut max_y, mut max_z) = (f64::MIN, f64::MIN, f64::MIN);
for p in pts {
min_x = min_x.min(p.x());
min_y = min_y.min(p.y());
min_z = min_z.min(p.z());
max_x = max_x.max(p.x());
max_y = max_y.max(p.y());
max_z = max_z.max(p.z());
}
let dx = max_x - min_x;
let dy = max_y - min_y;
let dz = max_z - min_z;
let diag = (dx * dx + dy * dy + dz * dz).sqrt();
let radius_est = diag / 2.0;
brepkit_math::chord::segments_for_chord_deviation(radius_est, TAU, deflection)
}
EdgeCurve::Line => 8,
}
}
pub fn split_closed_edge(
topo: &mut Topology,
edge_id: EdgeId,
n: usize,
tol: f64,
) -> Result<Vec<EdgeId>, crate::OperationsError> {
let edge = topo.edge(edge_id)?;
let start_vid = edge.start();
let curve = edge.curve().clone();
let (u0, u1) = match &curve {
EdgeCurve::NurbsCurve(nc) => nc.domain(),
EdgeCurve::Circle(_) => (0.0, std::f64::consts::TAU),
EdgeCurve::Ellipse(_) => (0.0, std::f64::consts::TAU),
EdgeCurve::Line => {
return Ok(vec![edge_id]);
}
};
let evaluate = |u: f64| -> Point3 {
match &curve {
EdgeCurve::NurbsCurve(nc) => nc.evaluate(u),
EdgeCurve::Circle(c) => c.evaluate(u),
EdgeCurve::Ellipse(e) => e.evaluate(u),
EdgeCurve::Line => Point3::new(0.0, 0.0, 0.0),
}
};
let mut new_vids = Vec::with_capacity(n);
new_vids.push(start_vid);
for i in 1..n {
#[allow(clippy::cast_precision_loss)]
let u = u0 + (u1 - u0) * (i as f64) / (n as f64);
let pt = evaluate(u);
let vid = topo.add_vertex(Vertex::new(pt, tol));
new_vids.push(vid);
}
let mut edge_ids = Vec::with_capacity(n);
for i in 0..n {
let v_start = new_vids[i];
let v_end = new_vids[(i + 1) % n];
let v_end_actual = if i == n - 1 { start_vid } else { v_end };
let eid = topo.add_edge(Edge::new(v_start, v_end_actual, EdgeCurve::Line));
edge_ids.push(eid);
}
Ok(edge_ids)
}
fn winding_sample_points(
topo: &Topology,
oriented: &[OrientedEdge],
) -> Result<Vec<Point3>, crate::OperationsError> {
let mut pts = Vec::with_capacity(oriented.len() * 3);
for oe in oriented {
let edge = topo.edge(oe.edge())?;
let p_start = topo.vertex(edge.start())?.point();
let p_end = topo.vertex(edge.end())?.point();
let curve = edge.curve();
let fracs: &[f64] = match curve {
EdgeCurve::Line => &[0.0],
_ => &[0.0, 0.25, 0.5, 0.75],
};
let (d0, d1) = curve.domain_with_endpoints(p_start, p_end);
for &f in fracs {
let f = if oe.is_forward() { f } else { 1.0 - f };
let t = d0 + (d1 - d0) * f;
pts.push(curve.evaluate_with_endpoints(t, p_start, p_end));
}
}
Ok(pts)
}
#[allow(clippy::type_complexity)]
fn extrude_wire_vertices_with(
topo: &mut Topology,
wire_id: WireId,
offset: Vec3,
pass_through_circles: bool,
) -> Result<
(
Vec<VertexId>,
Vec<Point3>,
Vec<OrientedEdge>,
Vec<EdgeId>,
Vec<VertexId>,
Vec<EdgeId>,
Vec<EdgeId>,
),
crate::OperationsError,
> {
let tol = Tolerance::new();
let wire = topo.wire(wire_id)?;
let original_oriented: Vec<_> = wire.edges().to_vec();
let oriented = maybe_split_closed_wire_with(
topo,
&original_oriented,
tol.linear,
DEFAULT_DEFLECTION,
pass_through_circles,
)?;
let mut verts: Vec<VertexId> = Vec::with_capacity(oriented.len());
for oe in &oriented {
let edge = topo.edge(oe.edge())?;
let vid = oe.oriented_start(edge);
verts.push(vid);
}
let n = verts.len();
let positions: Vec<Point3> = verts
.iter()
.map(|&vid| {
topo.vertex(vid)
.map(brepkit_topology::vertex::Vertex::point)
})
.collect::<Result<_, _>>()?;
let top_verts: Vec<VertexId> = positions
.iter()
.map(|p| {
let top_point = *p + offset;
topo.add_vertex(Vertex::new(top_point, tol.linear))
})
.collect();
let edge_ids: Vec<EdgeId> = oriented
.iter()
.map(brepkit_topology::wire::OrientedEdge::edge)
.collect();
let mut top_edge_ids = Vec::with_capacity(n);
for i in 0..n {
let next = (i + 1) % n;
let bottom_curve = topo.edge(edge_ids[i])?.curve().clone();
let mut top_curve = translate_edge_curve(&bottom_curve, offset)?;
if !oriented[i].is_forward() {
top_curve = reverse_edge_curve(&top_curve)?;
}
let top_edge = topo.add_edge(Edge::new(top_verts[i], top_verts[next], top_curve));
top_edge_ids.push(top_edge);
}
let mut vertical_edge_ids = Vec::with_capacity(n);
for i in 0..n {
let vert_edge = topo.add_edge(Edge::new(verts[i], top_verts[i], EdgeCurve::Line));
vertical_edge_ids.push(vert_edge);
}
Ok((
verts,
positions,
oriented,
edge_ids,
top_verts,
top_edge_ids,
vertical_edge_ids,
))
}
fn side_face_surface(
curve: &EdgeCurve,
p0: Point3,
p1: Point3,
curve_start: Point3,
curve_end: Point3,
offset: Vec3,
outer_is_cw: bool,
) -> Result<(FaceSurface, bool), crate::OperationsError> {
match curve {
EdgeCurve::Line => {
let edge_dir = p1 - p0;
let normal = if outer_is_cw {
offset.cross(edge_dir)
} else {
edge_dir.cross(offset)
}
.normalize()
.unwrap_or(Vec3::new(1.0, 0.0, 0.0));
let d = dot_normal_point(normal, p0);
Ok((FaceSurface::Plane { normal, d }, false))
}
EdgeCurve::Circle(circle) => {
let cyl = brepkit_math::surfaces::CylindricalSurface::new(
circle.center(),
offset.normalize().unwrap_or(Vec3::new(0.0, 0.0, 1.0)),
circle.radius(),
)
.map_err(crate::OperationsError::Math)?;
let edge_dir = p1 - p0;
let expected = if outer_is_cw {
offset.cross(edge_dir)
} else {
edge_dir.cross(offset)
};
let to_pt = Vec3::new(
p0.x() - circle.center().x(),
p0.y() - circle.center().y(),
p0.z() - circle.center().z(),
);
let along_axis = cyl.axis() * cyl.axis().dot(to_pt);
let radial = to_pt - along_axis;
let reversed = radial.dot(expected) < 0.0;
Ok((FaceSurface::Cylinder(cyl), reversed))
}
EdgeCurve::NurbsCurve(nc) => {
let tol = brepkit_math::tolerance::Tolerance::new().linear;
if let brepkit_geometry::convert::RecognizedCurve::Circle {
center,
normal: _,
radius,
} = brepkit_geometry::convert::recognize_curve(nc, tol * 100.0)
{
let cyl = brepkit_math::surfaces::CylindricalSurface::new(
center,
offset.normalize().unwrap_or(Vec3::new(0.0, 0.0, 1.0)),
radius,
)
.map_err(crate::OperationsError::Math)?;
let edge_dir = p1 - p0;
let expected = if outer_is_cw {
offset.cross(edge_dir)
} else {
edge_dir.cross(offset)
};
let to_pt = p0 - center;
let along_axis = cyl.axis() * cyl.axis().dot(to_pt);
let radial = to_pt - along_axis;
let reversed = radial.dot(expected) < 0.0;
return Ok((FaceSurface::Cylinder(cyl), reversed));
}
let surface = ruled_nurbs_surface(nc, offset)?;
let reversed = nurbs_needs_reversal(&surface, p0, p1, offset, outer_is_cw);
Ok((FaceSurface::Nurbs(surface), reversed))
}
EdgeCurve::Ellipse(ell) => {
let (t_start, t_end) = curve.domain_with_endpoints(curve_start, curve_end);
let nc =
brepkit_geometry::convert::ellipse_to_nurbs(ell, t_start, t_end).map_err(|e| {
crate::OperationsError::InvalidInput {
reason: format!("ellipse_to_nurbs failed: {e}"),
}
})?;
let surface = ruled_nurbs_surface(&nc, offset)?;
let reversed = nurbs_needs_reversal(&surface, p0, p1, offset, outer_is_cw);
Ok((FaceSurface::Nurbs(surface), reversed))
}
}
}
fn nurbs_needs_reversal(
surface: &NurbsSurface,
p0: Point3,
p1: Point3,
offset: Vec3,
outer_is_cw: bool,
) -> bool {
let (u_lo, u_hi) = surface.domain_u();
let (v_lo, v_hi) = surface.domain_v();
let u_mid = 0.5 * (u_lo + u_hi);
let v_mid = 0.5 * (v_lo + v_hi);
let Ok(native) = surface.normal(u_mid, v_mid) else {
return false;
};
let edge_dir = p1 - p0;
let edge_dir = if edge_dir.length_squared() > 1e-20 {
edge_dir
} else {
let v_next = if v_mid + 0.01 * (v_hi - v_lo) < v_hi {
v_mid + 0.01 * (v_hi - v_lo)
} else {
v_mid - 0.01 * (v_hi - v_lo)
};
let p_mid = surface.evaluate(u_lo, v_mid);
let p_next = surface.evaluate(u_lo, v_next);
p_next - p_mid
};
let expected = if outer_is_cw {
offset.cross(edge_dir)
} else {
edge_dir.cross(offset)
};
if expected.length_squared() < 1e-20 {
return false;
}
native.dot(expected) < 0.0
}
fn ruled_nurbs_surface(
nc: &brepkit_math::nurbs::curve::NurbsCurve,
offset: Vec3,
) -> Result<NurbsSurface, crate::OperationsError> {
let bottom_cps: Vec<Point3> = nc.control_points().to_vec();
let top_cps: Vec<Point3> = bottom_cps.iter().map(|&p| p + offset).collect();
let weights_row: Vec<f64> = nc.weights().to_vec();
NurbsSurface::new(
1, nc.degree(), vec![0.0, 0.0, 1.0, 1.0], nc.knots().to_vec(), vec![bottom_cps, top_cps],
vec![weights_row.clone(), weights_row],
)
.map_err(crate::OperationsError::Math)
}
fn normalize_profile_wire_curves(
topo: &mut Topology,
wire_id: WireId,
tol: f64,
) -> Result<(), crate::OperationsError> {
let edge_ids: Vec<brepkit_topology::edge::EdgeId> = topo
.wire(wire_id)?
.edges()
.iter()
.map(brepkit_topology::wire::OrientedEdge::edge)
.collect();
for eid in edge_ids {
let edge = topo.edge(eid)?;
let EdgeCurve::NurbsCurve(nc) = edge.curve() else {
continue;
};
let s3 = topo.vertex(edge.start())?.point();
let e3 = topo.vertex(edge.end())?.point();
let (d0, d1) = nc.domain();
let a3 = nc.evaluate(d0);
let b3 = nc.evaluate(d1);
let mid3 = nc.evaluate(f64::midpoint(d0, d1));
let fwd = (a3 - s3).length() <= tol && (b3 - e3).length() <= tol;
let rev = (a3 - e3).length() <= tol && (b3 - s3).length() <= tol;
if !fwd && !rev {
continue;
}
let new_curve = match brepkit_geometry::convert::recognize_curve(nc, tol * 100.0) {
brepkit_geometry::convert::RecognizedCurve::Line { .. } if (s3 - e3).length() > tol => {
Some(EdgeCurve::Line)
}
brepkit_geometry::convert::RecognizedCurve::Circle {
center,
normal,
radius,
} => {
let is_closed = (s3 - e3).length() <= tol;
let n = if is_closed {
normal
} else {
let (from, to) = if fwd { (a3, b3) } else { (b3, a3) };
let u = from - center;
let v = to - center;
let m = mid3 - center;
let ang = |w: Vec3| -> f64 {
u.cross(w)
.dot(normal)
.atan2(u.dot(w))
.rem_euclid(std::f64::consts::TAU)
};
let span = ang(v);
let mid_a = ang(m);
if span > 1e-12 && mid_a <= span + 1e-9 {
normal
} else {
-normal
}
};
brepkit_math::curves::Circle3D::new(center, n, radius)
.ok()
.map(EdgeCurve::Circle)
}
_ => None,
};
if let Some(c) = new_curve {
topo.edge_mut(eid)?.set_curve(c);
}
}
Ok(())
}
#[allow(clippy::too_many_lines)]
pub fn extrude(
topo: &mut Topology,
face: FaceId,
direction: Vec3,
distance: f64,
) -> Result<SolidId, crate::OperationsError> {
let tol = Tolerance::new();
if tol.approx_eq(direction.length_squared(), 0.0) {
return Err(crate::OperationsError::InvalidInput {
reason: "extrusion direction is zero-length".into(),
});
}
if tol.approx_eq(distance, 0.0) {
return Err(crate::OperationsError::InvalidInput {
reason: "extrusion distance is zero".into(),
});
}
let face_data = topo.face(face)?;
let mut input_surface = face_data.surface().clone();
let input_wire_id = face_data.outer_wire();
let inner_wire_ids: Vec<WireId> = face_data.inner_wires().to_vec();
normalize_profile_wire_curves(topo, input_wire_id, tol.linear)?;
for &iw in &inner_wire_ids {
normalize_profile_wire_curves(topo, iw, tol.linear)?;
}
let offset = Vec3::new(
direction.x() * distance,
direction.y() * distance,
direction.z() * distance,
);
let (
input_verts,
input_positions,
input_oriented,
input_edge_ids,
_top_verts,
top_edge_ids,
vertical_edge_ids,
) = extrude_wire_vertices_with(
topo,
input_wire_id,
offset,
true,
)?;
let n = input_verts.len();
let outer_winding_pts = winding_sample_points(topo, &input_oriented)?;
let outer_is_cw = crate::winding::is_cw_winding(&outer_winding_pts, &offset);
if let FaceSurface::Plane {
ref mut normal,
ref mut d,
} = input_surface
&& normal.dot(offset) < 0.0
{
*normal = -*normal;
*d = -*d;
}
let mut all_faces = Vec::with_capacity(n + 2 + inner_wire_ids.len() * 4);
let reversed_bottom_edges: Vec<OrientedEdge> = input_oriented
.iter()
.rev()
.map(|oe| OrientedEdge::new(oe.edge(), !oe.is_forward()))
.collect();
let bottom_wire =
Wire::new(reversed_bottom_edges, true).map_err(crate::OperationsError::Topology)?;
let bottom_wire_id = topo.add_wire(bottom_wire);
let mut bottom_inner_wire_ids = Vec::with_capacity(inner_wire_ids.len());
let mut top_inner_wire_ids = Vec::with_capacity(inner_wire_ids.len());
let mut inner_wire_data: Vec<InnerWireData> = Vec::new();
for &iw_id in &inner_wire_ids {
let (
_iw_verts,
iw_positions,
iw_oriented,
iw_edge_ids,
_iw_top_verts,
iw_top_edge_ids,
iw_vert_edge_ids,
) = extrude_wire_vertices_with(
topo,
iw_id,
offset,
inner_wire_is_single_circle(topo, iw_id),
)?;
let reversed_inner_edges: Vec<OrientedEdge> = iw_oriented
.iter()
.rev()
.map(|oe| OrientedEdge::new(oe.edge(), !oe.is_forward()))
.collect();
let bottom_inner_wire =
Wire::new(reversed_inner_edges, true).map_err(crate::OperationsError::Topology)?;
bottom_inner_wire_ids.push(topo.add_wire(bottom_inner_wire));
let top_inner_edges: Vec<OrientedEdge> = iw_top_edge_ids
.iter()
.map(|&eid| OrientedEdge::new(eid, true))
.collect();
let top_inner_wire =
Wire::new(top_inner_edges, true).map_err(crate::OperationsError::Topology)?;
top_inner_wire_ids.push(topo.add_wire(top_inner_wire));
inner_wire_data.push(InnerWireData {
positions: iw_positions,
oriented: iw_oriented,
edge_ids: iw_edge_ids,
top_edge_ids: iw_top_edge_ids,
vertical_edge_ids: iw_vert_edge_ids,
});
}
let bottom_surface = match &input_surface {
FaceSurface::Plane { normal, .. } => {
let bottom_normal = -*normal;
let bottom_d = dot_normal_point(bottom_normal, input_positions[0]);
FaceSurface::Plane {
normal: bottom_normal,
d: bottom_d,
}
}
FaceSurface::Nurbs(nurbs) => FaceSurface::Nurbs(nurbs.clone()),
other => other.clone(),
};
let bottom_face = topo.add_face(Face::new(
bottom_wire_id,
bottom_inner_wire_ids,
bottom_surface,
));
all_faces.push(bottom_face);
for i in 0..n {
let next = (i + 1) % n;
let p0 = input_positions[i];
let p1 = input_positions[next];
let (edge_curve, e_start, e_end) = {
let e = topo.edge(input_edge_ids[i])?;
(e.curve().clone(), e.start(), e.end())
};
let cs = topo.vertex(e_start)?.point();
let ce = topo.vertex(e_end)?.point();
let (surface, reversed) =
side_face_surface(&edge_curve, p0, p1, cs, ce, offset, outer_is_cw)?;
let side_wire = if reversed {
Wire::new(
vec![
OrientedEdge::new(vertical_edge_ids[i], true),
OrientedEdge::new(top_edge_ids[i], true),
OrientedEdge::new(vertical_edge_ids[next], false),
OrientedEdge::new(input_edge_ids[i], !input_oriented[i].is_forward()),
],
true,
)
} else {
Wire::new(
vec![
OrientedEdge::new(input_edge_ids[i], input_oriented[i].is_forward()),
OrientedEdge::new(vertical_edge_ids[next], true),
OrientedEdge::new(top_edge_ids[i], false),
OrientedEdge::new(vertical_edge_ids[i], false),
],
true,
)
}
.map_err(crate::OperationsError::Topology)?;
let side_wire_id = topo.add_wire(side_wire);
let side_face = if reversed {
topo.add_face(Face::new_reversed(side_wire_id, vec![], surface))
} else {
topo.add_face(Face::new(side_wire_id, vec![], surface))
};
all_faces.push(side_face);
}
for iwd in &inner_wire_data {
let iw_n = iwd.positions.len();
let is_cw = crate::winding::inner_wire_is_cw(&iwd.positions, &offset);
for i in 0..iw_n {
let next = (i + 1) % iw_n;
let p0 = iwd.positions[i];
let p1 = iwd.positions[next];
let (edge_curve, e_start, e_end) = {
let e = topo.edge(iwd.edge_ids[i])?;
(e.curve().clone(), e.start(), e.end())
};
let cs = topo.vertex(e_start)?.point();
let ce = topo.vertex(e_end)?.point();
let inner_is_cw = !is_cw;
let (surface, reversed) =
side_face_surface(&edge_curve, p0, p1, cs, ce, offset, inner_is_cw)?;
let reversed =
reversed || (e_start == e_end && matches!(surface, FaceSurface::Cylinder(_)));
let side_edges = if reversed {
vec![
OrientedEdge::new(iwd.vertical_edge_ids[i], true),
OrientedEdge::new(iwd.top_edge_ids[i], true),
OrientedEdge::new(iwd.vertical_edge_ids[next], false),
OrientedEdge::new(iwd.edge_ids[i], !iwd.oriented[i].is_forward()),
]
} else {
vec![
OrientedEdge::new(iwd.edge_ids[i], iwd.oriented[i].is_forward()),
OrientedEdge::new(iwd.vertical_edge_ids[next], true),
OrientedEdge::new(iwd.top_edge_ids[i], false),
OrientedEdge::new(iwd.vertical_edge_ids[i], false),
]
};
let side_wire =
Wire::new(side_edges, true).map_err(crate::OperationsError::Topology)?;
let side_wire_id = topo.add_wire(side_wire);
let side_face = if reversed {
topo.add_face(Face::new_reversed(side_wire_id, vec![], surface))
} else {
topo.add_face(Face::new(side_wire_id, vec![], surface))
};
all_faces.push(side_face);
}
}
let top_wire = Wire::new(
top_edge_ids
.iter()
.map(|&eid| OrientedEdge::new(eid, true))
.collect(),
true,
)
.map_err(crate::OperationsError::Topology)?;
let top_wire_id = topo.add_wire(top_wire);
let top_surface = match &input_surface {
FaceSurface::Plane { normal, .. } => {
let top_d = dot_normal_point(*normal, input_positions[0] + offset);
FaceSurface::Plane {
normal: *normal,
d: top_d,
}
}
FaceSurface::Nurbs(nurbs) => {
let translated_cps: Vec<Vec<Point3>> = nurbs
.control_points()
.iter()
.map(|row| row.iter().map(|&p| p + offset).collect())
.collect();
let translated_surface = brepkit_math::nurbs::surface::NurbsSurface::new(
nurbs.degree_u(),
nurbs.degree_v(),
nurbs.knots_u().to_vec(),
nurbs.knots_v().to_vec(),
translated_cps,
nurbs.weights().to_vec(),
)
.map_err(crate::OperationsError::Math)?;
FaceSurface::Nurbs(translated_surface)
}
FaceSurface::Cylinder(cyl) => FaceSurface::Cylinder(cyl.translated(offset)),
FaceSurface::Cone(cone) => FaceSurface::Cone(cone.translated(offset)),
FaceSurface::Sphere(sph) => FaceSurface::Sphere(sph.translated(offset)),
FaceSurface::Torus(tor) => FaceSurface::Torus(tor.translated(offset)),
};
let top_face = topo.add_face(Face::new(top_wire_id, top_inner_wire_ids, top_surface));
all_faces.push(top_face);
let shell = Shell::new(all_faces).map_err(crate::OperationsError::Topology)?;
let shell_id = topo.add_shell(shell);
let solid = topo.add_solid(Solid::new(shell_id, vec![]));
Ok(solid)
}
fn translate_edge_curve(
curve: &EdgeCurve,
offset: Vec3,
) -> Result<EdgeCurve, crate::OperationsError> {
Ok(match curve {
EdgeCurve::Line => EdgeCurve::Line,
EdgeCurve::Circle(c) => {
let new_center = c.center() + offset;
EdgeCurve::Circle(
brepkit_math::curves::Circle3D::with_axes(
new_center,
c.normal(),
c.radius(),
c.u_axis(),
c.v_axis(),
)
.map_err(crate::OperationsError::Math)?,
)
}
EdgeCurve::Ellipse(e) => {
let new_center = e.center() + offset;
EdgeCurve::Ellipse(
brepkit_math::curves::Ellipse3D::with_axes(
new_center,
e.normal(),
e.semi_major(),
e.semi_minor(),
e.u_axis(),
e.v_axis(),
)
.map_err(crate::OperationsError::Math)?,
)
}
EdgeCurve::NurbsCurve(nc) => {
let translated_cps: Vec<Point3> =
nc.control_points().iter().map(|&p| p + offset).collect();
EdgeCurve::NurbsCurve(
brepkit_math::nurbs::curve::NurbsCurve::new(
nc.degree(),
nc.knots().to_vec(),
translated_cps,
nc.weights().to_vec(),
)
.map_err(crate::OperationsError::Math)?,
)
}
})
}
fn reverse_edge_curve(curve: &EdgeCurve) -> Result<EdgeCurve, crate::OperationsError> {
Ok(match curve {
EdgeCurve::Line => EdgeCurve::Line,
EdgeCurve::Circle(c) => EdgeCurve::Circle(
brepkit_math::curves::Circle3D::with_axes(
c.center(),
-c.normal(),
c.radius(),
c.u_axis(),
-c.v_axis(),
)
.map_err(crate::OperationsError::Math)?,
),
EdgeCurve::Ellipse(e) => EdgeCurve::Ellipse(
brepkit_math::curves::Ellipse3D::with_axes(
e.center(),
-e.normal(),
e.semi_major(),
e.semi_minor(),
e.u_axis(),
-e.v_axis(),
)
.map_err(crate::OperationsError::Math)?,
),
EdgeCurve::NurbsCurve(nc) => {
let knots = nc.knots();
let span = knots[0] + knots[knots.len() - 1];
let new_knots: Vec<f64> = knots.iter().rev().map(|&k| span - k).collect();
let new_cps: Vec<Point3> = nc.control_points().iter().rev().copied().collect();
let new_weights: Vec<f64> = nc.weights().iter().rev().copied().collect();
EdgeCurve::NurbsCurve(
brepkit_math::nurbs::curve::NurbsCurve::new(
nc.degree(),
new_knots,
new_cps,
new_weights,
)
.map_err(crate::OperationsError::Math)?,
)
}
})
}
#[cfg(test)]
mod tests;