use crate::classification::{parameter_point_in_face, PolygonClass};
use crate::curve::KNOT_IDENTITY_TOL;
use crate::spatial::{Aabb, Bvh};
use crate::tolerance::{
assembler_weld, merge_scale, solid_scale, COINCIDENCE_DISTANCE_FLOOR, WELD_FLOOR,
};
use crate::topology::{BrepSolid, EdgeRecord, FaceRecord};
use crate::{
build_pcurve_on_surface, classify_surface_pair_cached,
fit_polyline, fit_polyline_of_degree, PolylineFit,
intersect_curves, intersect_surfaces, intersect_surfaces_supplemental, project_point_to_curve,
project_point_to_surface, project_point_to_surface_seeded, KnotVector, NurbsCurve, NurbsSurface,
SurfaceClassifyData,
SurfaceIntersectionOptions, SurfacePairClassification, SurfacePairRelation, Vec2, Vec3, Vec4,
};
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use web_time::Instant;
pub(crate) use crate::profile::{profile_emit, profile_enabled, ProfileSink};
#[derive(Default)]
struct ImprintProfile {
enabled: bool,
pairs: u64,
marched_pairs: u64,
whole_reading_reuses: u64,
classify: Duration,
cosurface: Duration,
lies_on: Duration,
planar_iso: Duration,
analytic: Duration,
seeds: Duration,
march: Duration,
clip_and_fit: Duration,
process_curve: Duration,
rescue_march: Duration,
clip: Duration,
shared_boundary: Duration,
fit: Duration,
process: Duration,
hermite_runs: u64,
fit_runs: u64,
hermite: Duration,
pairs_fits: u64,
exact_lane_fits: u64,
exact_iso_sections: u64,
exact_iso_declined: u64,
}
impl ImprintProfile {
fn new() -> Self {
Self {
enabled: profile_enabled(),
..Self::default()
}
}
fn lap(&self, started: &mut Option<Instant>) -> Duration {
crate::profile::lap(self.enabled, started)
}
fn report(&self) {
if !self.enabled {
return;
}
profile_line!("imprint.whole_reading_reuses count={}", self.whole_reading_reuses);
let ms = |d: Duration| d.as_secs_f64() * 1_000.0;
profile_line!(
"imprint.profile pairs={} marched={} classify={:.2} cosurface={:.2} lies_on={:.2} planar_iso={:.2} analytic={:.2} seeds={:.2} march={:.2} clip_fit={:.2} process_curve={:.2}",
self.pairs,
self.marched_pairs,
ms(self.classify),
ms(self.cosurface),
ms(self.lies_on),
ms(self.planar_iso),
ms(self.analytic),
ms(self.seeds),
ms(self.march),
ms(self.clip_and_fit),
ms(self.process_curve),
);
profile_line!(
"imprint.clip_fit rescue_march={:.2} clip={:.2} shared_boundary={:.2} fit={:.2} process={:.2} hermite={:.2} hermite_runs={} fit_runs={}",
ms(self.rescue_march),
ms(self.clip),
ms(self.shared_boundary),
ms(self.fit),
ms(self.process),
ms(self.hermite),
self.hermite_runs,
self.fit_runs,
);
profile_line!(
"imprint.fits pairs={} exact_lanes={} exact_iso_sections={} exact_iso_declined={}",
self.pairs_fits,
self.exact_lane_fits,
self.exact_iso_sections,
self.exact_iso_declined,
);
driver::fit_split_report();
}
}
#[derive(Default, Clone, Copy)]
pub(crate) struct ImprintSplit {
pub pierce: Duration,
pub pierce_edges: u64,
pub overlap: Duration,
pub overlap_edges: u64,
pub crossing: Duration,
pub crossing_edges: u64,
pub recover: Duration,
pub recover_pierces: u64,
pub lies_whole: Duration,
pub lies_whole_calls: u64,
pub lies_whole_hits: u64,
pub lies_span: Duration,
pub lies_span_calls: u64,
pub reuse_edges: u64,
}
thread_local! {
static IMPRINT_SPLIT: std::cell::Cell<ImprintSplit> = const { std::cell::Cell::new(ImprintSplit {
pierce: Duration::ZERO, pierce_edges: 0, overlap: Duration::ZERO, overlap_edges: 0,
crossing: Duration::ZERO, crossing_edges: 0, recover: Duration::ZERO, recover_pierces: 0,
lies_whole: Duration::ZERO, lies_whole_calls: 0, lies_whole_hits: 0,
lies_span: Duration::ZERO, lies_span_calls: 0, reuse_edges: 0,
}) };
}
pub(crate) fn imprint_split_add(update: impl FnOnce(&mut ImprintSplit)) {
IMPRINT_SPLIT.with(|cell| { let mut split = cell.get(); update(&mut split); cell.set(split); });
}
pub(crate) fn imprint_split_take() -> ImprintSplit {
IMPRINT_SPLIT.with(|cell| cell.replace(ImprintSplit::default()))
}
struct ImprintSubphaseClock {
started: Option<Instant>,
label: &'static str,
}
impl ImprintSubphaseClock {
fn new(label: &'static str) -> Self {
Self { started: profile_enabled().then(Instant::now), label }
}
}
impl Drop for ImprintSubphaseClock {
fn drop(&mut self) {
if let Some(started) = self.started {
profile_line!("imprint.subphase label={} ms={:.3}", self.label, started.elapsed().as_secs_f64() * 1000.0);
}
}
}
#[derive(Clone, Debug, Deserialize)]
pub struct ImprintOptions {
#[serde(default = "default_tolerance")]
pub tolerance: f64,
#[serde(default = "default_maximum_fit_points")]
pub maximum_fit_points: usize,
#[serde(default)]
pub local_fit: bool,
#[serde(default)]
pub fit_chunk_points: Option<usize>,
pub maximum_ssi_step: Option<f64>,
#[serde(default)]
pub span_budget: Option<f64>,
#[serde(default)]
pub hermite_sections: bool,
#[serde(default = "default_tangent_curve_lane")]
pub tangent_curve_lane: bool,
#[serde(default)]
pub exact_iso_pcurves: bool,
#[serde(default)]
pub march_starts: bool,
#[serde(default)]
pub two_radius_junctions: bool,
#[serde(default = "default_tangent_curve_imprint")]
pub tangent_curve_imprint: bool,
}
fn default_tolerance() -> f64 {
1e-7
}
fn default_tangent_curve_lane() -> bool {
switch!(live "BREP_TANGENT_CURVE_LANE", true)
}
fn default_tangent_curve_imprint() -> bool {
switch!(live "BREP_TANGENT_CURVE_IMPRINT", true)
}
fn default_maximum_fit_points() -> usize {
80
}
const PAIR_ANGULAR_TOLERANCE: f64 = 1e-4;
const NEAR_TANGENT_STEP_FRACTION: f64 = 1e-3;
const SHARED_SECTION_BAND_FRACTION: f64 = 1e-3;
fn march_maximum_step(
classification: &SurfacePairClassification,
requested: Option<f64>,
scale: f64,
) -> Option<f64> {
let grazes = matches!(
classification.relation,
SurfacePairRelation::NearTangent | SurfacePairRelation::Singular
) && classification.minimum_normal_cross <= PAIR_ANGULAR_TOLERANCE;
if !grazes {
return requested;
}
Some(
requested.unwrap_or(
classification
.minimum_separation
.max(NEAR_TANGENT_STEP_FRACTION * scale),
) * 0.5,
)
}
impl Default for ImprintOptions {
fn default() -> Self {
Self {
tolerance: default_tolerance(),
maximum_fit_points: default_maximum_fit_points(),
local_fit: false,
fit_chunk_points: None,
maximum_ssi_step: None,
span_budget: None,
hermite_sections: false,
tangent_curve_lane: default_tangent_curve_lane(),
tangent_curve_imprint: default_tangent_curve_imprint(),
exact_iso_pcurves: false,
march_starts: false,
two_radius_junctions: false,
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ImprintVertex {
pub id: u64,
pub point: Vec3,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct FacePcurve {
pub operand: u8,
pub face_id: u64,
pub pcurve: NurbsCurve,
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash, Serialize)]
pub struct FaceKey {
pub operand: u8,
pub face_id: u64,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ImprintPieceRecord {
pub id: u64,
pub curve: NurbsCurve,
pub t0: f64,
pub t1: f64,
pub start_vertex_id: u64,
pub end_vertex_id: u64,
pub pcurves: Vec<FacePcurve>,
pub support_faces: [FaceKey; 2],
#[serde(default, skip_serializing_if = "Option::is_none")]
pub shared_edge: Option<(u8, u64, bool)>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct EdgeSplitRecord {
pub operand: u8,
pub edge_id: u64,
pub parameters: Vec<f64>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct FaceImprints {
pub operand: u8,
pub face_id: u64,
pub piece_ids: Vec<u64>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ImprintResultRecord {
pub vertices: Vec<ImprintVertex>,
pub pieces: Vec<ImprintPieceRecord>,
pub by_face: Vec<FaceImprints>,
pub edge_splits: Vec<EdgeSplitRecord>,
#[serde(default)]
pub barrier_edges: Vec<(u8, u64)>,
#[serde(default)]
pub tangent_nodes: Vec<Vec3>,
#[serde(default)]
pub section_evidence: bool,
#[serde(default)]
pub cosurface_pairs: Vec<(FaceKey, FaceKey)>,
#[serde(default)]
pub tangent_sections: Vec<TangentSectionRecord>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct TangentSectionRecord {
pub first: FaceKey,
pub second: FaceKey,
pub start: Vec3,
pub end: Vec3,
pub length: f64,
pub closed: bool,
pub stations: usize,
pub planar: bool,
#[serde(default)]
pub faces: Vec<FaceKey>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub piece_ids: Vec<u64>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub grazing: Vec<(Vec3, f64)>,
}
impl TangentSectionRecord {
pub fn pierce_only(&self) -> bool {
self.stations == 0
&& self
.grazing
.first()
.is_some_and(|(point, _)| point.sub(self.start).length() == 0.0)
}
}
#[path = "imprint/ruled_torus.rs"]
mod ruled_torus;
pub(crate) use ruled_torus::{circular_revolution_profile, construct_ruled_torus, RuledTorusBoundary};
#[path = "imprint/support.rs"]
mod support;
#[path = "imprint/builder.rs"]
mod builder;
#[path = "imprint/junctions.rs"]
mod junctions;
#[path = "imprint/self_touch.rs"]
mod self_touch;
#[path = "imprint/sections.rs"]
mod sections;
#[path = "imprint/tangent_contact.rs"]
mod tangent_contact;
#[path = "imprint/driver.rs"]
mod driver;
#[path = "imprint/work_profile.rs"]
pub(crate) mod work_profile;
#[path = "imprint/reuse_profile.rs"]
pub(crate) mod reuse_profile;
#[path = "imprint/hermite.rs"]
mod hermite;
pub(crate) use hermite::{carried_uv_enabled, carried_uv_min_stations};
#[path = "imprint/gate_census.rs"]
mod gate_census;
use builder::*;
use junctions::*;
use sections::*;
use support::*;
#[path = "imprint/section_graph.rs"]
mod section_graph;
pub(crate) use section_graph::{plane_section_graph, plane_section_graph_range, plane_section_graph_range_bounded};
pub(crate) fn planar_carrier_section(
first: &NurbsSurface,
second: &NurbsSurface,
tolerance: f64,
) -> Result<Option<NurbsCurve>, crate::KernelRefusal> {
support::planar_iso_intersection(first,second,tolerance)
}
use tangent_contact::classify_tangent_contact;
pub use driver::build_imprints;
pub(crate) use driver::{build_carrier_imprints, build_carrier_imprints_within, take_rescue_firings};
pub(crate) use self_touch::self_touch_edge_splits;
pub(crate) use support::clear_window_memo;
pub(crate) use self_touch::clear_touch_memo;
#[path = "imprint/pcurve_cache.rs"]
mod pcurve_cache;
use pcurve_cache::cached_build_pcurve as build_pcurve_on_surface_marched;
#[path = "imprint/intersection_cache.rs"]
mod intersection_cache;
use intersection_cache::intersect as intersect_curve_surface;