mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-11 22:22:55 +00:00
feat(prc): PrcFile::units, the model's unit from its model file (#6919)
The model file's units_in_mm and units_from_cad_file were read and dropped. Keep them on ModelFile and expose PrcFile::units() -> Result<Option<ModelUnits>, PrcError>: millimetres per unit and whether the unit is the CAD system's own or the exporter's default. None when no tree is stored or the unit is not a positive finite number. Co-authored-by: KenM76 <KenM76> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
KenM76
Claude Opus 5.5
parent
f6c59c0b93
commit
532bdc8fa6
@@ -34,4 +34,4 @@ pub use error::PrcError;
|
||||
pub use geometry::Matrix;
|
||||
pub use schema::Schema;
|
||||
pub use tess::{Tessellation, TriangleMesh, TriangleStyle};
|
||||
pub use tree::{EntityOverrides, MAX_PLACEMENTS, ModelNode, NameSource, Placement, Rgba, StyleAlpha};
|
||||
pub use tree::{EntityOverrides, MAX_PLACEMENTS, ModelNode, ModelUnits, NameSource, Placement, Rgba, StyleAlpha};
|
||||
|
||||
@@ -221,6 +221,20 @@ struct Tree {
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
struct ModelFile {
|
||||
roots: Vec<(UniqueId, u32)>,
|
||||
units_in_mm: f64,
|
||||
units_from_cad_file: bool,
|
||||
}
|
||||
|
||||
/// The unit a PRC model's coordinates are in, from its model file [WD 7.3.3].
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub struct ModelUnits {
|
||||
/// Millimetres per model unit: 1 for millimetres, 25.4 for inches.
|
||||
pub mm_per_unit: f64,
|
||||
/// The file says the unit is the CAD system's own. When `false` it is the exporter's
|
||||
/// default and may not be the model's real scale, so a measurement should say the unit
|
||||
/// is assumed.
|
||||
pub from_cad_file: bool,
|
||||
}
|
||||
|
||||
impl PrcFile {
|
||||
@@ -270,6 +284,22 @@ impl PrcFile {
|
||||
self.model_file.iter().any(|&b| b != 0)
|
||||
}
|
||||
|
||||
/// The unit the model's coordinates are in. `None` when the file stores no tree
|
||||
/// ([`Self::stores_tree`]) or the stored unit is not a positive finite number.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`PrcError::Malformed`] or a read error when the model file is stored but does not read.
|
||||
pub fn units(&self) -> Result<Option<ModelUnits>, PrcError> {
|
||||
if !self.stores_tree() {
|
||||
return Ok(None);
|
||||
}
|
||||
let mut r = BitReader::with_budget(&self.model_file, Budget::FULL);
|
||||
let schema = Schema::read(&mut r)?;
|
||||
let model = Ctx::new(r, &schema, self.header.authoring_version).model_file()?;
|
||||
let mm = model.units_in_mm;
|
||||
Ok((mm.is_finite() && mm > 0.0).then_some(ModelUnits { mm_per_unit: mm, from_cad_file: model.units_from_cad_file }))
|
||||
}
|
||||
|
||||
fn walk(&self, rule: StyleAlpha, scope: EntityOverrides) -> Result<(Vec<Placement>, Vec<ModelNode>), PrcError> {
|
||||
self.walk_within(rule, scope, Budget::FULL)
|
||||
}
|
||||
|
||||
@@ -437,8 +437,8 @@ impl Ctx<'_, '_> {
|
||||
pub(super) fn model_file(&mut self) -> Result<ModelFile, PrcError> {
|
||||
self.expect_type(MODEL_FILE)?;
|
||||
self.content_prc_base()?;
|
||||
self.r.bit()?; // units_from_cad_file
|
||||
self.r.double()?; // units_in_mm
|
||||
let units_from_cad_file = self.r.bit()?;
|
||||
let units_in_mm = self.r.double()?;
|
||||
let n = self.count(6, "root occurrences")?;
|
||||
let mut roots = Vec::with_capacity(n);
|
||||
for _ in 0..n {
|
||||
@@ -447,6 +447,6 @@ impl Ctx<'_, '_> {
|
||||
self.r.bit()?; // is_active
|
||||
roots.push((id, index));
|
||||
}
|
||||
Ok(ModelFile { roots })
|
||||
Ok(ModelFile { roots, units_in_mm, units_from_cad_file })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::bits::{BitReader, Budget};
|
||||
use crate::entity::Ctx;
|
||||
use crate::tess::tests::two_face_square;
|
||||
use crate::testw::W;
|
||||
use crate::{PrcError, PrcFile, Rgba, Schema, Tessellation, TriangleMesh, UniqueId};
|
||||
use crate::{ModelUnits, PrcError, PrcFile, Rgba, Schema, Tessellation, TriangleMesh, UniqueId};
|
||||
|
||||
const RED: Rgba = Rgba { r: 1., g: 0., b: 0., a: 1. };
|
||||
const GREEN: Rgba = Rgba { r: 0., g: 1., b: 0., a: 1. };
|
||||
@@ -274,10 +274,15 @@ fn ref_faces(brep: (u32, u32), faces: &[u32], g: (u32, u16)) -> W {
|
||||
|
||||
/// A model file (schema included) whose roots are `(file structure, biased occurrence)`.
|
||||
fn model(roots: &[(usize, u32)]) -> W {
|
||||
model_in(roots, false, 1.)
|
||||
}
|
||||
|
||||
/// [`model`] with its unit: whether it is the CAD file's, and millimetres per unit.
|
||||
fn model_in(roots: &[(usize, u32)], from_cad_file: bool, units_in_mm: f64) -> W {
|
||||
let mut w = W::default();
|
||||
w.uint(0).uint(301);
|
||||
base(&mut w, None);
|
||||
w.bit(false).double(1.).uint(len(roots.len()));
|
||||
w.bit(from_cad_file).double(units_in_mm).uint(len(roots.len()));
|
||||
for &(fs, index) in roots {
|
||||
for v in fs_id(fs).0 {
|
||||
w.uint(v);
|
||||
@@ -391,6 +396,22 @@ fn a_parsed_assembly_places_names_and_colours_each_occurrence() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_model_file_gives_the_unit_and_whether_it_is_assumed() {
|
||||
let fs = || Fs { globals: palette(), tree: tree(&[plate(S_RED)], &[Occ::default()]), tess: two_face_square() };
|
||||
let units = |model: W| PrcFile::parse(&prc(&[fs()], &model)).unwrap().units();
|
||||
let unit = |mm_per_unit, from_cad_file| Some(ModelUnits { mm_per_unit, from_cad_file });
|
||||
assert_eq!(units(model(&[(0, 1)])).unwrap(), unit(1., false));
|
||||
assert_eq!(units(model_in(&[(0, 1)], true, 25.4)).unwrap(), unit(25.4, true));
|
||||
for bad in [0., -1., f64::NAN, f64::INFINITY] {
|
||||
assert_eq!(units(model_in(&[(0, 1)], true, bad)).unwrap(), None, "{bad}");
|
||||
}
|
||||
assert_eq!(units(W::default()).unwrap(), None);
|
||||
let mut not_a_model = W::default();
|
||||
not_a_model.uint(0).uint(302);
|
||||
assert!(units(not_a_model).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_file_without_a_model_file_entity_has_no_tree() {
|
||||
let fs = Fs { globals: palette(), tree: tree(&[], &[]), tess: two_face_square() };
|
||||
|
||||
Reference in New Issue
Block a user