acadrust 0.6.7

A pure Rust library for reading and writing CAD files in DXF format (ASCII and Binary) and DWG format (Binary).
Documentation
use std::collections::BTreeMap;
use std::io::Cursor;

use acadrust::entities::{DimensionLinear, EntityType, MText, Polyline2D, PolylineFlags, Vertex2D};
use acadrust::types::{DxfVersion, LineWeight, Vector3};
use acadrust::{CadDocument, DxfReader, DxfWriter};

fn record_fields(dxf: &[u8], entity_type: &str) -> BTreeMap<i32, String> {
    let source = String::from_utf8(dxf.to_vec()).unwrap();
    let lines: Vec<_> = source.lines().map(str::trim).collect();
    let pairs: Vec<_> = lines
        .chunks_exact(2)
        .map(|p| (p[0].parse::<i32>().unwrap(), p[1]))
        .collect();
    let start = pairs
        .iter()
        .position(|pair| pair == &(0, entity_type))
        .unwrap();
    pairs[start + 1..]
        .iter()
        .take_while(|(code, _)| *code != 0)
        .map(|(code, value)| (*code, (*value).to_string()))
        .collect()
}

#[test]
fn dimension_writes_all_common_entity_properties_and_insertion_point() {
    let mut document = CadDocument::with_version(DxfVersion::AC1032);
    let mut dimension = DimensionLinear::new(Vector3::ZERO, Vector3::new(10.0, 0.0, 0.0));
    dimension.base.common.invisible = true;
    dimension.base.common.linetype = "DASHED".to_string();
    dimension.base.common.linetype_scale = 2.0;
    dimension.base.common.line_weight = LineWeight::Value(50);
    dimension.base.insertion_point = Vector3::new(100.0, 50.0, 0.0);
    document
        .add_entity(EntityType::Dimension(Box::new(
            acadrust::entities::Dimension::Linear(dimension),
        )))
        .unwrap();

    let fields = record_fields(
        &DxfWriter::new(&document).write_to_vec().unwrap(),
        "DIMENSION",
    );
    assert_eq!(fields[&6], "DASHED");
    assert!((fields[&48].parse::<f64>().unwrap() - 2.0).abs() < 1e-12);
    assert_eq!(fields[&60], "1");
    assert_eq!(fields[&370], "50");
    assert!((fields[&12].parse::<f64>().unwrap() - 100.0).abs() < 1e-12);
    assert!((fields[&22].parse::<f64>().unwrap() - 50.0).abs() < 1e-12);

    let restored = DxfReader::from_reader(Cursor::new(
        DxfWriter::new(&document).write_to_vec().unwrap(),
    ))
    .unwrap()
    .read()
    .unwrap();
    let EntityType::Dimension(dimension) = restored
        .entities()
        .find(|entity| matches!(entity, EntityType::Dimension(_)))
        .unwrap()
    else {
        unreachable!()
    };
    assert!(dimension.base().common.invisible);
    assert_eq!(dimension.base().common.linetype, "DASHED");
    assert_eq!(dimension.base().common.linetype_scale, 2.0);
    assert_eq!(dimension.base().common.line_weight, LineWeight::Value(50));
    assert_eq!(
        dimension.base().insertion_point,
        Vector3::new(100.0, 50.0, 0.0)
    );

    let dwg = acadrust::DwgWriter::write_to_vec(&document).unwrap();
    let from_dwg = acadrust::DwgReader::from_stream(Cursor::new(dwg))
        .read()
        .unwrap();
    let dimension = from_dwg
        .entities()
        .find_map(|entity| match entity {
            EntityType::Dimension(dimension) => Some(dimension),
            _ => None,
        })
        .unwrap();
    assert!(dimension.base().common.invisible);
    assert_eq!(
        dimension.base().insertion_point,
        Vector3::new(100.0, 50.0, 0.0)
    );
}

#[test]
fn heavy_polyline_writes_ocs_normal_and_mirrored_mtext_writes_wcs_direction() {
    let mut document = CadDocument::with_version(DxfVersion::AC1032);
    let mut polyline = Polyline2D::new();
    polyline.flags = PolylineFlags::CURVE_FIT;
    polyline.normal = Vector3::new(0.0, 0.0, -1.0);
    for point in [Vector3::new(1.0, 2.0, 0.0), Vector3::new(3.0, 4.0, 0.0)] {
        polyline.add_vertex(Vertex2D::new(point));
    }
    document
        .add_entity(EntityType::Polyline2D(polyline))
        .unwrap();

    let mut mtext = MText::with_value("mirrored", Vector3::ZERO);
    mtext.normal = Vector3::new(0.0, 0.0, -1.0);
    mtext.rotation = 30.0_f64.to_radians();
    document
        .add_entity(EntityType::MText(Box::new(mtext)))
        .unwrap();

    let dxf = DxfWriter::new(&document).write_to_vec().unwrap();
    let polyline_fields = record_fields(&dxf, "POLYLINE");
    assert_eq!(polyline_fields[&70], "2");
    assert!(polyline_fields[&210].parse::<f64>().unwrap().abs() < 1e-12);
    assert!((polyline_fields[&230].parse::<f64>().unwrap() + 1.0).abs() < 1e-12);

    let mtext_fields = record_fields(&dxf, "MTEXT");
    assert!(!mtext_fields.contains_key(&50));
    assert!(
        (mtext_fields[&11].parse::<f64>().unwrap() - 30.0_f64.to_radians().cos()).abs() < 1e-12
    );
    assert!(
        (mtext_fields[&21].parse::<f64>().unwrap() - 30.0_f64.to_radians().sin()).abs() < 1e-12
    );

    let restored = DxfReader::from_reader(Cursor::new(dxf))
        .unwrap()
        .read()
        .unwrap();
    let restored_mtext = restored
        .entities()
        .find_map(|entity| match entity {
            EntityType::MText(mtext) => Some(mtext),
            _ => None,
        })
        .unwrap();
    assert!((restored_mtext.rotation - 30.0_f64.to_radians()).abs() < 1e-12);
    assert_eq!(restored_mtext.normal, Vector3::new(0.0, 0.0, -1.0));

    let binary = DxfWriter::new_binary(&document).write_to_vec().unwrap();
    let binary_restored = DxfReader::from_reader(Cursor::new(binary))
        .unwrap()
        .read()
        .unwrap();
    let binary_mtext = binary_restored
        .entities()
        .find_map(|entity| match entity {
            EntityType::MText(mtext) => Some(mtext),
            _ => None,
        })
        .unwrap();
    assert!((binary_mtext.rotation - 30.0_f64.to_radians()).abs() < 1e-12);

    let dwg = acadrust::DwgWriter::write_to_vec(&document).unwrap();
    let from_dwg = acadrust::DwgReader::from_stream(Cursor::new(dwg))
        .read()
        .unwrap();
    let dxf_from_dwg = DxfWriter::new(&from_dwg).write_to_vec().unwrap();
    let from_dwg_dxf = DxfReader::from_reader(Cursor::new(dxf_from_dwg))
        .unwrap()
        .read()
        .unwrap();
    let dwg_mtext = from_dwg_dxf
        .entities()
        .find_map(|entity| match entity {
            EntityType::MText(mtext) => Some(mtext),
            _ => None,
        })
        .unwrap();
    assert!((dwg_mtext.rotation - 30.0_f64.to_radians()).abs() < 1e-12);
}