Skip to main content

calib_targets_print/
lib.rs

1//! Printable calibration target generation.
2#![deny(missing_docs)]
3
4mod model;
5mod render;
6mod render_dxf;
7
8pub use model::{
9    stem_paths, CharucoTargetSpec, ChessboardTargetSpec, MarkerBoardTargetSpec, MarkerCircleSpec,
10    PageOrientation, PageSize, PageSpec, PrintableTargetDocument, PrintableTargetError,
11    PuzzleBoardTargetSpec, RenderOptions, ResolvedTargetLayout, ResolvedTargetPoint, StemPaths,
12    TargetSpec,
13};
14pub use render::{render_target_bundle, GeneratedTargetBundle};
15
16use std::{
17    fs,
18    path::{Path, PathBuf},
19};
20
21/// Paths of the files written by [`write_target_bundle`].
22///
23/// Marked `#[non_exhaustive]` (mirroring [`StemPaths`] and
24/// [`GeneratedTargetBundle`]) so that future formats can be added
25/// without breaking cross-crate consumers.
26#[non_exhaustive]
27#[derive(Clone, Debug, PartialEq, Eq)]
28pub struct WrittenTargetBundle {
29    /// Path of the written JSON description.
30    pub json_path: PathBuf,
31    /// Path of the written SVG rendering.
32    pub svg_path: PathBuf,
33    /// Path of the written PNG rendering.
34    pub png_path: PathBuf,
35    /// Path of the written DXF rendering (photolithography handoff).
36    pub dxf_path: PathBuf,
37}
38
39impl WrittenTargetBundle {
40    /// Construct a `WrittenTargetBundle` from explicit per-format paths.
41    pub fn new(
42        json_path: PathBuf,
43        svg_path: PathBuf,
44        png_path: PathBuf,
45        dxf_path: PathBuf,
46    ) -> Self {
47        Self {
48            json_path,
49            svg_path,
50            png_path,
51            dxf_path,
52        }
53    }
54}
55
56/// Render a printable target and write the JSON, SVG, PNG, and DXF
57/// files to disk, deriving their paths from `output_stem`.
58///
59/// Parent directories are created as needed.
60pub fn write_target_bundle(
61    document: &PrintableTargetDocument,
62    output_stem: impl AsRef<Path>,
63) -> Result<WrittenTargetBundle, PrintableTargetError> {
64    let bundle = render_target_bundle(document)?;
65    let paths = StemPaths::from_stem(output_stem);
66    for path in [&paths.json, &paths.svg, &paths.png, &paths.dxf] {
67        if let Some(parent) = path.parent() {
68            fs::create_dir_all(parent)?;
69        }
70    }
71    fs::write(&paths.json, bundle.json_text)?;
72    fs::write(&paths.svg, bundle.svg_text)?;
73    fs::write(&paths.png, bundle.png_bytes)?;
74    fs::write(&paths.dxf, bundle.dxf_text)?;
75    Ok(WrittenTargetBundle::new(
76        paths.json, paths.svg, paths.png, paths.dxf,
77    ))
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83    use tempfile::tempdir;
84
85    #[test]
86    fn writes_bundle_files() {
87        let dir = tempdir().expect("tempdir");
88        let doc = PrintableTargetDocument::new(TargetSpec::Chessboard(ChessboardTargetSpec {
89            inner_rows: 6,
90            inner_cols: 8,
91            square_size_mm: 20.0,
92        }));
93        let paths = write_target_bundle(&doc, dir.path().join("sample")).expect("bundle");
94        assert!(paths.json_path.is_file());
95        assert!(paths.svg_path.is_file());
96        assert!(paths.png_path.is_file());
97        assert!(paths.dxf_path.is_file());
98    }
99}