Skip to main content

geop_ops_drawing/
scene.rs

1//! What a drawing draws: the faces of a part and of every part placed in
2//! it, however deep, each where it is placed ([`Scene`]) — and seeing them
3//! all from one direction at once ([`Scene::project`]).
4//!
5//! An assembly places the same part many times — a hundred screws of one
6//! size — so its hidden lines are found in two stages, and the costly one
7//! is done once per part however often it is placed:
8//!
9//! 1. **Each part on its own.** A part's lines, and which of them its own
10//!    faces hide, are those [`project_view`] finds looking along the view's
11//!    direction turned into the part's own frame. Placed parts of one
12//!    component turned alike look at it from the same direction: they share
13//!    that work, and draw the same lines moved to where each is.
14//! 2. **The parts together.** Along a line one part shows, what another
15//!    part hides of it changes only where the line passes behind that
16//!    part's outline on the paper, which the lines that part shows hold.
17//!    So each line a part shows is cut where it crosses or runs along a
18//!    line another part shows, and each piece is decided by a ray from its
19//!    middle towards the eye, against the faces of that part (see
20//!    [`Scene::together`]). A line its own part hides stays hidden. Where
21//!    the lines of two parts cross is found to a coarser handoff than
22//!    where a part's own lines do (`COARSE_SUBDIVISION`): a part's lines
23//!    meet at its corners, which must be told apart from crossings near
24//!    them, while two parts touch along curves that only touch on the
25//!    paper — a screw's round head over the flats of its nut — where a
26//!    fine search runs a long way before giving up.
27//! 3. **Copies behind copies.** A copy of a part placed exactly behind
28//!    another, as the view sees it, is hidden by it whole, and adds no line
29//!    (see [`Scene::mark_copies_behind`]): a row of screws seen end on is
30//!    drawn as one.
31//!
32//! A line is not cut where it enters another part, only where it passes
33//! behind its outline: parts that pierce each other are a known limit. The
34//! parts of an assembly touch, they do not overlap.
35
36use geop_core_geometry::nurb_curve::NurbCurve3D;
37use geop_core_math::{
38    geop_error::{GeopError, GeopResult, WithContext},
39    plain,
40    primitives::{Motion, Pose, Quaternion},
41    scalars::Scalar,
42    vector::Vector3,
43};
44use geop_core_program::{element::ElementReference, reader::Reader};
45use geop_core_topology::{FaceId, Model};
46use geop_ops_model::placed::placed_parts;
47use geop_ops_model::{NamedModel, Threads, model, threads};
48
49use crate::{
50    drawing::drawn_faces,
51    hidden_lines::{
52        COARSE_SUBDIVISION, Occluder, ProjectedView, ViewLine, ViewOptions, bounds, boxes_meet,
53        cut_points, drop_drawn, meetings, occluders_bounds, project_view, ray_length, seen,
54    },
55    view::{ViewAxis, ViewFrame},
56};
57
58/// The faces of one part, where it is placed.
59pub struct Body<'p> {
60    /// Where it is placed, as the entities of a placed part are named: `""`
61    /// for the drawn part's own faces, `screw` for the part placed as
62    /// `screw`, `arm/screw` for one placed in that.
63    pub path: String,
64    /// Its part's model, which names its entities.
65    pub named: &'p NamedModel,
66    /// Its part's cosmetic threads, if it records any.
67    pub threads: Option<&'p Threads>,
68    pub faces: Vec<FaceId>,
69    /// Where its part is placed; `None` for the drawn part's own faces.
70    pub pose: Option<Pose>,
71}
72
73impl<'p> Body<'p> {
74    pub fn model(&self) -> &'p Model {
75        self.named.topology()
76    }
77
78    /// How it is turned, if it is turned at all.
79    fn turn(&self) -> Option<Quaternion> {
80        self.pose
81            .as_ref()
82            .map(|p| p.rotation())
83            .filter(|r| !r.is_identity())
84    }
85}
86
87/// A body as one view sees it: its box on the paper, `[x_min, y_min,
88/// x_max, y_max]`, and how near the eye it comes.
89#[derive(Clone, Copy)]
90pub(crate) struct Reach {
91    paper: [f64; 4],
92    near: f64,
93}
94
95/// The bodies a drawing draws, and what seeing them needs.
96pub struct Scene<'p> {
97    pub bodies: Vec<Body<'p>>,
98    /// Per body, moving its part's geometry to where it is placed, and
99    /// back; `None` for the drawn part's own.
100    there: Vec<Option<Motion>>,
101    back: Vec<Option<Motion>>,
102    /// The faces that can hide something, once per model; body `k`'s are
103    /// `occluders[occluders_of[k]]`, in its part's own frame.
104    occluders: Vec<Vec<Occluder>>,
105    occluders_of: Vec<usize>,
106    /// The corners of each body's box, where it is placed.
107    corners: Vec<[Vector3; 8]>,
108    /// The ray length that clears every body from anywhere on them.
109    length: Scalar,
110}
111
112impl<'p> Scene<'p> {
113    /// The scene of `bodies`.
114    pub fn new(bodies: Vec<Body<'p>>) -> GeopResult<Self> {
115        let mut occluders: Vec<Vec<Occluder>> = Vec::new();
116        let mut models: Vec<&Model> = Vec::new();
117        let mut occluders_of = Vec::new();
118        let mut corners = Vec::new();
119        let (mut there, mut back) = (Vec::new(), Vec::new());
120        for body in &bodies {
121            let model = body.model();
122            let index = match models.iter().position(|m| std::ptr::eq(*m, model)) {
123                Some(index) => index,
124                None => {
125                    occluders.push(
126                        body.faces
127                            .iter()
128                            .map(|&f| Occluder::of(model, f))
129                            .collect::<GeopResult<_>>()?,
130                    );
131                    models.push(model);
132                    models.len() - 1
133                }
134            };
135            occluders_of.push(index);
136            let motion = body.pose.map(|p| p.motion());
137            let (lo, hi) = occluders_bounds(&occluders[index]);
138            corners.push(std::array::from_fn(|i| {
139                let c = Vector3::from_array(std::array::from_fn(|k| {
140                    Scalar::from_f64(if (i >> k) & 1 == 1 { hi[k] } else { lo[k] })
141                }));
142                match &motion {
143                    Some(m) => m.apply(&c),
144                    None => c,
145                }
146            }));
147            there.push(motion);
148            back.push(body.pose.map(|p| p.inverse().motion()));
149        }
150        let mut lo = [f64::INFINITY; 3];
151        let mut hi = [f64::NEG_INFINITY; 3];
152        for c in corners.iter().flatten() {
153            for k in 0..3 {
154                lo[k] = lo[k].min(c[k].lower().to_f64());
155                hi[k] = hi[k].max(c[k].upper().to_f64());
156            }
157        }
158        let diagonal = plain::diagonal(lo, hi);
159        let length = match bodies.len() {
160            // One body: as a view of its model alone has it.
161            1 => ray_length(&occluders[0]),
162            _ => Scalar::from_f64(2.0 * diagonal + 1.0),
163        };
164        Ok(Scene {
165            bodies,
166            there,
167            back,
168            occluders,
169            occluders_of,
170            corners,
171            length,
172        })
173    }
174
175    /// Which side of the plane through `origin` normal to `normal` body `k`
176    /// lies on: wholly on the side `normal` points to (`Some(true)`),
177    /// wholly on the other (`Some(false)`), or on both, or touching it
178    /// (`None`).
179    pub fn side_of(&self, k: usize, origin: &Vector3, normal: &Vector3) -> Option<bool> {
180        let heights: Vec<Scalar> = self.corners[k]
181            .iter()
182            .map(|c| c.sub(origin).prod_dot(normal))
183            .collect();
184        if heights.iter().all(|h| h.definitely_greater(Scalar::ZERO)) {
185            Some(true)
186        } else if heights.iter().all(|h| h.definitely_less(Scalar::ZERO)) {
187            Some(false)
188        } else {
189            None
190        }
191    }
192
193    /// The bodies that share the work of the first stage (see the module
194    /// docs): the same model, turned alike. Each group in the order of its
195    /// first body.
196    pub fn groups(&self) -> Vec<Vec<usize>> {
197        let mut groups: Vec<Vec<usize>> = Vec::new();
198        for (k, body) in self.bodies.iter().enumerate() {
199            let alike = |g: &&mut Vec<usize>| {
200                let first = &self.bodies[g[0]];
201                std::ptr::eq(first.model(), body.model()) && same_turn(first.turn(), body.turn())
202            };
203            match groups.iter_mut().find(alike) {
204                Some(group) => group.push(k),
205                None => groups.push(vec![k]),
206            }
207        }
208        groups
209    }
210
211    /// `frame` as the part of body `k` sees it, in its own frame.
212    fn local_frame(&self, k: usize, frame: &ViewFrame) -> ViewFrame {
213        match (&self.back[k], self.bodies[k].turn()) {
214            (Some(back), Some(_)) => {
215                let turn = |axis: &ViewAxis| ViewAxis::General(back.rotate(&axis.vector()));
216                ViewFrame {
217                    direction: turn(&frame.direction),
218                    right: turn(&frame.right),
219                    up: turn(&frame.up),
220                }
221            }
222            _ => *frame,
223        }
224    }
225
226    /// The line `line` of body `k`'s part, in its own frame, where the body
227    /// is, seen through `frame`.
228    fn placed_line(&self, k: usize, line: &ViewLine, frame: &ViewFrame) -> GeopResult<ViewLine> {
229        let (curve3, curve) = match &self.there[k] {
230            Some(motion) => {
231                let curve3 = line.curve3.transform(motion);
232                let curve = frame.project_curve(&curve3)?;
233                (curve3, curve)
234            }
235            None => (line.curve3.clone(), line.curve.clone()),
236        };
237        Ok(ViewLine {
238            curve,
239            curve3,
240            kind: line.kind,
241            visible: line.visible,
242            edge: line.edge,
243            body: k,
244            source: line.source,
245            range: line.range,
246        })
247    }
248
249    /// Every body seen through `frame`, each line visible or hidden: see
250    /// the module docs.
251    pub fn project(&self, frame: &ViewFrame, options: &ViewOptions) -> GeopResult<ProjectedView> {
252        let mut lines = Vec::new();
253        let mut behind = vec![false; self.bodies.len()];
254        for group in self.groups() {
255            self.mark_copies_behind(&group, frame, &mut behind);
256            let first = &self.bodies[group[0]];
257            let view = project_view(
258                first.model(),
259                &first.faces,
260                &self.local_frame(group[0], frame),
261                options,
262            )
263            .map_err(|e| e.with_context(format!("the part placed as {:?}", first.path)))?;
264            for &k in group.iter().filter(|&&k| !behind[k]) {
265                for line in &view.lines {
266                    lines.push(self.placed_line(k, line, frame)?);
267                }
268            }
269        }
270        if self.bodies.len() > 1 {
271            lines = self.together(lines, &behind, frame, options)?;
272        }
273        lines.sort_by_key(|l| l.body);
274        Ok(ProjectedView {
275            frame: *frame,
276            lines,
277        })
278    }
279
280    /// Marks in `behind` each body of `group` — one model, turned alike —
281    /// that `frame` sees exactly behind another: placed where it lands on
282    /// the paper where the other does, farther from the eye, or at the
283    /// same place after it. Each point of it then has the other's point
284    /// that it is a copy of right in front of it, on its ray to the eye: it
285    /// is hidden whole, hides nothing the other does not, and each of its
286    /// lines lies on one of the other's. A row of screws seen from its end
287    /// draws one.
288    fn mark_copies_behind(&self, group: &[usize], frame: &ViewFrame, behind: &mut [bool]) {
289        let toward_eye = frame.toward_eye();
290        let at: Vec<Vector3> = group
291            .iter()
292            .map(|&k| {
293                self.bodies[k]
294                    .pose
295                    .map(|p| p.position())
296                    .unwrap_or_else(Vector3::zero)
297            })
298            .collect();
299        for (i, &k) in group.iter().enumerate() {
300            let (paper, depth) = (frame.project_point(&at[i]), toward_eye.prod_dot(&at[i]));
301            behind[k] = group.iter().enumerate().any(|(j, _)| {
302                j != i
303                    && frame.project_point(&at[j]).could_be_equal(&paper)
304                    && match toward_eye.prod_dot(&at[j]) {
305                        d if d.definitely_greater(depth) => true,
306                        d if d.could_be_equal(depth) => j < i,
307                        _ => false,
308                    }
309            });
310        }
311    }
312
313    /// How `frame` sees each body.
314    fn reach(&self, frame: &ViewFrame) -> Vec<Reach> {
315        let toward_eye = frame.toward_eye();
316        self.corners
317            .iter()
318            .map(|corners| {
319                let mut paper = [
320                    f64::INFINITY,
321                    f64::INFINITY,
322                    f64::NEG_INFINITY,
323                    f64::NEG_INFINITY,
324                ];
325                let mut near = f64::NEG_INFINITY;
326                for c in corners {
327                    let p = frame.project_point(c);
328                    for k in 0..2 {
329                        paper[k] = paper[k].min(p[k].lower().to_f64());
330                        paper[k + 2] = paper[k + 2].max(p[k].upper().to_f64());
331                    }
332                    near = near.max(toward_eye.prod_dot(c).upper().to_f64());
333                }
334                Reach { paper, near }
335            })
336            .collect()
337    }
338
339    /// Whether any of the bodies `bodies` hides `point` from the eye
340    /// through `frame` (see [`seen`]), each asked in its own part's frame.
341    /// A body wholly behind the point, or beside it on the paper, cannot.
342    fn hidden_by(
343        &self,
344        bodies: impl IntoIterator<Item = usize>,
345        reach: &[Reach],
346        frame: &ViewFrame,
347        point: Vector3,
348    ) -> GeopResult<bool> {
349        let toward_eye = frame.toward_eye();
350        let depth = toward_eye.prod_dot(&point).lower().to_f64();
351        let on_paper = frame.project_point(&point);
352        let (x, y) = (on_paper[0], on_paper[1]);
353        for b in bodies {
354            let r = &reach[b];
355            if r.near < depth
356                || x.upper().to_f64() < r.paper[0]
357                || r.paper[2] < x.lower().to_f64()
358                || y.upper().to_f64() < r.paper[1]
359                || r.paper[3] < y.lower().to_f64()
360            {
361                continue;
362            }
363            let (p, d) = match &self.back[b] {
364                Some(back) => (back.apply(&point), back.rotate(&toward_eye)),
365                None => (point, toward_eye),
366            };
367            let model = self.bodies[b].model();
368            let occluders = &self.occluders[self.occluders_of[b]];
369            let ctx = |e: GeopError| {
370                e.with_context(format!(
371                    "whether the part placed as {:?} hides {point:?}",
372                    self.bodies[b].path
373                ))
374            };
375            if !seen(model, occluders, p, &d, self.length).with_context(&ctx)? {
376                return Ok(true);
377            }
378        }
379        Ok(false)
380    }
381
382    /// Whether `point` is seen through `frame`, every body in front of it
383    /// counted: what decides a line drawn that is no edge or silhouette of
384    /// a body, such as a cosmetic thread.
385    pub fn point_seen(&self, frame: &ViewFrame, point: Vector3) -> GeopResult<bool> {
386        let reach = self.reach(frame);
387        Ok(!self.hidden_by(0..self.bodies.len(), &reach, frame, point)?)
388    }
389
390    /// The second stage (see the module docs): `lines`, each body's as its
391    /// own faces hide them, with what the other bodies hide of them.
392    ///
393    /// A line is decided against one other body at a time, those with the
394    /// largest box on the paper first: its pieces still seen are cut where
395    /// they meet a line that body shows, and each new piece is decided by
396    /// whether that body hides it. A piece one body hides is not looked at
397    /// again — most of what is inside an assembly is hidden by the few
398    /// large parts around it, and need not be cut for the small ones. In
399    /// which order the bodies come changes how much work that is, not what
400    /// is found.
401    ///
402    /// The bodies `behind` marks (see [`Scene::mark_copies_behind`]) have
403    /// no lines and hide nothing.
404    fn together(
405        &self,
406        lines: Vec<ViewLine>,
407        behind: &[bool],
408        frame: &ViewFrame,
409        options: &ViewOptions,
410    ) -> GeopResult<Vec<ViewLine>> {
411        let n = self.bodies.len();
412        let reach = self.reach(frame);
413        let boxes: Vec<_> = lines.iter().map(|l| bounds(&l.curve)).collect();
414        let mut shown: Vec<Vec<usize>> = vec![Vec::new(); n];
415        for (i, line) in lines.iter().enumerate() {
416            if line.visible {
417                shown[line.body].push(i);
418            }
419        }
420        let area = |b: usize| {
421            let p = &reach[b].paper;
422            (p[2] - p[0]) * (p[3] - p[1])
423        };
424        let mut order: Vec<usize> = (0..n).filter(|&b| !behind[b]).collect();
425        order.sort_by(|&a, &b| area(b).total_cmp(&area(a)));
426
427        let toward_eye = frame.toward_eye();
428        // How near the eye the farthest point of a curve could be: a body
429        // that comes no nearer cannot hide any of it.
430        let farthest = |curve3: &NurbCurve3D| -> GeopResult<f64> {
431            let mut depth = f64::INFINITY;
432            for q in &curve3.control_points {
433                let p = Vector3::from_array([q[0].div(q[3])?, q[1].div(q[3])?, q[2].div(q[3])?]);
434                depth = depth.min(toward_eye.prod_dot(&p).lower().to_f64());
435            }
436            Ok(depth)
437        };
438        let mut out = Vec::new();
439        for (i, line) in lines.iter().enumerate() {
440            if !line.visible {
441                out.push(line.clone());
442                continue;
443            }
444            let domain = line.curve.domain();
445            let (mut seen, mut hidden) = (vec![domain], Vec::new());
446            for &b in &order {
447                if b == line.body || !boxes_meet(&boxes[i], &reach[b].paper) {
448                    continue;
449                }
450                let mut still = Vec::new();
451                for (lo, hi) in seen {
452                    let whole = lo.could_be_equal(domain.0) && hi.could_be_equal(domain.1);
453                    let (curve, curve3) = match whole {
454                        true => (line.curve.clone(), line.curve3.clone()),
455                        false => (
456                            line.curve.sub_curve(lo, hi)?,
457                            line.curve3.sub_curve(lo, hi)?,
458                        ),
459                    };
460                    let here = bounds(&curve);
461                    if !boxes_meet(&here, &reach[b].paper) || reach[b].near < farthest(&curve3)? {
462                        still.push((lo, hi));
463                        continue;
464                    }
465                    let mut cuts = Vec::new();
466                    for &j in shown[b].iter().filter(|&&j| boxes_meet(&here, &boxes[j])) {
467                        let other = &lines[j];
468                        let (on_line, _) = meetings(
469                            (&curve, &curve3),
470                            (&other.curve, &other.curve3),
471                            Scalar::from_f64(COARSE_SUBDIVISION),
472                        )
473                        .map_err(|e| {
474                            e.with_context(format!(
475                                "where the {:?} of edge {:?} of the part placed as {:?} \
476                                         and the {:?} of edge {:?} of the part placed as {:?} \
477                                         cross on the paper",
478                                line.kind,
479                                line.edge,
480                                self.bodies[line.body].path,
481                                other.kind,
482                                other.edge,
483                                self.bodies[b].path
484                            ))
485                        })?;
486                        cuts.extend(on_line);
487                    }
488                    let mut ends = vec![lo];
489                    ends.extend(cut_points(cuts, (lo, hi)));
490                    ends.push(hi);
491                    for (a, z, visible) in self.decide(line, &ends, b, &reach, frame)? {
492                        match visible {
493                            true => still.push((a, z)),
494                            false => hidden.push((a, z)),
495                        }
496                    }
497                }
498                seen = still;
499                if seen.is_empty() {
500                    break;
501                }
502            }
503            // Back in order along the line, neighbours alike joined.
504            let mut pieces: Vec<(Scalar, Scalar, bool)> = seen
505                .into_iter()
506                .map(|(a, z)| (a, z, true))
507                .chain(hidden.into_iter().map(|(a, z)| (a, z, false)))
508                .collect();
509            pieces.sort_by(|p, q| p.0.to_f64().total_cmp(&q.0.to_f64()));
510            let mut decided: Vec<(Scalar, Scalar, bool)> = Vec::new();
511            for (a, z, visible) in pieces {
512                match decided.last_mut() {
513                    Some(last) if last.2 == visible => last.1 = z,
514                    _ => decided.push((a, z, visible)),
515                }
516            }
517            if let [(_, _, visible)] = decided.as_slice() {
518                out.push(ViewLine {
519                    visible: *visible,
520                    ..line.clone()
521                });
522                continue;
523            }
524            for (a, z, visible) in decided {
525                out.push(ViewLine {
526                    curve: line.curve.sub_curve(a, z)?,
527                    curve3: line.curve3.sub_curve(a, z)?,
528                    visible,
529                    range: (a, z),
530                    ..line.clone()
531                });
532            }
533        }
534        if !options.hidden_lines {
535            out.retain(|l| l.visible);
536        }
537        // Each body's own lines were cleared of those lying on another in
538        // its own view.
539        drop_drawn(out, |a, b| a.body != b.body)
540    }
541
542    /// The pieces of `line` between consecutive `ends`, each with whether
543    /// body `b` leaves it seen: decided by its middle — or, should a ray
544    /// from there graze a face too closely for its search to settle, by a
545    /// point a quarter along either way. A piece none of whose points could
546    /// be decided takes the visibility of its nearest neighbour.
547    fn decide(
548        &self,
549        line: &ViewLine,
550        ends: &[Scalar],
551        b: usize,
552        reach: &[Reach],
553        frame: &ViewFrame,
554    ) -> GeopResult<Vec<(Scalar, Scalar, bool)>> {
555        let mut pieces: Vec<(Scalar, Scalar, GeopResult<bool>)> = Vec::new();
556        for pair in ends.windows(2) {
557            let (lo, hi) = (pair[0], pair[1]);
558            let at = |f: f64| -> GeopResult<bool> {
559                let t = Scalar::fraction(lo, hi, Scalar::from_f64(f));
560                let point = line.curve3.evaluate(t)?;
561                Ok(!self.hidden_by([b], reach, frame, point)?)
562            };
563            let mut outcome = Err(GeopError::new("no point tried"));
564            for f in [0.5, 0.25, 0.75] {
565                outcome = at(f).map_err(|e| {
566                    e.with_context(format!(
567                        "the piece [{lo:?}, {hi:?}] of the {:?} of edge {:?} of the part \
568                         placed as {:?}",
569                        line.kind, line.edge, self.bodies[line.body].path
570                    ))
571                });
572                if outcome.is_ok() {
573                    break;
574                }
575            }
576            pieces.push((lo, hi, outcome));
577        }
578        let known: Vec<Option<bool>> = pieces.iter().map(|p| p.2.as_ref().ok().copied()).collect();
579        pieces
580            .into_iter()
581            .enumerate()
582            .map(|(k, (lo, hi, outcome))| {
583                let visible = match outcome {
584                    Ok(v) => v,
585                    Err(e) => (1..known.len())
586                        .find_map(|step| {
587                            let before = k.checked_sub(step).and_then(|i| known[i]);
588                            before.or_else(|| known.get(k + step).copied().flatten())
589                        })
590                        .ok_or(e)?,
591                };
592                Ok((lo, hi, visible))
593            })
594            .collect()
595    }
596}
597
598/// Whether two turns are the same: no turn at all, or rotations enclosed
599/// alike, bound for bound. Sharing a view between rotations that are merely
600/// close would draw one with the other's lines.
601fn same_turn(a: Option<Quaternion>, b: Option<Quaternion>) -> bool {
602    match (a, b) {
603        (None, None) => true,
604        (Some(a), Some(b)) => a.components().iter().zip(b.components()).all(|(x, y)| {
605            x.lower().to_f64() == y.lower().to_f64() && x.upper().to_f64() == y.upper().to_f64()
606        }),
607        _ => false,
608    }
609}
610
611impl<'p> Scene<'p> {
612    /// The faces of the part `reader` reads and of every part placed in
613    /// it, however deep, each where it is placed: the part's own first,
614    /// then those placed, in the order they were placed, each followed by
615    /// those placed in it.
616    pub fn of(reader: &Reader<'p>) -> GeopResult<Self> {
617        let mut bodies = Vec::new();
618        push(reader, String::new(), None, &mut bodies)?;
619        for placed in placed_parts(reader)? {
620            let inner = placed.located.part.clone();
621            push(&inner, placed.name, Some(placed.located.world), &mut bodies)?;
622        }
623        Self::new(bodies)
624    }
625}
626
627/// Adds the body of the part `reader` reads itself, placed at `pose`, as
628/// `path`, to `bodies` — if it has faces to draw.
629fn push<'p>(
630    reader: &Reader<'p>,
631    path: String,
632    pose: Option<Pose>,
633    bodies: &mut Vec<Body<'p>>,
634) -> GeopResult<()> {
635    // A part with no model has nothing to draw.
636    if !reader.defines(&model()) {
637        return Ok(());
638    }
639    let whole = ElementReference::<NamedModel, NamedModel>::whole(model());
640    let named = reader.retrieve_element(&whole)?;
641    let faces = drawn_faces(named.topology());
642    if !faces.is_empty() {
643        let threads = match reader.defines(&threads()) {
644            true => {
645                let whole = ElementReference::<Threads, Threads>::whole(threads());
646                Some(reader.retrieve_element(&whole)?)
647            }
648            false => None,
649        };
650        bodies.push(Body {
651            path,
652            named,
653            threads,
654            faces,
655            pose,
656        });
657    }
658    Ok(())
659}