Skip to content

Commit

Permalink
Iterator and other API quality-of-life improvements (#83)
Browse files Browse the repository at this point in the history
* Clean up the function signatures which accept an Iterator

* Make farthest() return a plain Face, add try_farthest() for Option<Face>

* Add explanatory comment for all the AsRef impls
  • Loading branch information
bschwind authored Jul 16, 2023
1 parent aa54ccd commit 769cf39
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 42 deletions.
17 changes: 8 additions & 9 deletions crates/opencascade/examples/chamfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,25 @@ pub fn main() {
top.translate(dvec3(0.0, 0.0, 10.0));
top.chamfer(1.0);

let chamfered_box = Solid::loft([&base, &top].into_iter()).to_shape();
let chamfered_box = Solid::loft([&base, &top]).to_shape();

// insert the workplane into the chamfered box area so union returns edges
// Insert the workplane into the chamfered box area so union returns edges
let handle = Workplane::xy().translated(dvec3(0.0, 0.0, 0.1)).rect(5.0, 5.0);
let handle_face = Face::from_wire(&handle);

let handle_body = handle_face.extrude(dvec3(0.0, 0.0, -10.1));
let (mut chamfered_shape, fuse_edges) = chamfered_box.union(&handle_body);
chamfered_shape.chamfer_edges(0.5, &fuse_edges);

// chamfer the top of the protrusion
let top_edges: Vec<_> = chamfered_shape
// Chamfer the top of the protrusion
let top_edges = chamfered_shape
.faces()
.farthest(Direction::NegZ) // Get the face whose center of mass is the farthest in the negative Z direction
.expect("Should have a face on the bottom of the handle")
.edges() // Get all the edges of this face
.collect();
chamfered_shape.chamfer_edges(1.0, &top_edges);
.edges(); // Get all the edges of this face

// can also just chamfer the whole shape
chamfered_shape.chamfer_edges(1.0, top_edges);

// Can also just chamfer the whole shape with:
// chamfered_shape.chamfer(0.5);

chamfered_shape.write_stl("chamfer.stl").unwrap();
Expand Down
13 changes: 5 additions & 8 deletions crates/opencascade/examples/keycap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ pub fn main() {
top_wire.translate(dvec3(-tx / 2.0, -ty / 2.0, 0.0));
top_wire.transform(dvec3(0.0, 0.0, height), dvec3(1.0, 0.0, 0.0), angle);

let mut keycap = Solid::loft([&base, &mid, &top_wire].into_iter());
let mut keycap = Solid::loft([&base, &mid, &top_wire]);

let scoop = if convex {
let scoop = Workplane::yz()
Expand Down Expand Up @@ -95,7 +95,7 @@ pub fn main() {
.line_to(-by / 2.0, height)
.close();

Solid::loft([&scoop_right, &scoop_mid, &scoop_left].into_iter())
Solid::loft([&scoop_right, &scoop_mid, &scoop_left])
};

let (mut keycap, edges) = keycap.subtract(&scoop);
Expand All @@ -114,15 +114,14 @@ pub fn main() {
)
.rect(tx - thickness * 2.0 + 0.5, ty - thickness * 2.0 + 0.5);

let shell = Solid::loft([&shell_bottom, &shell_mid, &shell_top].into_iter());
let shell = Solid::loft([&shell_bottom, &shell_mid, &shell_top]);

let (mut keycap, _edges) = keycap.subtract(&shell);

let temp_face = shell
.to_shape()
.faces()
.farthest(Direction::PosZ)
.expect("shell should have a top face")
.workplane()
.rect(bx * 2.0, by * 2.0)
.to_face();
Expand Down Expand Up @@ -180,8 +179,7 @@ pub fn main() {
}
}

let bottom_face =
keycap.faces().farthest(Direction::NegZ).expect("keycap should have a bottom face");
let bottom_face = keycap.faces().farthest(Direction::NegZ);

let bottom_workplane = bottom_face.workplane().translated(dvec3(0.0, 0.0, -4.5));

Expand Down Expand Up @@ -213,8 +211,7 @@ pub fn main() {
keycap.clean();

for (x, y) in &stem_points {
let bottom_face =
keycap.faces().farthest(Direction::NegZ).expect("keycap should have a bottom face");
let bottom_face = keycap.faces().farthest(Direction::NegZ);
let workplane = bottom_face.workplane().translated(dvec3(0.0, 0.0, -0.6));

let circle = workplane.circle(*x, *y, 2.75).to_face();
Expand Down
6 changes: 2 additions & 4 deletions crates/opencascade/examples/rounded_chamfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@ pub fn main() {
let shape = base.to_face().extrude(dvec3(0.0, 0.0, 3.0));
let mut shape = shape.to_shape();

let top_edges: Vec<_> =
shape.faces().farthest(Direction::PosZ).expect("Should have a top face").edges().collect();

shape.chamfer_edges(0.7, &top_edges);
let top_edges = shape.faces().farthest(Direction::PosZ).edges();
shape.chamfer_edges(0.7, top_edges);

shape.write_stl("rounded_chamfer.stl").unwrap();
}
102 changes: 88 additions & 14 deletions crates/opencascade/src/primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,22 @@ pub struct Vertex {
_inner: UniquePtr<ffi::TopoDS_Vertex>,
}

// You'll see several of these `impl AsRef` blocks for the various primitive
// geometry types. This is for functions which take an Iterator of primitives
// which are either owned or borrowed values. The general pattern looks like this:
//
// pub fn do_something_with_edges<T: AsRef<Edge>>(edges: impl IntoIterator<Item = T>) {
// for edge in edges.into_iter() {
// let edge_ref = edge.as_ref();
// // Do something with edge_ref
// }
// }
impl AsRef<Vertex> for Vertex {
fn as_ref(&self) -> &Vertex {
self
}
}

impl Vertex {
pub fn new(point: DVec3) -> Self {
let mut make_vertex = ffi::BRepBuilderAPI_MakeVertex_gp_Pnt(&make_point(point));
Expand All @@ -85,6 +101,12 @@ pub struct Edge {
inner: UniquePtr<ffi::TopoDS_Edge>,
}

impl AsRef<Edge> for Edge {
fn as_ref(&self) -> &Edge {
self
}
}

impl Edge {
pub fn segment(p1: DVec3, p2: DVec3) -> Self {
let mut make_edge =
Expand Down Expand Up @@ -182,6 +204,12 @@ pub struct Wire {
inner: UniquePtr<ffi::TopoDS_Wire>,
}

impl AsRef<Wire> for Wire {
fn as_ref(&self) -> &Wire {
self
}
}

impl Wire {
pub fn from_edges<'a>(edges: impl IntoIterator<Item = &'a Edge>) -> Self {
let mut make_wire = ffi::BRepBuilderAPI_MakeWire_ctor();
Expand Down Expand Up @@ -242,7 +270,7 @@ impl Wire {
let bottom = Edge::segment(p3, p4);
let left = Edge::segment(p4, p1);

Self::from_edges([&top, &right, &bottom, &left].into_iter())
Self::from_edges([&top, &right, &bottom, &left])
}

pub fn fillet(&mut self, radius: f64) {
Expand Down Expand Up @@ -315,6 +343,12 @@ pub struct Face {
inner: UniquePtr<ffi::TopoDS_Face>,
}

impl AsRef<Face> for Face {
fn as_ref(&self) -> &Face {
self
}
}

impl Face {
pub fn from_wire(wire: &Wire) -> Self {
let only_plane = false;
Expand Down Expand Up @@ -545,6 +579,12 @@ pub struct CompoundFace {
inner: UniquePtr<ffi::TopoDS_Compound>,
}

impl AsRef<CompoundFace> for CompoundFace {
fn as_ref(&self) -> &CompoundFace {
self
}
}

impl CompoundFace {
pub fn clean(&mut self) -> Self {
let inner = ffi::cast_compound_to_shape(&self.inner);
Expand Down Expand Up @@ -615,10 +655,22 @@ pub struct Shell {
_inner: UniquePtr<ffi::TopoDS_Shell>,
}

impl AsRef<Shell> for Shell {
fn as_ref(&self) -> &Shell {
self
}
}

pub struct Solid {
inner: UniquePtr<ffi::TopoDS_Solid>,
}

impl AsRef<Solid> for Solid {
fn as_ref(&self) -> &Solid {
self
}
}

impl Solid {
pub fn to_shape(self) -> Shape {
let inner_shape = ffi::cast_solid_to_shape(&self.inner);
Expand All @@ -645,13 +697,12 @@ impl Solid {
Compound { inner }
}

// TODO(bschwind) - Accept IntoIter instead of Iterator
pub fn loft<'a>(wires: impl Iterator<Item = &'a Wire>) -> Self {
pub fn loft<T: AsRef<Wire>>(wires: impl IntoIterator<Item = T>) -> Self {
let is_solid = true;
let mut make_loft = ffi::BRepOffsetAPI_ThruSections_ctor(is_solid);

for wire in wires {
make_loft.pin_mut().AddWire(&wire.inner);
for wire in wires.into_iter() {
make_loft.pin_mut().AddWire(&wire.as_ref().inner);
}

// Set to CheckCompatibility to `true` to avoid twisted results.
Expand Down Expand Up @@ -732,6 +783,12 @@ pub struct Compound {
inner: UniquePtr<ffi::TopoDS_Compound>,
}

impl AsRef<Compound> for Compound {
fn as_ref(&self) -> &Compound {
self
}
}

impl Compound {
pub fn clean(&mut self) -> Shape {
let inner = ffi::cast_compound_to_shape(&self.inner);
Expand All @@ -755,6 +812,12 @@ pub struct Shape {
pub(crate) inner: UniquePtr<ffi::TopoDS_Shape>,
}

impl AsRef<Shape> for Shape {
fn as_ref(&self) -> &Shape {
self
}
}

impl Shape {
pub fn fillet_edge(&mut self, radius: f64, edge: &Edge) {
let mut make_fillet = ffi::BRepFilletAPI_MakeFillet_ctor(&self.inner);
Expand All @@ -774,23 +837,31 @@ impl Shape {
self.inner = ffi::TopoDS_Shape_to_owned(chamfered_shape);
}

pub fn fillet_edges<'a>(&mut self, radius: f64, edges: impl IntoIterator<Item = &'a Edge>) {
pub fn fillet_edges<T: AsRef<Edge>>(
&mut self,
radius: f64,
edges: impl IntoIterator<Item = T>,
) {
let mut make_fillet = ffi::BRepFilletAPI_MakeFillet_ctor(&self.inner);

for edge in edges.into_iter() {
make_fillet.pin_mut().add_edge(radius, &edge.inner);
make_fillet.pin_mut().add_edge(radius, &edge.as_ref().inner);
}

let filleted_shape = make_fillet.pin_mut().Shape();

self.inner = ffi::TopoDS_Shape_to_owned(filleted_shape);
}

pub fn chamfer_edges<'a>(&mut self, distance: f64, edges: impl IntoIterator<Item = &'a Edge>) {
pub fn chamfer_edges<T: AsRef<Edge>>(
&mut self,
distance: f64,
edges: impl IntoIterator<Item = T>,
) {
let mut make_chamfer = ffi::BRepFilletAPI_MakeChamfer_ctor(&self.inner);

for edge in edges.into_iter() {
make_chamfer.pin_mut().add_edge(distance, &edge.inner);
make_chamfer.pin_mut().add_edge(distance, &edge.as_ref().inner);
}

let chamfered_shape = make_chamfer.pin_mut().Shape();
Expand All @@ -800,14 +871,12 @@ impl Shape {

/// Performs fillet of `radius` on all edges of the shape
pub fn fillet(&mut self, radius: f64) {
let all_edges = self.edges().collect::<Vec<_>>();
self.fillet_edges(radius, &all_edges);
self.fillet_edges(radius, self.edges());
}

/// Performs chamfer of `distance` on all edges of the shape
pub fn chamfer(&mut self, distance: f64) {
let all_edges = self.edges().collect::<Vec<_>>();
self.chamfer_edges(distance, &all_edges);
self.chamfer_edges(distance, self.edges());
}

pub fn subtract(&mut self, other: &Solid) -> (Shape, Vec<Edge>) {
Expand Down Expand Up @@ -958,6 +1027,7 @@ impl Shape {
FaceIterator { explorer }
}

// TODO(bschwind) - Convert the return type to an iterator.
pub fn faces_along_ray(&self, ray_start: DVec3, ray_dir: DVec3) -> Vec<(Face, DVec3)> {
let mut intersector = ffi::BRepIntCurveSurface_Inter_ctor();
let tolerance = 0.0001;
Expand Down Expand Up @@ -1156,7 +1226,11 @@ impl Direction {
}

impl FaceIterator {
pub fn farthest(self, direction: Direction) -> Option<Face> {
pub fn farthest(self, direction: Direction) -> Face {
self.try_farthest(direction).unwrap()
}

pub fn try_farthest(self, direction: Direction) -> Option<Face> {
let normalized_dir = direction.normalized_vec();

Iterator::max_by(self, |face_1, face_2| {
Expand Down
8 changes: 4 additions & 4 deletions crates/opencascade/src/workplane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,15 +192,15 @@ impl Workplane {
let bottom = Edge::segment(p3, p4);
let left = Edge::segment(p4, p1);

Wire::from_edges([&top, &right, &bottom, &left].into_iter())
Wire::from_edges([&top, &right, &bottom, &left])
}

pub fn circle(&self, x: f64, y: f64, radius: f64) -> Wire {
let center = self.to_world_pos(dvec3(x, y, 0.0));

let circle = Edge::circle(center, self.normal(), radius);

Wire::from_edges([&circle].into_iter())
Wire::from_edges([&circle])
}

pub fn sketch(&self) -> Sketch {
Expand Down Expand Up @@ -264,14 +264,14 @@ impl Sketch {
}

pub fn wire(self) -> Wire {
Wire::from_edges(self.edges.iter())
Wire::from_edges(&self.edges)
}

pub fn close(mut self) -> Wire {
let start_point = self.first_point.unwrap();

let new_edge = Edge::segment(self.cursor, start_point);
self.add_edge(new_edge);
Wire::from_edges(self.edges.iter())
Wire::from_edges(&self.edges)
}
}
6 changes: 3 additions & 3 deletions crates/viewer/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ fn keycap() -> Shape {
top_wire.translate(dvec3(-tx / 2.0, -ty / 2.0, 0.0));
top_wire.transform(dvec3(0.0, 0.0, height), dvec3(1.0, 0.0, 0.0), angle);

let mut keycap = Solid::loft([&base, &mid, &top_wire].into_iter());
let mut keycap = Solid::loft([&base, &mid, &top_wire]);

let scoop = if convex {
let scoop = Workplane::yz()
Expand Down Expand Up @@ -316,7 +316,7 @@ fn keycap() -> Shape {
.line_to(-by / 2.0, height)
.close();

Solid::loft([&scoop_right, &scoop_mid, &scoop_left].into_iter())
Solid::loft([&scoop_right, &scoop_mid, &scoop_left])
};

let (mut keycap, edges) = keycap.subtract(&scoop);
Expand All @@ -332,7 +332,7 @@ fn keycap() -> Shape {
.transformed(dvec3(0.0, 0.0, height - height / 4.0 - 4.5), dvec3(angle, 0.0, 0.0))
.rect(tx - thickness * 2.0 + 0.5, ty - thickness * 2.0 + 0.5);

let shell = Solid::loft([&shell_bottom, &shell_mid, &shell_top].into_iter());
let shell = Solid::loft([&shell_bottom, &shell_mid, &shell_top]);

let (keycap, _edges) = keycap.subtract(&shell);

Expand Down

0 comments on commit 769cf39

Please sign in to comment.