1use crate::NamedModel;
8use geop_core_editor::editing::Choice;
9use geop_core_geometry::aabb::union_aabb;
10use geop_core_math::{
11 geop_error::{GeopError, GeopResult},
12 plain,
13 primitives::CoordinateSystem,
14 vector::Vector3,
15};
16use geop_core_program::{numeric::NumericInput, reader::Reader};
17use geop_core_topology::{Body, FaceId, Model, boundary::BoundaryType};
18use serde::{Deserialize, Serialize};
19
20#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
26#[serde(rename_all = "snake_case")]
27pub enum Extent {
28 Blind(NumericInput),
29 UpToNext,
30 ThroughAll,
31}
32
33#[derive(Clone, Copy, Debug, PartialEq)]
35pub enum End {
36 Blind(f64),
37 UpToNext,
38 ThroughAll,
39}
40
41impl End {
42 pub fn length(self) -> Option<f64> {
44 match self {
45 End::Blind(length) => Some(length),
46 End::UpToNext | End::ThroughAll => None,
47 }
48 }
49}
50
51impl Extent {
52 pub fn blind(amount: impl Into<NumericInput>) -> Self {
54 Extent::Blind(amount.into())
55 }
56
57 pub fn length_mut(&mut self) -> Option<&mut NumericInput> {
59 match self {
60 Extent::Blind(length) => Some(length),
61 Extent::UpToNext | Extent::ThroughAll => None,
62 }
63 }
64
65 pub fn end(&self, reader: &Reader) -> GeopResult<End> {
67 Ok(match self {
68 Extent::Blind(length) => End::Blind(length.resolve(reader)?),
69 Extent::UpToNext => End::UpToNext,
70 Extent::ThroughAll => End::ThroughAll,
71 })
72 }
73
74 pub fn choices(none: bool) -> Vec<Choice> {
77 let mut choices = Vec::new();
78 if none {
79 choices.push(Choice::new("none", "None"));
80 }
81 choices.push(Choice::new("blind", "Blind"));
82 choices.push(Choice::new("up_to_next", "Up to next"));
83 choices.push(Choice::new("through_all", "Through all"));
84 choices
85 }
86
87 pub fn mode(extent: Option<&Extent>) -> &'static str {
89 match extent {
90 None => "none",
91 Some(Extent::Blind(_)) => "blind",
92 Some(Extent::UpToNext) => "up_to_next",
93 Some(Extent::ThroughAll) => "through_all",
94 }
95 }
96
97 pub fn pick(old: Option<&Extent>, mode: &str, default: f64) -> Option<Extent> {
100 match mode {
101 "blind" => Some(Extent::Blind(match old {
102 Some(Extent::Blind(d)) => d.clone(),
103 _ => NumericInput::Plain(default),
104 })),
105 "up_to_next" => Some(Extent::UpToNext),
106 "through_all" => Some(Extent::ThroughAll),
107 _ => None,
108 }
109 }
110}
111
112pub fn hull(model: &NamedModel, targets: &[String]) -> GeopResult<Option<Vec<Vector3>>> {
115 let topology = model.topology();
116 let mut faces = Vec::new();
117 for target in targets {
118 faces.extend(topology.body_faces(Body::Solid(model.solid_id(target)?))?);
119 }
120 let points = face_hull(topology, &faces)?;
121 Ok((!points.is_empty()).then_some(points))
122}
123
124pub fn face_hull(model: &Model, faces: &[FaceId]) -> GeopResult<Vec<Vector3>> {
138 let mut points = Vec::new();
139 for &face in faces {
140 let face = model.get_face(face)?;
141 let outer = match face.outer {
142 BoundaryType::Loop(anchor) => model
143 .iterate_loop_coedges(anchor)
144 .map(|c| Ok(model.get_coedge(c)?.pcurve.aabb()))
145 .collect::<GeopResult<Vec<_>>>()?,
146 BoundaryType::Vertex(_) => Vec::new(),
147 };
148 let surface = match union_aabb(outer) {
149 Some(uv) => face.surface.sub_surface(
150 (uv[0].lower(), uv[0].upper()),
151 (uv[1].lower(), uv[1].upper()),
152 )?,
153 None => face.surface.clone(),
154 };
155 for cp in &surface.control_points {
156 points.push(cp.dehomogenized()?);
157 }
158 }
159 Ok(points)
160}
161
162pub fn spread_along(points: &[Vector3], plane: &CoordinateSystem) -> (f64, f64) {
165 let o = plane.origin().to_f64_array();
166 let w = plane.w().to_f64_array();
167 let along = |p: &Vector3| {
168 let p = p.to_f64_array();
169 (0..3).map(|k| (p[k] - o[k]) * w[k]).sum::<f64>()
170 };
171 points
172 .iter()
173 .map(along)
174 .fold((f64::INFINITY, f64::NEG_INFINITY), |(near, far), x| {
175 (near.min(x), far.max(x))
176 })
177}
178
179pub fn reach_past(hull: &[Vector3], plane: &CoordinateSystem, sign: f64) -> GeopResult<f64> {
184 let (near, far) = spread_along(hull, plane);
185 let ahead = if sign > 0.0 { far } else { -near };
186 if ahead <= 0.0 {
187 return Err(GeopError::new(format!(
188 "up to next: nothing of the target lies {} the sketch, so there is no next face",
189 if sign > 0.0 { "in front of" } else { "behind" }
190 )));
191 }
192 let (lo, hi) = plain::bounds(hull.iter().map(Vector3::to_f64_array))
194 .ok_or_else(|| GeopError::new("up to next: the target has no faces"))?;
195 Ok(ahead + plain::diagonal(lo, hi))
196}