diff --git a/build/entity_generator.rs b/build/entity_generator.rs index 6012844..27e9d44 100644 --- a/build/entity_generator.rs +++ b/build/entity_generator.rs @@ -674,6 +674,9 @@ fn generate_write_code_pairs_for_write_order( attr(&write_command, "DontWriteIfValueIs") )); } + if !attr(&write_command, "WriteCondition").is_empty() { + predicates.push(attr(&write_command, "WriteCondition")); + } let code = code(&write_command); let expected_type = ExpectedType::get_expected_type(code).unwrap(); let typ = get_code_pair_type(&expected_type); diff --git a/examples/src/block_examples.rs b/examples/src/block_examples.rs new file mode 100644 index 0000000..c1b8414 --- /dev/null +++ b/examples/src/block_examples.rs @@ -0,0 +1,50 @@ +use dxf::entities::*; +use dxf::enums::AcadVersion; +use dxf::{Block, Drawing, Point}; + +pub fn all() -> dxf::DxfResult<()> { + basic_block_and_insert()?; + Ok(()) +} + +fn basic_block_and_insert() -> dxf::DxfResult<()> { + let mut drawing = Drawing::new(); + drawing.header.version = AcadVersion::R12; // this example only tested on R12 + + // + // create a block with a unique name... + // + let mut block = Block::default(); + block.name = "my-block-name".to_string(); + + // + // ...and populate it with entities + // + block.entities.push(Entity { + common: Default::default(), + specific: EntityType::Line(Line::new( + // line from (0,0) to (1,1) + Point::new(0.0, 0.0, 0.0), + Point::new(1.0, 1.0, 0.0), + )), + }); + + // + // add the block to the drawing + // + drawing.add_block(block); + + // + // add a reference to the block with an `INSERT` entity + // + let mut insert = Insert::default(); + insert.name = "my-block-name".to_string(); // use the same name as the block defined above + insert.location = Point::new(3.0, 3.0, 0.0); // select the base-point of the insertion + drawing.add_entity(Entity { + common: Default::default(), + specific: EntityType::Insert(insert), + }); // the end result is a line from (3,3) to (4,4) + + drawing.save_file("basic_block_and_insert.dxf")?; + Ok(()) +} diff --git a/examples/src/main.rs b/examples/src/main.rs index 62aa713..5879d59 100644 --- a/examples/src/main.rs +++ b/examples/src/main.rs @@ -1,8 +1,10 @@ extern crate dxf; +mod block_examples; mod line_type_examples; fn main() -> dxf::DxfResult<()> { + block_examples::all()?; line_type_examples::all()?; Ok(()) } diff --git a/spec/EntitiesSpec.xml b/spec/EntitiesSpec.xml index 7413f11..a3b36c1 100644 --- a/spec/EntitiesSpec.xml +++ b/spec/EntitiesSpec.xml @@ -486,7 +486,7 @@ - + diff --git a/src/block.rs b/src/block.rs index 81e43db..91923d9 100644 --- a/src/block.rs +++ b/src/block.rs @@ -206,7 +206,7 @@ impl Block { write_handles: bool, ) { pairs.push(CodePair::new_str(0, "BLOCK")); - if write_handles { + if write_handles && version >= AcadVersion::R13 { pairs.push(CodePair::new_string(5, &self.handle.as_string())); } @@ -705,4 +705,49 @@ mod tests { _ => panic!("expected a circle"), } } + + /// Test case derived from https://ezdxf.readthedocs.io/en/stable/dxfinternals/block_management.html + #[test] + fn write_block_r12_compat() { + let mut drawing = Drawing::new(); + drawing.header.version = AcadVersion::R12; + let mut block = Block::default(); + block.name = "block-name".to_string(); + block.entities.push(Entity { + common: Default::default(), + specific: EntityType::Line(Line::new( + Point::new(0.0, 0.0, 0.0), + Point::new(1.0, 1.0, 0.0), + )), + }); + drawing.add_block(block); + assert_contains_pairs( + &drawing, + vec![ + CodePair::new_str(0, "SECTION"), + CodePair::new_str(2, "BLOCKS"), + CodePair::new_str(0, "BLOCK"), + // no handle + CodePair::new_str(8, "0"), // layer + CodePair::new_str(2, "block-name"), // name + CodePair::new_i16(70, 0), // flags + CodePair::new_f64(10, 0.0), // insertion point + CodePair::new_f64(20, 0.0), + CodePair::new_f64(30, 0.0), + CodePair::new_str(3, "block-name"), // name again + CodePair::new_str(1, ""), // x-ref name; empty = external + CodePair::new_str(0, "LINE"), // first entity + CodePair::new_str(5, "12"), // entity handle + ], + ); + assert_contains_pairs( + &drawing, + vec![ + CodePair::new_str(0, "ENDBLK"), + CodePair::new_str(5, "10"), // endblk got handle, original block didn't + CodePair::new_str(8, "0"), // layer + CodePair::new_str(0, "ENDSEC"), // end of block + ], + ); + } } diff --git a/src/drawing.rs b/src/drawing.rs index c9e6bf7..650adf1 100644 --- a/src/drawing.rs +++ b/src/drawing.rs @@ -666,9 +666,11 @@ impl Drawing { None } } - pub(crate) fn add_block_no_handle_set(&mut self, block: Block) -> &Block { + pub(crate) fn add_block_no_handle_set(&mut self, mut block: Block) -> &Block { self.ensure_layer_is_present_for_block(&block); self.ensure_line_type_is_present_for_block(&block); + self.ensure_block_record_is_present_for_block(&mut block); + self.ensure_block_entity_handles_are_set(&mut block); self.__blocks.push(block); self.__blocks.last().unwrap() } @@ -826,6 +828,17 @@ impl Drawing { self.ensure_line_type_is_present(&ent.common.line_type_name); } } + fn ensure_block_record_is_present_for_block(&mut self, block: &mut Block) { + self.add_block_record(BlockRecord { + name: String::from(&block.name), + ..Default::default() + }); + } + fn ensure_block_entity_handles_are_set(&mut self, block: &mut Block) { + for ent in &mut block.entities { + ent.common.handle = self.next_handle(); + } + } fn ensure_line_type_is_present_for_object(&mut self, obj: &Object) { if let ObjectType::MLineStyle(ref style) = &obj.specific { self.ensure_line_type_is_present(&style.style_name); @@ -933,13 +946,15 @@ impl Drawing { pairs.push(CodePair::new_str(0, "ENDSEC")); } pub(crate) fn add_objects_pairs(&self, pairs: &mut Vec) { - pairs.push(CodePair::new_str(0, "SECTION")); - pairs.push(CodePair::new_str(2, "OBJECTS")); - for o in &self.__objects { - o.add_code_pairs(pairs, self.header.version); - } + if self.header.version >= AcadVersion::R13 { + pairs.push(CodePair::new_str(0, "SECTION")); + pairs.push(CodePair::new_str(2, "OBJECTS")); + for o in &self.__objects { + o.add_code_pairs(pairs, self.header.version); + } - pairs.push(CodePair::new_str(0, "ENDSEC")); + pairs.push(CodePair::new_str(0, "ENDSEC")); + } } pub(crate) fn add_thumbnail_pairs(&self, pairs: &mut Vec) -> DxfResult<()> { if self.header.version >= AcadVersion::R2000 { @@ -1227,6 +1242,7 @@ impl Drawing { #[cfg(test)] mod tests { use crate::entities::*; + use crate::enums::AcadVersion; use crate::helper_functions::tests::*; use crate::objects::*; use crate::tables::*; @@ -1315,6 +1331,32 @@ mod tests { assert_ne!(Handle(0), layer.handle); } + #[test] + fn objects_section_is_not_written_on_r12() { + let mut drawing = Drawing::new(); + drawing.header.version = AcadVersion::R12; + assert_not_contains_pairs( + &drawing, + vec![ + CodePair::new_str(0, "SECTION"), + CodePair::new_str(2, "OBJECTS"), + ], + ); + } + + #[test] + fn objects_section_is_written_on_r13() { + let mut drawing = Drawing::new(); + drawing.header.version = AcadVersion::R13; + assert_contains_pairs( + &drawing, + vec![ + CodePair::new_str(0, "SECTION"), + CodePair::new_str(2, "OBJECTS"), + ], + ); + } + #[test] fn block_handle_is_set_during_read_if_not_specified() { let drawing = drawing_from_pairs(vec![ diff --git a/src/entity.rs b/src/entity.rs index c4ab47e..12c93ff 100644 --- a/src/entity.rs +++ b/src/entity.rs @@ -1436,7 +1436,9 @@ impl Entity { } else { "AcDb2dPolyline" }; - pairs.push(CodePair::new_str(100, subclass_marker)); + if version >= AcadVersion::R13 { + pairs.push(CodePair::new_str(100, subclass_marker)); + } if version <= AcadVersion::R13 { pairs.push(CodePair::new_i16(66, as_i16(poly.contains_vertices))); } @@ -1496,7 +1498,9 @@ impl Entity { } else { "AcDb2dVertex" }; - pairs.push(CodePair::new_str(100, subclass_marker)); + if version >= AcadVersion::R13 { + pairs.push(CodePair::new_str(100, subclass_marker)); + } pairs.push(CodePair::new_f64(10, v.location.x)); pairs.push(CodePair::new_f64(20, v.location.y)); pairs.push(CodePair::new_f64(30, v.location.z)); @@ -1773,6 +1777,7 @@ mod tests { #[test] fn write_specific_entity_fields() { let mut drawing = Drawing::new(); + drawing.header.version = AcadVersion::R13; let line = Line { p1: Point::new(1.1, 2.2, 3.3), p2: Point::new(4.4, 5.5, 6.6), @@ -2393,6 +2398,7 @@ mod tests { #[test] fn write_2d_polyline() { let mut drawing = Drawing::new(); + drawing.header.version = AcadVersion::R13; let mut poly = Polyline::default(); poly.add_vertex( &mut drawing, @@ -2419,6 +2425,7 @@ mod tests { common: Default::default(), specific: EntityType::Polyline(poly), }); + // TODO assert_contains_pairs( &drawing, vec![ @@ -2472,6 +2479,7 @@ mod tests { #[test] fn write_3d_polyline() { let mut drawing = Drawing::new(); + drawing.header.version = AcadVersion::R13; let mut poly = Polyline::default(); poly.add_vertex( &mut drawing, @@ -2775,15 +2783,24 @@ mod tests { #[test] fn write_insert_no_embedded_attributes() { let mut drawing = Drawing::new(); - let ins = Insert::default(); + let mut ins = Insert::default(); + ins.name = "insert-name".to_string(); let ent = Entity::new(EntityType::Insert(ins)); drawing.add_entity(ent); + assert_not_contains_pairs( + &drawing, + vec![ + CodePair::new_i16(66, 0), // contains no attributes + CodePair::new_str(2, "insert-name"), // sentinel to ensure we're reading at the correct location + ], + ); assert_not_contains_pairs(&drawing, vec![CodePair::new_str(0, "SEQEND")]); } #[test] fn write_insert_with_embedded_attributes() { let mut drawing = Drawing::new(); + drawing.header.version = AcadVersion::R13; let mut ins = Insert::default(); ins.add_attribute(&mut drawing, Attribute::default()); let ent = Entity::new(EntityType::Insert(ins)); diff --git a/src/helper_functions.rs b/src/helper_functions.rs index 10b40d1..ca70100 100644 --- a/src/helper_functions.rs +++ b/src/helper_functions.rs @@ -626,7 +626,10 @@ pub mod tests { pub fn assert_contains_pairs(drawing: &Drawing, expected: Vec) { let actual = drawing.get_code_pairs().ok().unwrap(); - println!("checking pairs: {:?}", actual); + println!("checking pairs:"); + for pair in &actual { + println!("{:?}", pair); + } let actual_index = try_find_index(&actual, &expected); assert!(actual_index.is_some()); } @@ -638,7 +641,10 @@ pub mod tests { pub fn assert_not_contains_pairs(drawing: &Drawing, not_expected: Vec) { let actual = drawing.get_code_pairs().ok().unwrap(); - println!("checking pairs: {:?}", actual); + println!("checking pairs:"); + for pair in &actual { + println!("{:?}", pair); + } let actual_index = try_find_index(&actual, ¬_expected); assert!(actual_index.is_none()); } diff --git a/src/object.rs b/src/object.rs index 4f0b97d..61a0497 100644 --- a/src/object.rs +++ b/src/object.rs @@ -2016,6 +2016,7 @@ mod tests { dict.value_handles .insert(String::from("key2"), Handle(0xBBBB)); let mut drawing = Drawing::new(); + drawing.header.version = AcadVersion::R13; // OBJECTS section only written on R13+ drawing.add_object(Object { common: Default::default(), specific: ObjectType::Dictionary(dict),