Skip to main content

brep_kernel/offset/
offset_shell.rs

1use crate::boolean::{assemble_open_fragments, edge_interior_lies_on, finalize_assembled_solid};
2use crate::classification::{parameter_point_in_face, PolygonClass};
3use crate::face_merge::merge_same_surface_faces_open;
4use crate::fragment::{fragment_solid, FaceFragmentRecord, FragmentEdgeSource};
5use crate::imprint::{
6    EdgeSplitRecord, FaceImprints, FaceKey, FacePcurve, ImprintOptions, ImprintPieceRecord,
7    ImprintResultRecord, ImprintVertex,
8};
9use crate::mass_properties::parameter_space_area;
10use crate::project_point_to_surface;
11use crate::topology::{
12    BrepSolid, CoedgeRecord, EdgeRecord, FaceRecord, LoopRecord, ShellRecord, VertexRecord,
13};
14use crate::{
15    apply_edge_splits, build_imprints, classify_point, offset_face_carrier, DiagnosticSeverity,
16    KernelDiagnostics, KernelOutcome, KernelStage, KernelTolerances, PointClass, Vec2, Vec3,
17};
18use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
19use serde::Serialize;
20
21const SOURCE_OPERAND: u8 = 250;
22
23/// The smallest |distance| an offset shell answers: at or under it the shell
24/// refuses ("distance must be finite and non-zero"). One constant, because the
25/// revolved-wedge lane runs in FRONT of that refusal and must stand down exactly
26/// where it fires — it once took (−1e-7, 0) as an outward distance and built a
27/// sliver where the shell had always refused.
28const MINIMUM_DISTANCE: f64 = 1e-7;
29
30fn debug_enabled() -> bool {
31    env_read!("BREP_OS_DEBUG").is_some_and(|value| !value.is_empty() && value != "0")
32}
33
34macro_rules! os_debug {
35    ($($arg:tt)*) => {
36        if debug_enabled() {
37            eprintln!($($arg)*);
38        }
39    };
40}
41
42#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
43#[serde(rename_all = "lowercase")]
44pub enum OffsetFaceRole {
45    Source,
46    Offset,
47    Wall,
48}
49
50#[derive(Clone, Debug, Serialize)]
51pub struct OffsetShellResultRecord {
52    pub solid: BrepSolid,
53    pub face_images: Vec<OffsetShellFaceImageRecord>,
54}
55
56#[derive(Clone, Debug, Serialize)]
57pub struct OffsetShellFaceImageRecord {
58    pub role: OffsetFaceRole,
59    pub source_face_id: u64,
60}
61
62#[derive(Clone)]
63struct Carrier {
64    solid: BrepSolid,
65    source_face_id: u64,
66    kind: OffsetFaceRole,
67}
68
69#[path = "offset_shell/carriers.rs"]
70mod carriers;
71#[path = "offset_shell/smooth_sync.rs"]
72mod smooth_sync;
73#[path = "offset_shell/carrier_rebuild.rs"]
74mod carrier_rebuild;
75#[path = "offset_shell/connectors.rs"]
76mod connectors;
77#[path = "offset_shell/orientation.rs"]
78mod orientation;
79#[path = "offset_shell/rim_welds.rs"]
80mod rim_welds;
81#[path = "offset_shell/rim_round.rs"]
82mod rim_round;
83
84#[path = "offset_shell/revolved_wedge.rs"]
85mod revolved_wedge;
86#[path = "offset_shell/planar_shell.rs"]
87mod planar_shell;
88#[path = "offset_shell/sphere_body.rs"]
89mod sphere_body;
90#[path = "offset_shell/sphere_cut_shell.rs"]
91mod sphere_cut_shell;
92#[path = "offset_shell/outline_offset.rs"]
93mod outline_offset;
94#[path = "offset_shell/vacated_rim.rs"]
95mod vacated_rim;
96#[path = "offset_shell/pipeline.rs"]
97mod pipeline;
98#[path = "offset_shell/acceptance.rs"]
99mod acceptance;
100#[path = "offset_shell/profile.rs"]
101mod profile;
102#[path = "offset_shell/solid.rs"]
103mod solid;
104#[path = "offset_shell/joint_offset.rs"]
105mod joint_offset;
106#[path = "offset_shell/refusals.rs"]
107mod refusals;
108#[path = "offset_shell/torus_body.rs"]
109mod torus_body;
110#[path = "offset_shell/bore_dimples.rs"]
111mod bore_dimples;
112
113use carrier_rebuild::*;
114use carriers::*;
115use connectors::*;
116use orientation::*;
117use outline_offset::*;
118use vacated_rim::*;
119use rim_welds::*;
120use smooth_sync::*;
121
122pub use pipeline::{offset_shell, offset_shell_with_diagnostics};
123pub use solid::{offset_shell_by_boolean, offset_shell_closed, offset_solid, OffsetCorners};
124pub(crate) use refusals::{is_offset_shell_no_retained_face, is_offset_shell_open_curved_rim};
125pub(crate) use orientation::{flip_all_faces, flip_shell_faces, orient_open_solid_faces};
126
127/// Merge connected shells, preserving the first shell record and face encounter order.
128fn merge_connected_shells(solid: &mut BrepSolid, shell_unions: Vec<(usize, usize)>) {
129    if shell_unions.is_empty() {
130        return;
131    }
132    let mut parent = (0..solid.shells.len()).collect::<Vec<_>>();
133    fn root(parent: &mut [usize], index: usize) -> usize {
134        crate::union_find::find_root(parent, index)
135    }
136    for (first, second) in shell_unions {
137        let first_root = root(&mut parent, first);
138        let second_root = root(&mut parent, second);
139        if first_root != second_root {
140            parent[second_root] = first_root;
141        }
142    }
143    let original = std::mem::take(&mut solid.shells);
144    let mut merged = Vec::<ShellRecord>::new();
145    let mut group_of = HashMap::<usize, usize>::default();
146    for (index, shell) in original.into_iter().enumerate() {
147        let group = root(&mut parent, index);
148        if let Some(target) = group_of.get(&group).copied() {
149            merged[target].faces.extend(shell.faces);
150        } else {
151            group_of.insert(group, merged.len());
152            merged.push(shell);
153        }
154    }
155    solid.shells = merged;
156}