runmat-meshing-surface 0.6.0

Surface meshing stage for RunMat meshing
Documentation
use runmat_meshing_curve::{CurveValidationError, CurveValidationReport};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct SurfaceDiscretizationOptions {
    pub preserve_source_faces: bool,
    pub centroid_subdivision: bool,
    pub max_curve_segments_per_edge: usize,
}

impl Default for SurfaceDiscretizationOptions {
    fn default() -> Self {
        Self {
            preserve_source_faces: true,
            centroid_subdivision: false,
            max_curve_segments_per_edge: 256,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SurfaceNode {
    pub node_id: u32,
    pub source_vertex_id: u32,
    pub coordinates_m: [f64; 3],
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SurfaceElement {
    pub element_id: u32,
    pub source_face_id: u32,
    #[serde(default)]
    pub cad_face_id: Option<String>,
    pub source_edge_ids: [u32; 3],
    pub node_ids: [u32; 3],
    #[serde(default)]
    pub parametric_node_uv: [[f64; 2]; 3],
    #[serde(default)]
    pub max_projection_error_m: f64,
    #[serde(default)]
    pub region_ids: Vec<String>,
    #[serde(default)]
    pub material_region_ids: Vec<String>,
    pub area_m2: f64,
    pub unit_normal: [f64; 3],
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SurfaceDiscretization {
    pub nodes: Vec<SurfaceNode>,
    pub elements: Vec<SurfaceElement>,
    #[serde(default)]
    pub curve_boundary_validation: Option<CurveValidationReport>,
    #[serde(default)]
    pub loop_coverage: Option<SurfaceLoopCoverageReport>,
    #[serde(default)]
    pub cad_curve_boundary_provenance: Option<SurfaceCadCurveBoundaryProvenanceReport>,
    #[serde(default)]
    pub exact_cad_sample_node_count: usize,
    #[serde(default)]
    pub rejected_exact_cad_sample_count: usize,
    #[serde(default)]
    pub sizing_sample_node_count: usize,
    #[serde(default)]
    pub rejected_sizing_sample_count: usize,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SurfaceLoopCoverageReport {
    pub source_face_count: usize,
    pub recovered_face_count: usize,
    pub boundary_loop_count: usize,
    #[serde(default)]
    pub hole_loop_count: usize,
    #[serde(default)]
    pub boundary_node_count: usize,
    pub recovered_source_edge_count: usize,
    pub boundary_segment_count: usize,
    pub max_loops_per_face: usize,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SurfaceCadCurveBoundaryProvenanceReport {
    pub recovered_source_edge_count: usize,
    pub boundary_segment_count: usize,
    pub imported_curve_edge_count: usize,
    pub evaluator_curve_edge_count: usize,
    pub evaluator_sample_count: usize,
    pub live_query_edge_count: usize,
    pub live_query_sample_count: usize,
    pub rejected_evaluator_sample_count: usize,
    pub curvature_sized_edge_count: usize,
    pub curvature_sample_count: usize,
    pub edges: Vec<SurfaceCadCurveBoundaryEdgeProvenance>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SurfaceCadCurveBoundaryEdgeProvenance {
    pub source_edge_id: u32,
    pub cad_edge_id: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub imported_curve_id: Option<u64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub evaluator_id: Option<String>,
    #[serde(default)]
    pub evaluator_supports_point_evaluation: bool,
    #[serde(default)]
    pub evaluator_supports_projection: bool,
    #[serde(default)]
    pub evaluator_supports_tangent: bool,
    #[serde(default)]
    pub evaluator_supports_curvature: bool,
    #[serde(default)]
    pub evaluator_sample_count: usize,
    #[serde(default)]
    pub live_query_backed: bool,
    #[serde(default)]
    pub live_query_sample_count: usize,
    #[serde(default)]
    pub rejected_evaluator_sample_count: usize,
    #[serde(default)]
    pub curvature_sample_count: usize,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub curvature_limited_target_size_m: Option<f64>,
    pub boundary_segment_count: usize,
}

#[derive(Debug, Clone, PartialEq)]
pub enum SurfaceDiscretizationError {
    InvalidCurveBoundary(CurveValidationError),
    MissingFaceVertex {
        face_id: u32,
        node_id: u32,
    },
    MissingFaceEdge {
        face_id: u32,
        edge_id: u32,
    },
    MissingCadFaceFrame {
        source_face_id: u32,
    },
    CadFaceWithoutSourceFaces {
        cad_face_id: String,
    },
    CadFaceWithoutLoops {
        cad_face_id: String,
    },
    MissingCadFaceLoop {
        cad_face_id: String,
        loop_id: String,
    },
    CadLoopFaceMismatch {
        cad_face_id: String,
        loop_id: String,
        actual_face_id: String,
    },
    InvalidCadLoopEdgeId {
        cad_face_id: String,
        loop_edge_id: String,
    },
    MissingCurveEdge {
        source_edge_id: u32,
    },
    MissingCadCurveProvenance {
        source_edge_id: u32,
    },
    InvalidFaceEdgeOrientation {
        face_id: u32,
        edge_id: u32,
    },
    CadProjectionOutsideFaceDomain {
        face_id: u32,
        node_id: u32,
    },
    EmptyFaceLoop {
        face_id: u32,
    },
    InvalidFaceLoopTopology {
        face_id: u32,
        node_id: u32,
        incident_segment_count: usize,
    },
}

impl std::fmt::Display for SurfaceDiscretizationError {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::InvalidCurveBoundary(source) => {
                write!(formatter, "invalid recovered curve boundary: {source}")
            }
            Self::MissingFaceVertex { face_id, node_id } => write!(
                formatter,
                "source face {face_id} references missing topology vertex {node_id}"
            ),
            Self::MissingFaceEdge { face_id, edge_id } => write!(
                formatter,
                "source face {face_id} references missing topology edge {edge_id}"
            ),
            Self::MissingCadFaceFrame { source_face_id } => write!(
                formatter,
                "source face {source_face_id} does not have a CAD evaluation frame"
            ),
            Self::CadFaceWithoutSourceFaces { cad_face_id } => write!(
                formatter,
                "CAD face {cad_face_id} does not reference any source faces"
            ),
            Self::CadFaceWithoutLoops { cad_face_id } => write!(
                formatter,
                "CAD face {cad_face_id} does not reference any CAD loops"
            ),
            Self::MissingCadFaceLoop {
                cad_face_id,
                loop_id,
            } => write!(
                formatter,
                "CAD face {cad_face_id} references missing CAD loop {loop_id}"
            ),
            Self::CadLoopFaceMismatch {
                cad_face_id,
                loop_id,
                actual_face_id,
            } => write!(
                formatter,
                "CAD face {cad_face_id} references CAD loop {loop_id} owned by {actual_face_id}"
            ),
            Self::InvalidCadLoopEdgeId {
                cad_face_id,
                loop_edge_id,
            } => write!(
                formatter,
                "CAD face {cad_face_id} has invalid loop edge id {loop_edge_id}"
            ),
            Self::MissingCurveEdge { source_edge_id } => write!(
                formatter,
                "source edge {source_edge_id} does not have curve discretization nodes"
            ),
            Self::MissingCadCurveProvenance { source_edge_id } => write!(
                formatter,
                "source edge {source_edge_id} does not have CAD curve provenance"
            ),
            Self::InvalidFaceEdgeOrientation { face_id, edge_id } => write!(
                formatter,
                "source face {face_id} cannot orient source edge {edge_id} along its boundary"
            ),
            Self::CadProjectionOutsideFaceDomain { face_id, node_id } => write!(
                formatter,
                "source face {face_id} node {node_id} projects outside the CAD face domain"
            ),
            Self::EmptyFaceLoop { face_id } => {
                write!(formatter, "source face {face_id} has an empty boundary loop")
            }
            Self::InvalidFaceLoopTopology {
                face_id,
                node_id,
                incident_segment_count,
            } => write!(
                formatter,
                "source face {face_id} boundary node {node_id} has {incident_segment_count} incident curve segments"
            ),
        }
    }
}

impl std::error::Error for SurfaceDiscretizationError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::InvalidCurveBoundary(source) => Some(source),
            _ => None,
        }
    }
}