use std::fs::File;
use std::io::{BufWriter, Cursor, Seek, Write};
use std::path::Path;
use crate::document::{CadDocument, HeaderVariables};
use crate::error::{DxfError, Result};
use crate::types::{DxfVersion, Handle};
use super::dwg_stream_writers::{
app_info_writer, aux_header_writer, classes_writer, handle_writer, header_writer,
preview_writer, DwgObjectWriter,
};
use super::file_headers::{
section_names, DwgFileHeaderWriterAC15, DwgFileHeaderWriterAC18, DwgFileHeaderWriterAC21,
};
pub struct DwgWriter;
impl DwgWriter {
pub fn write_to_file<P: AsRef<Path>>(path: P, document: &CadDocument) -> Result<()> {
let file = File::create(path)?;
let writer = BufWriter::new(file);
Self::write_to_writer(writer, document)
}
pub fn write_to_writer<W: Write + Seek>(mut output: W, document: &CadDocument) -> Result<()> {
validate_version(document.version)?;
let version = document.version;
if uses_ac21_format(version) {
write_ac21(&mut output, document, version)
} else if uses_paged_format(version) {
write_ac18(&mut output, document, version)
} else {
write_ac15(&mut output, document, version)
}
}
pub fn write_to_file_no_lz77<P: AsRef<Path>>(path: P, document: &CadDocument) -> Result<()> {
let file = File::create(path)?;
let writer = BufWriter::new(file);
Self::write_to_writer_no_lz77(writer, document)
}
pub fn write_to_writer_no_lz77<W: Write + Seek>(
mut output: W,
document: &CadDocument,
) -> Result<()> {
validate_version(document.version)?;
write_ac21_impl(&mut output, document, document.version, true)
}
pub fn write_to_vec(document: &CadDocument) -> Result<Vec<u8>> {
let mut buffer = Cursor::new(Vec::new());
Self::write_to_writer(&mut buffer, document)?;
Ok(buffer.into_inner())
}
}
fn validate_version(version: DxfVersion) -> Result<()> {
match version {
DxfVersion::Unknown => Err(DxfError::UnsupportedVersion(
"Unknown version".to_string(),
)),
_ => Ok(()),
}
}
fn uses_ac21_format(version: DxfVersion) -> bool {
version == DxfVersion::AC1021
}
fn uses_paged_format(version: DxfVersion) -> bool {
version >= DxfVersion::AC1018
}
fn prepare_header(
document: &CadDocument,
handle_map: &[(u64, u32)],
extents: &Option<crate::types::BoundingBox3D>,
) -> HeaderVariables {
let mut h = document.header.clone();
h.block_control_handle = document.block_records.handle();
h.layer_control_handle = document.layers.handle();
h.style_control_handle = document.text_styles.handle();
h.linetype_control_handle = document.line_types.handle();
h.view_control_handle = document.views.handle();
h.ucs_control_handle = document.ucss.handle();
h.vport_control_handle = document.vports.handle();
h.appid_control_handle = document.app_ids.handle();
h.dimstyle_control_handle = document.dim_styles.handle();
if h.named_objects_dict_handle.is_null() {
h.named_objects_dict_handle = find_root_dict_handle(&document.objects);
}
if !h.named_objects_dict_handle.is_null() && !document.objects.contains_key(&h.named_objects_dict_handle) {
h.named_objects_dict_handle = find_root_dict_handle(&document.objects);
}
if let Some(crate::objects::ObjectType::Dictionary(root_dict)) =
document.objects.get(&h.named_objects_dict_handle)
{
if let Some(handle) = root_dict.get("ACAD_GROUP") {
h.acad_group_dict_handle = handle;
}
if let Some(handle) = root_dict.get("ACAD_MLINESTYLE") {
h.acad_mlinestyle_dict_handle = handle;
}
if let Some(handle) = root_dict.get("ACAD_LAYOUT") {
h.acad_layout_dict_handle = handle;
}
if let Some(handle) = root_dict.get("ACAD_PLOTSETTINGS") {
h.acad_plotsettings_dict_handle = handle;
}
if let Some(handle) = root_dict.get("ACAD_PLOTSTYLENAME") {
h.acad_plotstylename_dict_handle = handle;
}
if let Some(handle) = root_dict.get("ACAD_MATERIAL") {
h.acad_material_dict_handle = handle;
}
if let Some(handle) = root_dict.get("ACAD_COLOR") {
h.acad_color_dict_handle = handle;
}
if let Some(handle) = root_dict.get("ACAD_VISUALSTYLE") {
h.acad_visualstyle_dict_handle = handle;
}
}
if let Some(lt) = document.line_types.get("ByLayer") {
h.bylayer_linetype_handle = lt.handle;
}
if let Some(lt) = document.line_types.get("ByBlock") {
h.byblock_linetype_handle = lt.handle;
}
if let Some(lt) = document.line_types.get("Continuous") {
h.continuous_linetype_handle = lt.handle;
}
if let Some(br) = document.block_records.get("*Model_Space") {
h.model_space_block_handle = br.handle;
}
if let Some(br) = document.block_records.get("*Paper_Space") {
h.paper_space_block_handle = br.handle;
}
if h.current_layer_handle.is_null() {
if let Some(layer) = document.layers.get("0") {
h.current_layer_handle = layer.handle;
}
}
if h.current_text_style_handle.is_null() {
if let Some(style) = document.text_styles.get("Standard") {
h.current_text_style_handle = style.handle;
}
}
if h.current_dimstyle_handle.is_null() {
if let Some(ds) = document.dim_styles.get("Standard") {
h.current_dimstyle_handle = ds.handle;
}
}
if h.current_linetype_handle.is_null() {
h.current_linetype_handle = h.bylayer_linetype_handle;
}
if h.current_multiline_style_handle.is_null() {
for (_, obj) in &document.objects {
if let crate::objects::ObjectType::MLineStyle(mls) = obj {
if mls.name == "Standard" {
h.current_multiline_style_handle = mls.handle;
break;
}
}
}
}
let max_handle = handle_map.iter().map(|&(ha, _)| ha).max().unwrap_or(0);
if h.handle_seed <= max_handle {
h.handle_seed = max_handle + 1;
}
if let Some(ref ext) = extents {
h.model_space_extents_min = ext.min;
h.model_space_extents_max = ext.max;
}
h
}
fn find_root_dict_handle(
objects: &std::collections::HashMap<crate::types::Handle, crate::objects::ObjectType>,
) -> crate::types::Handle {
use crate::objects::ObjectType;
use crate::types::Handle;
let mut best_handle = Handle::NULL;
let mut best_entry_count = 0usize;
for (handle, obj) in objects {
if let ObjectType::Dictionary(dict) = obj {
if dict.owner.is_null() {
if dict.entries.len() > best_entry_count
|| (dict.entries.len() == best_entry_count && handle.value() > best_handle.value())
{
best_handle = *handle;
best_entry_count = dict.entries.len();
}
}
}
}
best_handle
}
fn write_ac15<W: Write + Seek>(
output: &mut W,
document: &CadDocument,
version: DxfVersion,
) -> Result<()> {
let mut fhw = DwgFileHeaderWriterAC15::new(version);
let obj_writer = DwgObjectWriter::new(document)?;
let (obj_data, handle_map_u32, extents, _sab_entries) = obj_writer.write();
let corrected_header = prepare_header(document, &handle_map_u32, &extents);
let maint = document.maintenance_version;
let header_data = header_writer::write_header(version, &corrected_header, maint);
fhw.add_section(section_names::HEADER, header_data);
let classes: Vec<_> = document.classes.iter().cloned().collect();
let classes_data = classes_writer::write_classes(version, &classes, maint);
fhw.add_section(section_names::CLASSES, classes_data);
fhw.add_section(section_names::ACDB_OBJECTS, obj_data);
let obj_free_space = build_obj_free_space(version, document, handle_map_u32.len());
fhw.add_section(section_names::OBJ_FREE_SPACE, obj_free_space);
let template = build_template();
fhw.add_section(section_names::TEMPLATE, template);
let aux_data = aux_header_writer::write_aux_header(version, &corrected_header);
fhw.add_section(section_names::AUX_HEADER, aux_data);
let section_offset = fhw.handle_section_offset() as i32;
let handle_map_i64: Vec<(u64, i64)> = handle_map_u32
.iter()
.map(|&(h, o)| (h, o as i64))
.collect();
let handles_data = handle_writer::write_handles(&handle_map_i64, section_offset);
fhw.add_section(section_names::HANDLES, handles_data);
let preview_data = preview_writer::write_preview(version);
fhw.add_section(section_names::PREVIEW, preview_data);
fhw.write_file(output)?;
Ok(())
}
fn write_ac18<W: Write + Seek>(
output: &mut W,
document: &CadDocument,
version: DxfVersion,
) -> Result<()> {
let mut fhw = DwgFileHeaderWriterAC18::new(version, document.maintenance_version, output)?;
const PAGE_SIZE: usize = 0x7400;
const SMALL_PAGE: usize = 0x80;
let obj_writer = DwgObjectWriter::new(document)?;
let (obj_data, handle_map_u32, extents, sab_entries) = obj_writer.write();
let corrected_header = prepare_header(document, &handle_map_u32, &extents);
let maint = document.maintenance_version;
let header_data = header_writer::write_header(version, &corrected_header, maint);
fhw.add_section(output, section_names::HEADER, &header_data, true, PAGE_SIZE)?;
let classes: Vec<_> = document.classes.iter().cloned().collect();
let classes_data = classes_writer::write_classes(version, &classes, maint);
fhw.add_section(output, section_names::CLASSES, &classes_data, true, PAGE_SIZE)?;
let summary_data = build_summary_info(version);
fhw.add_section(
output,
section_names::SUMMARY_INFO,
&summary_data,
false,
0x100,
)?;
let preview_data = preview_writer::write_preview(version);
fhw.add_section(output, section_names::PREVIEW, &preview_data, false, 0x400)?;
let app_info_data = app_info_writer::write_app_info(version);
fhw.add_section(output, section_names::APP_INFO, &app_info_data, false, SMALL_PAGE)?;
let file_dep_data = build_file_dep_list();
fhw.add_section(output, section_names::FILE_DEP_LIST, &file_dep_data, false, SMALL_PAGE)?;
let rev_history_data = build_rev_history();
fhw.add_section(output, section_names::REV_HISTORY, &rev_history_data, true, PAGE_SIZE)?;
let aux_data = aux_header_writer::write_aux_header(version, &corrected_header);
fhw.add_section(output, section_names::AUX_HEADER, &aux_data, true, PAGE_SIZE)?;
fhw.add_section(output, section_names::ACDB_OBJECTS, &obj_data, true, PAGE_SIZE)?;
if !sab_entries.is_empty() {
let acds_data = build_acds_prototype(&sab_entries);
fhw.add_section(output, section_names::ACDS_PROTOTYPE, &acds_data, true, PAGE_SIZE)?;
}
let obj_free_space = build_obj_free_space(version, document, handle_map_u32.len());
fhw.add_section(output, section_names::OBJ_FREE_SPACE, &obj_free_space, true, PAGE_SIZE)?;
let template = build_template();
fhw.add_section(output, section_names::TEMPLATE, &template, true, PAGE_SIZE)?;
let section_offset = fhw.handle_section_offset() as i32;
let handle_map_i64: Vec<(u64, i64)> = handle_map_u32
.iter()
.map(|&(h, o)| (h, o as i64))
.collect();
let handles_data = handle_writer::write_handles(&handle_map_i64, section_offset);
fhw.add_section(output, section_names::HANDLES, &handles_data, true, PAGE_SIZE)?;
fhw.write_file(output)?;
Ok(())
}
fn write_ac21<W: Write + Seek>(
output: &mut W,
document: &CadDocument,
version: DxfVersion,
) -> Result<()> {
write_ac21_impl(output, document, version, false)
}
fn write_ac21_impl<W: Write + Seek>(
output: &mut W,
document: &CadDocument,
version: DxfVersion,
skip_lz77: bool,
) -> Result<()> {
let mut fhw = DwgFileHeaderWriterAC21::new(version, output)?;
fhw.skip_lz77 = skip_lz77;
let obj_writer = DwgObjectWriter::new(document)?;
let (obj_data, handle_map_u32, extents, sab_entries) = obj_writer.write();
let corrected_header = prepare_header(document, &handle_map_u32, &extents);
let summary_data = build_summary_info(version);
fhw.add_section(output, section_names::SUMMARY_INFO, &summary_data)?;
let preview_data = preview_writer::write_preview(version);
fhw.add_section(output, section_names::PREVIEW, &preview_data)?;
let app_info_data = app_info_writer::write_app_info(version);
fhw.add_section(output, section_names::APP_INFO, &app_info_data)?;
let file_dep_data = build_file_dep_list();
fhw.add_section(output, section_names::FILE_DEP_LIST, &file_dep_data)?;
let rev_history_data = build_rev_history();
fhw.add_section(output, section_names::REV_HISTORY, &rev_history_data)?;
fhw.add_section(output, section_names::ACDB_OBJECTS, &obj_data)?;
if !sab_entries.is_empty() {
let acds_data = build_acds_prototype(&sab_entries);
fhw.add_section(output, section_names::ACDS_PROTOTYPE, &acds_data)?;
}
let obj_free_space = build_obj_free_space(version, document, handle_map_u32.len());
fhw.add_section(output, section_names::OBJ_FREE_SPACE, &obj_free_space)?;
let template = build_template();
fhw.add_section(output, section_names::TEMPLATE, &template)?;
let section_offset = fhw.handle_section_offset() as i32;
let handle_map_i64: Vec<(u64, i64)> = handle_map_u32
.iter()
.map(|&(h, o)| (h, o as i64))
.collect();
let handles_data = handle_writer::write_handles(&handle_map_i64, section_offset);
fhw.add_section(output, section_names::HANDLES, &handles_data)?;
let classes: Vec<_> = document.classes.iter().cloned().collect();
let maint = document.maintenance_version;
let classes_data = classes_writer::write_classes(version, &classes, maint);
fhw.add_section(output, section_names::CLASSES, &classes_data)?;
let aux_data = aux_header_writer::write_aux_header(version, &corrected_header);
fhw.add_section(output, section_names::AUX_HEADER, &aux_data)?;
let header_data = header_writer::write_header(version, &corrected_header, maint);
fhw.add_section(output, section_names::HEADER, &header_data)?;
fhw.write_file(output)?;
Ok(())
}
fn build_obj_free_space(
version: DxfVersion,
document: &CadDocument,
handle_count: usize,
) -> Vec<u8> {
let mut data = Vec::with_capacity(64);
data.extend_from_slice(&0i32.to_le_bytes());
data.extend_from_slice(&(handle_count as u32).to_le_bytes());
if version >= DxfVersion::AC1015 {
let _ = &document.header; }
data.extend_from_slice(&0i32.to_le_bytes()); data.extend_from_slice(&0i32.to_le_bytes());
data.extend_from_slice(&0u32.to_le_bytes());
data.push(4);
data.extend_from_slice(&0x00000032u32.to_le_bytes());
data.extend_from_slice(&0x00000000u32.to_le_bytes());
data.extend_from_slice(&0x00000064u32.to_le_bytes());
data.extend_from_slice(&0x00000000u32.to_le_bytes());
data.extend_from_slice(&0x00000200u32.to_le_bytes());
data.extend_from_slice(&0x00000000u32.to_le_bytes());
data.extend_from_slice(&0xFFFFFFFFu32.to_le_bytes());
data.extend_from_slice(&0x00000000u32.to_le_bytes());
data
}
fn build_template() -> Vec<u8> {
let mut data = Vec::with_capacity(4);
data.extend_from_slice(&0i32.to_le_bytes());
data
}
fn build_summary_info(version: DxfVersion) -> Vec<u8> {
let mut data = Vec::with_capacity(128);
let is_utf16 = version >= DxfVersion::AC1021;
for _ in 0..8 {
data.extend_from_slice(&1u16.to_le_bytes()); if is_utf16 {
data.push(0);
data.push(0);
} else {
data.push(0);
}
}
data.extend_from_slice(&0i32.to_le_bytes());
data.extend_from_slice(&0i32.to_le_bytes());
data.extend_from_slice(&0i32.to_le_bytes());
data.extend_from_slice(&0i32.to_le_bytes());
data.extend_from_slice(&0i32.to_le_bytes());
data.extend_from_slice(&0i32.to_le_bytes());
data.extend_from_slice(&0u16.to_le_bytes());
data.extend_from_slice(&0i32.to_le_bytes());
data.extend_from_slice(&0i32.to_le_bytes());
data
}
fn build_file_dep_list() -> Vec<u8> {
let mut data = Vec::with_capacity(8);
data.extend_from_slice(&0u32.to_le_bytes());
data.extend_from_slice(&0u32.to_le_bytes());
data
}
fn build_rev_history() -> Vec<u8> {
let mut data = Vec::with_capacity(16);
data.extend_from_slice(&0u32.to_le_bytes());
data.extend_from_slice(&0u32.to_le_bytes());
data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes());
data
}
fn build_acds_prototype(sab_entries: &[(Handle, Vec<u8>)]) -> Vec<u8> {
if sab_entries.is_empty() {
return Vec::new();
}
let (entity_handle, sab_data) = &sab_entries[0];
let handle_val = entity_handle.value() as u32;
let data2 = build_acds_data2_segment(handle_val, sab_data);
#[rustfmt::skip]
let data3: &[u8] = &[
0xAC, 0xD5, 0x5F, 0x64, 0x61, 0x74, 0x61, 0x5F, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62,
];
let datidx = build_acds_datidx();
let schdat = ACDS_SCHDAT_TEMPLATE;
let schidx = ACDS_SCHIDX_TEMPLATE;
let search = build_acds_search_segment(handle_val);
let off_data2 = 0x80u32;
let off_data3 = off_data2 + data2.len() as u32;
let off_datidx = off_data3 + data3.len() as u32;
let off_schdat = off_datidx + datidx.len() as u32;
let off_schidx = off_schdat + schdat.len() as u32;
let off_search = off_schidx + schidx.len() as u32;
let off_segidx = off_search + search.len() as u32;
let segidx_size = 192u32;
let segidx = build_acds_segidx(
off_segidx, segidx_size,
off_data2, data2.len() as u32,
off_data3, data3.len() as u32,
off_datidx, datidx.len() as u32,
off_schdat, schdat.len() as u32,
off_schidx, schidx.len() as u32,
off_search, search.len() as u32,
);
let total_size = off_segidx + segidx_size;
let segidx_offset = off_segidx; let header = build_acds_jard_header(
sab_entries.len() as u32,
segidx_offset,
total_size,
);
let mut result = Vec::with_capacity(total_size as usize);
result.extend_from_slice(&header);
result.extend_from_slice(&data2);
result.extend_from_slice(data3);
result.extend_from_slice(&datidx);
result.extend_from_slice(schdat);
result.extend_from_slice(schidx);
result.extend_from_slice(&search);
result.extend_from_slice(&segidx);
debug_assert_eq!(result.len(), total_size as usize);
result
}
fn build_acds_jard_header(
num_records: u32,
segidx_offset: u32,
total_size: u32,
) -> Vec<u8> {
let mut h = vec![0u8; 128];
h[0..4].copy_from_slice(b"jard");
h[4..8].copy_from_slice(&128u32.to_le_bytes());
h[8..12].copy_from_slice(&2u32.to_le_bytes());
h[12..16].copy_from_slice(&2u32.to_le_bytes());
h[16..20].copy_from_slice(&0u32.to_le_bytes());
h[20..24].copy_from_slice(&num_records.to_le_bytes());
h[24..32].copy_from_slice(&(segidx_offset as u64).to_le_bytes());
h[32..36].copy_from_slice(&8u32.to_le_bytes());
h[36..40].copy_from_slice(&6u32.to_le_bytes());
h[40..44].copy_from_slice(&4u32.to_le_bytes());
h[44..48].copy_from_slice(&7u32.to_le_bytes());
h[48..52].copy_from_slice(&0u32.to_le_bytes());
h[52..60].copy_from_slice(&(total_size as u64).to_le_bytes());
h
}
fn build_acds_data2_segment(handle_val: u32, sab_data: &[u8]) -> Vec<u8> {
let sab_len = sab_data.len();
let raw_size = 84 + sab_len;
let seg_size = align16(raw_size);
let padding = seg_size - raw_size;
let mut seg = Vec::with_capacity(seg_size);
seg.extend_from_slice(&[0xAC, 0xD5, 0x5F, 0x64, 0x61, 0x74, 0x61, 0x5F]); seg.extend_from_slice(&2u32.to_le_bytes()); seg.extend_from_slice(&0u32.to_le_bytes()); seg.extend_from_slice(&(seg_size as u64).to_le_bytes()); seg.extend_from_slice(&1u64.to_le_bytes()); seg.extend_from_slice(&0u32.to_le_bytes()); seg.extend_from_slice(&5u32.to_le_bytes()); seg.extend_from_slice(&[0x55; 8]);
seg.extend_from_slice(&0x14u32.to_le_bytes()); seg.extend_from_slice(&1u32.to_le_bytes()); seg.extend_from_slice(&(handle_val as u64).to_le_bytes()); seg.extend_from_slice(&0u32.to_le_bytes()); seg.extend_from_slice(&[0x62; 12]); seg.extend_from_slice(&(sab_len as u32).to_le_bytes());
seg.extend_from_slice(sab_data);
seg.extend(std::iter::repeat(0x70u8).take(padding));
debug_assert_eq!(seg.len(), seg_size);
seg
}
fn build_acds_datidx() -> Vec<u8> {
let mut seg = vec![0x70u8; 128];
seg[0..8].copy_from_slice(&[0xAC, 0xD5, 0x64, 0x61, 0x74, 0x69, 0x64, 0x78]); seg[8..12].copy_from_slice(&4u32.to_le_bytes()); seg[12..16].copy_from_slice(&0u32.to_le_bytes()); seg[16..24].copy_from_slice(&128u64.to_le_bytes()); seg[24..32].copy_from_slice(&1u64.to_le_bytes()); seg[32..40].copy_from_slice(&0u64.to_le_bytes()); seg[40..48].copy_from_slice(&[0x55; 8]);
seg[48..52].copy_from_slice(&1u32.to_le_bytes());
seg[52..56].copy_from_slice(&0u32.to_le_bytes());
seg[56..60].copy_from_slice(&2u32.to_le_bytes());
seg[60..64].copy_from_slice(&0u32.to_le_bytes());
seg[64..68].copy_from_slice(&1u32.to_le_bytes());
seg
}
fn build_acds_search_segment(handle_val: u32) -> Vec<u8> {
let mut seg = vec![0x70u8; 192];
seg[0..8].copy_from_slice(&[0xAC, 0xD5, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68]); seg[8..12].copy_from_slice(&7u32.to_le_bytes()); seg[12..16].copy_from_slice(&0u32.to_le_bytes()); seg[16..24].copy_from_slice(&192u64.to_le_bytes()); seg[24..32].copy_from_slice(&1u64.to_le_bytes()); seg[32..40].copy_from_slice(&0u64.to_le_bytes()); seg[40..48].copy_from_slice(&[0x55; 8]);
let content: &[u8] = &[
0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, ];
seg[48..48 + content.len()].copy_from_slice(content);
seg[84..88].copy_from_slice(&handle_val.to_le_bytes());
seg[88..92].copy_from_slice(&0u32.to_le_bytes());
let tail: &[u8] = &[
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
seg[92..92 + tail.len()].copy_from_slice(tail);
seg
}
#[allow(clippy::too_many_arguments)]
fn build_acds_segidx(
off_segidx: u32, sz_segidx: u32,
off_data2: u32, sz_data2: u32,
off_data3: u32, sz_data3: u32,
off_datidx: u32, sz_datidx: u32,
off_schdat: u32, sz_schdat: u32,
off_schidx: u32, sz_schidx: u32,
off_search: u32, sz_search: u32,
) -> Vec<u8> {
let mut seg = vec![0x70u8; sz_segidx as usize];
seg[0..8].copy_from_slice(&[0xAC, 0xD5, 0x73, 0x65, 0x67, 0x69, 0x64, 0x78]); seg[8..12].copy_from_slice(&1u32.to_le_bytes()); seg[12..16].copy_from_slice(&0u32.to_le_bytes()); seg[16..24].copy_from_slice(&(sz_segidx as u64).to_le_bytes()); seg[24..32].copy_from_slice(&1u64.to_le_bytes()); seg[32..40].copy_from_slice(&0u64.to_le_bytes()); seg[40..48].copy_from_slice(&[0x55; 8]);
let mut pos = 48;
write_segidx_entry(&mut seg, pos, 0, 0); pos += 12;
write_segidx_entry(&mut seg, pos, off_segidx, sz_segidx); pos += 12;
write_segidx_entry(&mut seg, pos, off_data2, sz_data2); pos += 12;
write_segidx_entry(&mut seg, pos, off_data3, sz_data3); pos += 12;
write_segidx_entry(&mut seg, pos, off_datidx, sz_datidx); pos += 12;
write_segidx_entry(&mut seg, pos, off_schdat, sz_schdat); pos += 12;
write_segidx_entry(&mut seg, pos, off_schidx, sz_schidx); pos += 12;
write_segidx_entry(&mut seg, pos, off_search, sz_search);
seg
}
fn write_segidx_entry(buf: &mut [u8], pos: usize, offset: u32, size: u32) {
buf[pos..pos + 4].copy_from_slice(&offset.to_le_bytes());
buf[pos + 4..pos + 8].copy_from_slice(&0u32.to_le_bytes());
buf[pos + 8..pos + 12].copy_from_slice(&size.to_le_bytes());
}
fn align16(n: usize) -> usize {
(n + 15) & !15
}
#[rustfmt::skip]
const ACDS_SCHDAT_TEMPLATE: &[u8] = &[
0xAC, 0xD5, 0x73, 0x63, 0x68, 0x64, 0x61, 0x74, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00,
0x02, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00,
0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x06, 0x00,
0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00,
0x73, 0x73, 0x73, 0x07, 0x00, 0x00, 0x00,
0x41, 0x63, 0x44, 0x62, 0x44, 0x73, 0x3A, 0x3A, 0x49, 0x44, 0x00,
0x54, 0x68, 0x75, 0x6D, 0x62, 0x6E, 0x61, 0x69, 0x6C, 0x5F, 0x44, 0x61, 0x74, 0x61, 0x00,
0x41, 0x53, 0x4D, 0x5F, 0x44, 0x61, 0x74, 0x61, 0x00,
0x41, 0x63, 0x44, 0x62, 0x44, 0x73, 0x3A, 0x3A,
0x54, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x73, 0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x44,
0x61, 0x74, 0x61, 0x00,
0x41, 0x63, 0x44, 0x62, 0x44, 0x73, 0x3A, 0x3A, 0x4C, 0x65, 0x67, 0x61, 0x63, 0x79, 0x00,
0x41, 0x63, 0x44, 0x73, 0x3A, 0x49, 0x6E, 0x64, 0x65, 0x78, 0x61, 0x62, 0x6C, 0x65, 0x00,
0x41, 0x63, 0x44, 0x62, 0x44, 0x73, 0x3A, 0x3A, 0x48, 0x61, 0x6E, 0x64, 0x6C, 0x65, 0x41,
0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x00,
0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70,
];
#[rustfmt::skip]
const ACDS_SCHIDX_TEMPLATE: &[u8] = &[
0xAC, 0xD5, 0x73, 0x63, 0x68, 0x69, 0x64, 0x78, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0xC0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0xD2, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0xE4, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0xF6, 0x00, 0x00, 0x00,
0x0C, 0xF1, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x18, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x28, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x38, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00,
0x41, 0x63, 0x44, 0x62, 0x5F, 0x54, 0x68, 0x75, 0x6D, 0x62, 0x6E, 0x61,
0x69, 0x6C, 0x5F, 0x53, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x00,
0x41, 0x63, 0x44, 0x62, 0x33, 0x44,
0x53, 0x6F, 0x6C, 0x69, 0x64, 0x5F, 0x41, 0x53, 0x4D, 0x5F, 0x44, 0x61, 0x74, 0x61, 0x00,
0x41,
0x63, 0x44, 0x62, 0x44, 0x73, 0x3A, 0x3A, 0x54, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x73,
0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x44, 0x61, 0x74, 0x61, 0x53, 0x63, 0x68, 0x65, 0x6D, 0x61,
0x00,
0x41, 0x63, 0x44, 0x62, 0x44, 0x73, 0x3A, 0x3A, 0x4C, 0x65, 0x67, 0x61, 0x63, 0x79,
0x53, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x00,
0x41, 0x63, 0x44, 0x62, 0x44, 0x73, 0x3A, 0x3A, 0x49, 0x6E,
0x64, 0x65, 0x78, 0x65, 0x64, 0x50, 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, 0x79, 0x53, 0x63, 0x68,
0x65, 0x6D, 0x61, 0x00,
0x41, 0x63, 0x44, 0x62, 0x44, 0x73, 0x3A, 0x3A, 0x48, 0x61, 0x6E, 0x64,
0x6C, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x53, 0x63, 0x68, 0x65, 0x6D,
0x61, 0x00,
0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70,
0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70,
0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70,
];
#[cfg(test)]
mod tests {
use super::*;
use crate::document::CadDocument;
use crate::types::{DxfVersion, Handle};
#[test]
fn test_validate_version_r2007_ok() {
assert!(validate_version(DxfVersion::AC1021).is_ok());
}
#[test]
fn test_validate_version_unknown_rejected() {
let result = validate_version(DxfVersion::Unknown);
assert!(result.is_err());
}
#[test]
fn test_validate_version_r2000_ok() {
assert!(validate_version(DxfVersion::AC1015).is_ok());
}
#[test]
fn test_validate_version_r2004_ok() {
assert!(validate_version(DxfVersion::AC1018).is_ok());
}
#[test]
fn test_validate_version_r2010_ok() {
assert!(validate_version(DxfVersion::AC1024).is_ok());
}
#[test]
fn test_build_template() {
let t = build_template();
assert_eq!(t.len(), 4);
assert_eq!(i32::from_le_bytes([t[0], t[1], t[2], t[3]]), 0); }
#[test]
fn test_build_obj_free_space() {
let doc = CadDocument::new();
let data = build_obj_free_space(DxfVersion::AC1015, &doc, 42);
assert_eq!(data.len(), 4 + 4 + 8 + 4 + 1 + 32);
let count = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
assert_eq!(count, 42);
}
#[test]
fn test_build_file_dep_list() {
let d = build_file_dep_list();
assert_eq!(d.len(), 8);
}
#[test]
fn test_build_rev_history() {
let d = build_rev_history();
assert_eq!(d.len(), 16);
}
#[test]
fn test_build_summary_info_ac18() {
let d = build_summary_info(DxfVersion::AC1018);
assert_eq!(d.len(), 58);
assert_eq!(u16::from_le_bytes([d[0], d[1]]), 1);
assert_eq!(d[2], 0);
assert_eq!(u16::from_le_bytes([d[3], d[4]]), 1);
}
#[test]
fn test_build_summary_info_ac21() {
let d = build_summary_info(DxfVersion::AC1021);
assert_eq!(d.len(), 66);
assert_eq!(u16::from_le_bytes([d[0], d[1]]), 1);
assert_eq!(d[2], 0);
assert_eq!(d[3], 0);
assert_eq!(u16::from_le_bytes([d[4], d[5]]), 1);
}
#[test]
fn test_write_to_vec_r2000() {
let mut doc = CadDocument::new();
doc.version = DxfVersion::AC1015;
let result = DwgWriter::write_to_vec(&doc);
assert!(result.is_ok());
let bytes = result.unwrap();
assert!(bytes.len() > 100);
let magic = std::str::from_utf8(&bytes[0..6]).unwrap_or("");
assert_eq!(magic, "AC1015");
}
#[test]
fn test_write_to_vec_r2004() {
let mut doc = CadDocument::new();
doc.version = DxfVersion::AC1018;
let result = DwgWriter::write_to_vec(&doc);
assert!(result.is_ok());
let bytes = result.unwrap();
assert!(bytes.len() > 0x100);
let magic = std::str::from_utf8(&bytes[0..6]).unwrap_or("");
assert_eq!(magic, "AC1018");
}
#[test]
fn test_write_to_vec_r2010() {
let mut doc = CadDocument::new();
doc.version = DxfVersion::AC1024;
let result = DwgWriter::write_to_vec(&doc);
assert!(result.is_ok());
let bytes = result.unwrap();
assert!(bytes.len() > 0x100);
}
#[test]
fn test_write_to_vec_r2013() {
let mut doc = CadDocument::new();
doc.version = DxfVersion::AC1027;
let result = DwgWriter::write_to_vec(&doc);
assert!(result.is_ok());
}
#[test]
fn test_write_to_vec_r2018() {
let mut doc = CadDocument::new();
doc.version = DxfVersion::AC1032;
let result = DwgWriter::write_to_vec(&doc);
assert!(result.is_ok());
}
#[test]
fn test_write_to_vec_r2007() {
let mut doc = CadDocument::new();
doc.version = DxfVersion::AC1021;
let result = DwgWriter::write_to_vec(&doc);
assert!(result.is_ok(), "AC1021 writing should succeed");
let bytes = result.unwrap();
assert!(bytes.len() > 0x480, "AC1021 file should be larger than header");
let magic = std::str::from_utf8(&bytes[0..6]).unwrap_or("");
assert_eq!(magic, "AC1021");
}
#[test]
fn test_uses_ac21_format() {
assert!(uses_ac21_format(DxfVersion::AC1021));
assert!(!uses_ac21_format(DxfVersion::AC1018));
assert!(!uses_ac21_format(DxfVersion::AC1024));
assert!(!uses_ac21_format(DxfVersion::AC1015));
}
#[test]
fn test_write_to_vec_r14() {
let mut doc = CadDocument::new();
doc.version = DxfVersion::AC1014;
let result = DwgWriter::write_to_vec(&doc);
assert!(result.is_ok());
let bytes = result.unwrap();
let magic = std::str::from_utf8(&bytes[0..6]).unwrap_or("");
assert_eq!(magic, "AC1014");
}
#[test]
fn test_roundtrip_file_write() {
let doc = CadDocument::new();
let mut doc2 = doc.clone();
doc2.version = DxfVersion::AC1015;
let bytes = DwgWriter::write_to_vec(&doc2).unwrap();
assert!(bytes.len() > 200, "DWG file should be non-trivial");
}
#[test]
fn test_prepare_header_syncs_null_handles() {
let mut doc = CadDocument::new();
doc.version = DxfVersion::AC1015;
let correct_block_control = doc.block_records.handle();
let correct_layer_control = doc.layers.handle();
let correct_style_control = doc.text_styles.handle();
let correct_ltype_control = doc.line_types.handle();
let correct_view_control = doc.views.handle();
let correct_ucs_control = doc.ucss.handle();
let correct_vport_control = doc.vports.handle();
let correct_appid_control = doc.app_ids.handle();
let correct_dimstyle_control = doc.dim_styles.handle();
let correct_root_dict = doc.header.named_objects_dict_handle;
doc.header.block_control_handle = Handle::NULL;
doc.header.layer_control_handle = Handle::NULL;
doc.header.style_control_handle = Handle::NULL;
doc.header.linetype_control_handle = Handle::NULL;
doc.header.view_control_handle = Handle::NULL;
doc.header.ucs_control_handle = Handle::NULL;
doc.header.vport_control_handle = Handle::NULL;
doc.header.appid_control_handle = Handle::NULL;
doc.header.dimstyle_control_handle = Handle::NULL;
doc.header.named_objects_dict_handle = Handle::NULL;
doc.header.acad_group_dict_handle = Handle::NULL;
doc.header.acad_mlinestyle_dict_handle = Handle::NULL;
doc.header.acad_layout_dict_handle = Handle::NULL;
doc.header.bylayer_linetype_handle = Handle::NULL;
doc.header.byblock_linetype_handle = Handle::NULL;
doc.header.continuous_linetype_handle = Handle::NULL;
doc.header.current_layer_handle = Handle::NULL;
doc.header.current_text_style_handle = Handle::NULL;
doc.header.current_dimstyle_handle = Handle::NULL;
doc.header.current_linetype_handle = Handle::NULL;
let handle_map = vec![(1u64, 0u32), (2, 100), (3, 200)]; let prepared = prepare_header(&doc, &handle_map, &None);
assert_eq!(prepared.block_control_handle, correct_block_control,
"block_control_handle should be synced from block_records.handle()");
assert_eq!(prepared.layer_control_handle, correct_layer_control,
"layer_control_handle should be synced from layers.handle()");
assert_eq!(prepared.style_control_handle, correct_style_control,
"style_control_handle should be synced from text_styles.handle()");
assert_eq!(prepared.linetype_control_handle, correct_ltype_control,
"linetype_control_handle should be synced from line_types.handle()");
assert_eq!(prepared.view_control_handle, correct_view_control,
"view_control_handle should be synced from views.handle()");
assert_eq!(prepared.ucs_control_handle, correct_ucs_control,
"ucs_control_handle should be synced from ucss.handle()");
assert_eq!(prepared.vport_control_handle, correct_vport_control,
"vport_control_handle should be synced from vports.handle()");
assert_eq!(prepared.appid_control_handle, correct_appid_control,
"appid_control_handle should be synced from app_ids.handle()");
assert_eq!(prepared.dimstyle_control_handle, correct_dimstyle_control,
"dimstyle_control_handle should be synced from dim_styles.handle()");
assert_eq!(prepared.named_objects_dict_handle, correct_root_dict,
"named_objects_dict_handle should be found by scanning objects");
assert!(!prepared.named_objects_dict_handle.is_null(),
"named_objects_dict_handle must not be NULL");
assert!(!prepared.acad_group_dict_handle.is_null(),
"acad_group_dict_handle must be resolved from root dict");
assert!(!prepared.acad_mlinestyle_dict_handle.is_null(),
"acad_mlinestyle_dict_handle must be resolved from root dict");
assert!(!prepared.acad_layout_dict_handle.is_null(),
"acad_layout_dict_handle must be resolved from root dict");
assert!(!prepared.bylayer_linetype_handle.is_null(),
"bylayer_linetype_handle must be resolved");
assert!(!prepared.byblock_linetype_handle.is_null(),
"byblock_linetype_handle must be resolved");
assert!(!prepared.continuous_linetype_handle.is_null(),
"continuous_linetype_handle must be resolved");
assert!(!prepared.current_layer_handle.is_null(),
"current_layer_handle must be resolved");
assert!(!prepared.current_text_style_handle.is_null(),
"current_text_style_handle must be resolved");
assert!(!prepared.current_dimstyle_handle.is_null(),
"current_dimstyle_handle must be resolved");
assert!(!prepared.current_linetype_handle.is_null(),
"current_linetype_handle must be resolved (default to ByLayer)");
}
#[test]
fn test_prepare_header_null_handles_write_produces_valid_dwg() {
let mut doc = CadDocument::new();
doc.version = DxfVersion::AC1015;
doc.header.block_control_handle = Handle::NULL;
doc.header.layer_control_handle = Handle::NULL;
doc.header.named_objects_dict_handle = Handle::NULL;
let result = DwgWriter::write_to_vec(&doc);
assert!(result.is_ok(), "Writing with NULL headers should succeed after sync");
let bytes = result.unwrap();
assert!(bytes.len() > 200, "Output should be non-trivial");
}
fn make_sat_sample() -> &'static str {
"700 0 1 0\n\
@7 unknown 12 ACIS 7.0 NT 24 Wed Jan 01 00:00:00 2025 1.0 9.9999999999999995e-007 1e-010\n\
body $-1 $1 $-1 $-1 #\n\
lump $-1 $-1 $2 $0 #\n\
shell $-1 $-1 $-1 $3 $-1 $1 #\n\
face $-1 $-1 $-1 $4 $2 $5 forward single #\n\
loop $-1 $-1 $6 $3 #\n\
plane-surface $-1 0 0 5 0 0 1 1 0 0 forward_v I I I I #\n\
coedge $-1 $6 $6 $-1 $7 forward $4 $-1 #\n\
edge $-1 $8 0 $8 1 $6 $9 forward #\n\
vertex $-1 $7 $10 #\n\
straight-curve $-1 -5 -5 5 1 0 0 I I #\n\
point $-1 -5 -5 5 #\n\
End-of-ACIS-data\n"
}
#[test]
fn test_roundtrip_solid3d_r2000() {
use crate::entities::solid3d::Solid3D;
use crate::entities::EntityType;
use crate::io::dwg::DwgReader;
let mut doc = CadDocument::new();
doc.version = DxfVersion::AC1015;
let solid = Solid3D::from_sat(make_sat_sample());
let _ = doc.add_entity(EntityType::Solid3D(solid));
let bytes = DwgWriter::write_to_vec(&doc).expect("write R2000 should succeed");
assert!(bytes.len() > 200);
let mut reader = DwgReader::from_stream(std::io::Cursor::new(bytes));
let doc2 = reader.read().expect("read R2000 should succeed");
let solids: Vec<&Solid3D> = doc2.entities().filter_map(|e| {
if let EntityType::Solid3D(s) = e { Some(s) } else { None }
}).collect();
assert_eq!(solids.len(), 1, "should have exactly one Solid3D");
assert!(!solids[0].acis_data.is_binary, "R2000 should use SAT text");
assert!(solids[0].acis_data.sat_data.contains("body"), "SAT data must contain 'body'");
assert!(solids[0].acis_data.sat_data.contains("plane-surface"), "SAT data must contain 'plane-surface'");
}
#[test]
fn test_roundtrip_solid3d_r2004() {
use crate::entities::solid3d::Solid3D;
use crate::entities::EntityType;
use crate::io::dwg::DwgReader;
let mut doc = CadDocument::new();
doc.version = DxfVersion::AC1018;
let solid = Solid3D::from_sat(make_sat_sample());
let _ = doc.add_entity(EntityType::Solid3D(solid));
let bytes = DwgWriter::write_to_vec(&doc).expect("write R2004 should succeed");
assert!(bytes.len() > 200);
let mut reader = DwgReader::from_stream(std::io::Cursor::new(bytes));
let doc2 = reader.read().expect("read R2004 should succeed");
let solids: Vec<&Solid3D> = doc2.entities().filter_map(|e| {
if let EntityType::Solid3D(s) = e { Some(s) } else { None }
}).collect();
assert_eq!(solids.len(), 1, "should have exactly one Solid3D");
assert!(!solids[0].acis_data.is_binary, "R2004 should use SAT text");
assert!(solids[0].acis_data.sat_data.contains("body"));
}
#[test]
fn test_roundtrip_solid3d_r2007() {
use crate::entities::solid3d::Solid3D;
use crate::entities::EntityType;
use crate::io::dwg::DwgReader;
let mut doc = CadDocument::new();
doc.version = DxfVersion::AC1021;
let solid = Solid3D::from_sat(make_sat_sample());
let _ = doc.add_entity(EntityType::Solid3D(solid));
let bytes = DwgWriter::write_to_vec(&doc).expect("write R2007 should succeed");
assert!(bytes.len() > 200);
let mut reader = DwgReader::from_stream(std::io::Cursor::new(bytes));
let doc2 = reader.read().expect("read R2007 should succeed");
let solids: Vec<&Solid3D> = doc2.entities().filter_map(|e| {
if let EntityType::Solid3D(s) = e { Some(s) } else { None }
}).collect();
assert_eq!(solids.len(), 1, "should have exactly one Solid3D");
assert!(solids[0].acis_data.has_data(), "should have ACIS data");
}
#[test]
fn test_roundtrip_region_r2000() {
use crate::entities::solid3d::Region;
use crate::entities::EntityType;
use crate::io::dwg::DwgReader;
let mut doc = CadDocument::new();
doc.version = DxfVersion::AC1015;
let region = Region::from_sat(make_sat_sample());
let _ = doc.add_entity(EntityType::Region(region));
let bytes = DwgWriter::write_to_vec(&doc).expect("write R2000 should succeed");
let mut reader = DwgReader::from_stream(std::io::Cursor::new(bytes));
let doc2 = reader.read().expect("read R2000 should succeed");
let regions: Vec<&Region> = doc2.entities().filter_map(|e| {
if let EntityType::Region(r) = e { Some(r) } else { None }
}).collect();
assert_eq!(regions.len(), 1, "should have exactly one Region");
assert!(regions[0].acis_data.sat_data.contains("body"));
}
#[test]
fn test_roundtrip_body_r2004() {
use crate::entities::solid3d::Body;
use crate::entities::EntityType;
use crate::io::dwg::DwgReader;
let mut doc = CadDocument::new();
doc.version = DxfVersion::AC1018;
let body = Body::from_sat(make_sat_sample());
let _ = doc.add_entity(EntityType::Body(body));
let bytes = DwgWriter::write_to_vec(&doc).expect("write R2004 should succeed");
let mut reader = DwgReader::from_stream(std::io::Cursor::new(bytes));
let doc2 = reader.read().expect("read R2004 should succeed");
let bodies: Vec<&Body> = doc2.entities().filter_map(|e| {
if let EntityType::Body(b) = e { Some(b) } else { None }
}).collect();
assert_eq!(bodies.len(), 1, "should have exactly one Body");
assert!(bodies[0].acis_data.sat_data.contains("body"));
}
#[test]
fn test_roundtrip_mixed_acis_r2010() {
use crate::entities::solid3d::{Solid3D, Region, Body};
use crate::entities::EntityType;
use crate::io::dwg::DwgReader;
let mut doc = CadDocument::new();
doc.version = DxfVersion::AC1024;
let _ = doc.add_entity(EntityType::Solid3D(Solid3D::from_sat(make_sat_sample())));
let _ = doc.add_entity(EntityType::Region(Region::from_sat(make_sat_sample())));
let _ = doc.add_entity(EntityType::Body(Body::from_sat(make_sat_sample())));
let bytes = DwgWriter::write_to_vec(&doc).expect("write R2010 should succeed");
let mut reader = DwgReader::from_stream(std::io::Cursor::new(bytes));
let doc2 = reader.read().expect("read R2010 should succeed");
let n_solid = doc2.entities().filter(|e| matches!(e, EntityType::Solid3D(_))).count();
let n_region = doc2.entities().filter(|e| matches!(e, EntityType::Region(_))).count();
let n_body = doc2.entities().filter(|e| matches!(e, EntityType::Body(_))).count();
assert_eq!(n_solid, 1, "should have 1 Solid3D");
assert_eq!(n_region, 1, "should have 1 Region");
assert_eq!(n_body, 1, "should have 1 Body");
for e in doc2.entities() {
match e {
EntityType::Solid3D(s) => assert!(s.acis_data.sat_data.contains("body")),
EntityType::Region(r) => assert!(r.acis_data.sat_data.contains("body")),
EntityType::Body(b) => assert!(b.acis_data.sat_data.contains("body")),
_ => {}
}
}
}
}