axiolid-construct 0.3.0

Solid generation: profiles, lofts, sweeps, revolutions and half-space clipping
Documentation
use axiolid_brep::{Curve2Id, ExactBRep, ExactBRepBuilder, SurfaceId};
use axiolid_contracts::{GeomError, GeomResult};
use axiolid_core::{Interval, Point2, Point3};
use axiolid_curve::{Curve2, Curve3, Line2, Line3};
use axiolid_surface::Surface;
use axiolid_topology::{
    Edge, EdgeId, EdgeUse, Face, FaceBound, FaceId, Loop, LoopId, Orientation, Vertex, VertexId,
};

use crate::BACKEND_ID;

pub(super) struct ArrangementBuilder {
    inner: ExactBRepBuilder,
}

impl ArrangementBuilder {
    pub fn new() -> GeomResult<Self> {
        let mut inner = ExactBRepBuilder::default();
        inner
            .try_reserve(11, 13, 2, 11, 12)
            .map_err(|_| allocation_error("trimmed B-rep support allocation"))?;
        inner
            .topology_mut()
            .try_reserve(10, 11, 3, 3, 0, 0)
            .map_err(|_| allocation_error("trimmed B-rep topology allocation"))?;
        Ok(Self { inner })
    }

    pub fn add_surface(&mut self, surface: Surface) -> SurfaceId {
        self.inner.add_surface(surface)
    }

    pub fn add_vertex(&mut self, position: Point3) -> VertexId {
        self.inner.topology_mut().add_vertex(Vertex { position })
    }

    pub fn add_line_edge(
        &mut self,
        start: VertexId,
        end: VertexId,
        start_point: Point3,
        end_point: Point3,
    ) -> GeomResult<EdgeId> {
        let direction = end_point - start_point;
        if !start_point.is_finite()
            || !end_point.is_finite()
            || !direction.is_finite()
            || direction.length_squared() == 0.0
        {
            return Err(GeomError::Degenerate(
                "trimmed B-rep edge has a non-finite or zero-length carrier".into(),
            ));
        }
        let curve = self.inner.add_curve3(Curve3::Line(Line3 {
            origin: start_point,
            direction,
        }));
        let edge = self.inner.topology_mut().add_edge(Edge {
            start,
            end,
            curve: Some(curve),
        });
        self.inner.set_edge_interval(edge, Interval::UNIT);
        Ok(edge)
    }

    pub fn add_pcurve(&mut self, start: Point2, end: Point2) -> GeomResult<Curve2Id> {
        let direction = end - start;
        if !start.is_finite()
            || !end.is_finite()
            || !direction.is_finite()
            || direction.length_squared() == 0.0
        {
            return Err(GeomError::Degenerate(
                "trimmed B-rep pcurve has a non-finite or zero-length carrier".into(),
            ));
        }
        Ok(self.inner.add_curve2(Curve2::Line(Line2 {
            origin: start,
            direction,
        })))
    }

    pub fn add_loop(&mut self, edges: Vec<EdgeUse<Curve2Id>>) -> GeomResult<LoopId> {
        if edges.len() < 3 {
            return Err(GeomError::Degenerate(
                "trimmed B-rep loop needs at least three edge uses".into(),
            ));
        }
        let use_count = edges.len();
        let loop_id = self.inner.topology_mut().add_loop(Loop { edges });
        for use_index in 0..use_count {
            self.inner
                .set_pcurve_interval(loop_id, use_index, Interval::UNIT);
        }
        Ok(loop_id)
    }

    pub fn add_face(&mut self, loop_id: LoopId, surface: SurfaceId) -> GeomResult<FaceId> {
        let mut bounds = Vec::new();
        bounds
            .try_reserve_exact(1)
            .map_err(|_| allocation_error("trimmed B-rep face bound allocation"))?;
        bounds.push(FaceBound {
            loop_id,
            orientation: Orientation::Forward,
            outer: true,
        });
        Ok(self.inner.topology_mut().add_face(Face {
            surface: Some(surface),
            bounds,
            orientation: Orientation::Forward,
        }))
    }

    pub fn finish(self) -> GeomResult<ExactBRep> {
        self.inner
            .finish()
            .map_err(|error| GeomError::BackendContractViolation {
                backend: BACKEND_ID,
                detail: format!("trimmed B-rep assembly failed: {error:?}"),
            })
    }
}

pub(super) fn edge_use(
    edge: EdgeId,
    orientation: Orientation,
    pcurve: Curve2Id,
) -> EdgeUse<Curve2Id> {
    EdgeUse {
        edge,
        orientation,
        pcurve: Some(pcurve),
    }
}

pub(super) fn allocation_error(resource: &'static str) -> GeomError {
    GeomError::BudgetExceeded { resource }
}