1use std::collections::BTreeMap;
14
15use geop_core_geometry::{
16 nurb_curve::{NurbCurve2D, NurbCurve3D},
17 nurb_surface::NurbSurface3D,
18};
19use geop_core_math::{
20 geop_error::{GeopError, GeopResult, WithContext},
21 scalars::Scalar,
22 vector::{Vector2, Vector3},
23 with_context,
24};
25use geop_core_topology::{
26 Curve3, Sense,
27 build::{BodySpec, BuiltBody, CoedgeOn, CoedgeSpec, EdgeSpec, FaceSpec},
28};
29use geop_ops_model::{BodyNames, NamedModel, Namer};
30
31#[derive(Clone, Debug)]
33pub struct SheetVertex {
34 pub a: Vector3,
35 pub b: Vector3,
36 pub name: Namer,
39}
40
41#[derive(Clone, Debug)]
45pub struct SheetEdge {
46 pub a: Curve3,
47 pub b: Curve3,
48 pub start: usize,
49 pub end: usize,
50 pub name: Namer,
52}
53
54#[derive(Clone, Debug)]
57pub struct SheetCoedge {
58 pub edge: usize,
59 pub sense: Sense,
60 pub pcurve: NurbCurve2D,
61}
62
63#[derive(Clone, Debug)]
68pub struct SheetFace {
69 pub a: NurbSurface3D,
70 pub b: NurbSurface3D,
71 pub outer: Vec<SheetCoedge>,
72 pub holes: Vec<Vec<SheetCoedge>>,
73 pub name: Namer,
75}
76
77#[derive(Clone, Debug, Default)]
80pub struct SheetSurface {
81 pub vertices: Vec<SheetVertex>,
82 pub edges: Vec<SheetEdge>,
83 pub faces: Vec<SheetFace>,
84}
85
86impl SheetSurface {
87 fn uses(&self) -> Vec<Vec<(usize, Sense)>> {
89 let mut uses = vec![Vec::new(); self.edges.len()];
90 for (f, face) in self.faces.iter().enumerate() {
91 for coedge in std::iter::once(&face.outer).chain(&face.holes).flatten() {
92 uses[coedge.edge].push((f, coedge.sense));
93 }
94 }
95 uses
96 }
97
98 fn boundary(&self) -> GeopResult<Vec<bool>> {
101 self.uses()
102 .iter()
103 .enumerate()
104 .map(|(e, uses)| match uses[..] {
105 [_] => Ok(true),
106 [(_, a), (_, b)] if a != b => Ok(false),
107 _ => Err(GeopError::new(format!(
108 "the sheet's edge {} is used {:?} by its faces",
109 self.edges[e].name.root(),
110 uses
111 ))),
112 })
113 .collect()
114 }
115
116 pub fn face_names(&self) -> GeopResult<Vec<String>> {
119 let boundary = self.boundary()?;
120 let mut names: Vec<String> = self
121 .faces
122 .iter()
123 .flat_map(|f| [f.name.name(&["a"]), f.name.name(&["b"])])
124 .collect();
125 names.extend(
126 self.edges
127 .iter()
128 .zip(&boundary)
129 .filter(|(_, b)| **b)
130 .map(|(e, _)| e.name.root()),
131 );
132 Ok(names)
133 }
134}
135
136pub fn thicken(
145 model: &mut NamedModel,
146 sheet: &SheetSurface,
147 solid: &str,
148 rename: &dyn Fn(String) -> String,
149) -> GeopResult<BuiltBody> {
150 let ctx = with_context!(
151 "thicken({solid}: {} faces, {} edges, {} vertices)",
152 sheet.faces.len(),
153 sheet.edges.len(),
154 sheet.vertices.len()
155 );
156 let boundary = sheet.boundary().with_context(ctx)?;
157 let nv = sheet.vertices.len();
158 let mut spec = BodySpec::new(true);
159 let mut names = BodyNames {
160 solid: Some(solid.to_string()),
161 ..BodyNames::default()
162 };
163 for side in ["a", "b"] {
165 for v in &sheet.vertices {
166 spec.vertices.push(if side == "a" { v.a } else { v.b });
167 names.vertices.push(rename(v.name.name(&[side])));
168 }
169 }
170 for e in &sheet.edges {
172 spec.edges.push(EdgeSpec {
173 curve: e.a.clone(),
174 start: e.start,
175 end: e.end,
176 });
177 names.edges.push(rename(e.name.name(&["a"])));
178 spec.edges.push(EdgeSpec {
179 curve: e.b.clone(),
180 start: nv + e.start,
181 end: nv + e.end,
182 });
183 names.edges.push(rename(e.name.name(&["b"])));
184 }
185 let mut arriving = vec![0; nv];
188 let mut leaving = vec![0; nv];
189 for (e, edge) in sheet.edges.iter().enumerate() {
190 if boundary[e] {
191 leaving[edge.start] += 1;
192 arriving[edge.end] += 1;
193 }
194 }
195 let mut across: BTreeMap<usize, usize> = BTreeMap::new();
196 for (v, vertex) in sheet.vertices.iter().enumerate() {
197 match (arriving[v] + leaving[v], arriving[v] == leaving[v]) {
198 (0, _) => {}
199 (2, _) => {
200 spec.edges.push(EdgeSpec {
201 curve: NurbCurve3D::line(vertex.a, vertex.b).with_context(ctx)?,
202 start: v,
203 end: nv + v,
204 });
205 names.edges.push(rename(vertex.name.root()));
206 across.insert(v, spec.edges.len() - 1);
207 }
208 (n, _) => {
209 return Err(GeopError::new(format!(
210 "the sheet's boundary meets itself at vertex {}: {n} boundary edges end there, where two must",
211 vertex.name.root()
212 )))
213 .with_context(ctx);
214 }
215 }
216 }
217
218 let coedges = |lp: &[SheetCoedge], side: usize| -> Vec<CoedgeSpec> {
219 lp.iter()
220 .map(|c| CoedgeSpec {
221 on: CoedgeOn::Edge(2 * c.edge + side, c.sense),
222 pcurve: c.pcurve.clone(),
223 })
224 .collect()
225 };
226 for face in &sheet.faces {
227 let b = FaceSpec {
230 surface: face.b.clone(),
231 outer: coedges(&face.outer, 1),
232 holes: face.holes.iter().map(|h| coedges(h, 1)).collect(),
233 };
234 let a = FaceSpec {
235 surface: face.a.clone(),
236 outer: coedges(&face.outer, 0),
237 holes: face.holes.iter().map(|h| coedges(h, 0)).collect(),
238 }
239 .reversed();
240 spec.faces.push(a);
241 names.faces.push(rename(face.name.name(&["a"])));
242 spec.faces.push(b);
243 names.faces.push(rename(face.name.name(&["b"])));
244 }
245
246 let uses = sheet.uses();
251 let point = |u: Scalar, v: Scalar| Vector2::from_array([u, v]);
252 for (e, edge) in sheet.edges.iter().enumerate() {
253 if !boundary[e] {
254 continue;
255 }
256 let (t0, t1) = edge.a.domain();
257 let face = FaceSpec {
258 surface: NurbSurface3D::ruled_compatible(&edge.a, &edge.b)
261 .map_err(|err| err.with_context(format!("the wall of edge {}", edge.name.root())))
262 .with_context(ctx)?,
263 outer: vec![
264 CoedgeSpec {
265 on: CoedgeOn::Edge(2 * e, Sense::Forward),
266 pcurve: NurbCurve2D::line(point(t0, Scalar::ZERO), point(t1, Scalar::ZERO))?,
267 },
268 CoedgeSpec {
269 on: CoedgeOn::Edge(across[&edge.end], Sense::Forward),
270 pcurve: NurbCurve2D::line(point(t1, Scalar::ZERO), point(t1, Scalar::ONE))?,
271 },
272 CoedgeSpec {
273 on: CoedgeOn::Edge(2 * e + 1, Sense::Reversed),
274 pcurve: NurbCurve2D::line(point(t1, Scalar::ONE), point(t0, Scalar::ONE))?,
275 },
276 CoedgeSpec {
277 on: CoedgeOn::Edge(across[&edge.start], Sense::Reversed),
278 pcurve: NurbCurve2D::line(point(t0, Scalar::ONE), point(t0, Scalar::ZERO))?,
279 },
280 ],
281 holes: Vec::new(),
282 };
283 spec.faces.push(match uses[e][0].1 {
284 Sense::Forward => face,
285 Sense::Reversed => face.reversed(),
286 });
287 names.faces.push(rename(edge.name.root()));
288 }
289 spec.shells = vec![(0..spec.faces.len()).collect()];
290 model.build_body(spec, names).with_context(ctx)
291}