DXF: write a MINSERT with the AcDbMInsertBlock marker (#3127) (#3131)

An INSERT repeated in rows or columns (groups 70/71 above 1) was written with
the subclass marker AcDbBlockReference, the marker of a single block
reference. Readers that go by the marker, BricsCAD among them, then drew only
the first copy of the array. The writer now marks such a reference
AcDbMInsertBlock, as AutoCAD does, and a plain INSERT keeps
AcDbBlockReference. The reader does not look at the marker, so CADCraft reads
both back as before.
This commit is contained in:
Zinadin Zidan
2026-11-14 15:22:22 +01:00
committed by GitHub
parent fbb083803b
commit 8e69890223
3 changed files with 84 additions and 2 deletions
+5 -2
View File
@@ -344,7 +344,10 @@ fn write_text_entity(w: &mut W, d: &Drawing, e: &Entity, owner: &str, paper: boo
fn write_insert_entity(w: &mut W, d: &Drawing, e: &Entity, owner: &str, paper: bool, cx: &Ctx) {
if let EntityKind::Insert(i) = &e.kind {
w.s(0, "INSERT");
common(w, e, owner, paper, "AcDbBlockReference");
// A reference repeated in rows or columns (MINSERT, groups 70/71) is an AcDbMInsertBlock;
// readers that go by the marker draw only the first copy of an AcDbBlockReference.
let array = i.cols > 1 || i.rows > 1;
common(w, e, owner, paper, if array { "AcDbMInsertBlock" } else { "AcDbBlockReference" });
if !i.attribs.is_empty() {
w.i(66, 1);
}
@@ -354,7 +357,7 @@ fn write_insert_entity(w: &mut W, d: &Drawing, e: &Entity, owner: &str, paper: b
w.f(42, i.scale.y);
w.f(43, i.scale.z);
w.f(50, i.rotation.to_degrees());
if i.cols > 1 || i.rows > 1 {
if array {
w.i(70, i64::from(i.cols));
w.i(71, i64::from(i.rows));
w.f(44, i.col_spacing);
+1
View File
@@ -37,6 +37,7 @@ mod layout_plot_window;
mod leader_vertex_elevation;
#[cfg(not(target_arch = "wasm32"))]
mod low_handseed;
mod minsert_marker;
mod mleader_content;
mod mleader_roundtrip;
mod mline_roundtrip;
+78
View File
@@ -0,0 +1,78 @@
//! A block reference repeated in rows or columns (MINSERT) is written with the subclass marker
//! `AcDbMInsertBlock`, as AutoCAD writes it; other readers (BricsCAD) draw only the first copy of
//! an array marked `AcDbBlockReference`. A plain INSERT keeps `AcDbBlockReference` (#3127).
use std::sync::Arc;
use cadcraft_doc::{Block, Common, Drawing, Entity, EntityKind, Insert, Line, Space};
use cadcraft_geom::Vec3;
fn drawing(refs: &[Insert]) -> Drawing {
let mut d = Drawing::new_metric();
let mut block = Block::new("MP");
block.entities.push(Entity::new(d.new_handle(), EntityKind::Line(Line { a: Vec3::ZERO, b: Vec3::new(2.5, 5.0, 0.0) })));
d.blocks.insert(block.name.clone(), Arc::new(block));
for r in refs {
d.add(&Space::Model, Common::default(), EntityKind::Insert(r.clone())).unwrap();
}
d
}
/// The subclass markers (group 100) of each INSERT record in the DXF text, in file order.
fn insert_markers(text: &str) -> Vec<Vec<String>> {
let lines: Vec<&str> = text.lines().map(str::trim).collect();
let pairs: Vec<(&str, &str)> = lines.chunks(2).filter_map(|c| Some((*c.first()?, *c.get(1)?))).collect();
let mut out = Vec::new();
let mut cur: Option<Vec<String>> = None;
for (code, value) in pairs {
if code == "0" {
out.extend(cur.take());
if value == "INSERT" {
cur = Some(Vec::new());
}
} else if code == "100"
&& let Some(m) = cur.as_mut()
{
m.push(value.to_string());
}
}
out.extend(cur);
out
}
fn inserts(d: &Drawing) -> Vec<Insert> {
d.model.iter().filter_map(|e| if let EntityKind::Insert(i) = &e.kind { Some(i.clone()) } else { None }).collect()
}
#[test]
fn minsert_is_written_with_the_minsert_marker() {
let columns = Insert { block: "MP".into(), cols: 6, col_spacing: 2.5, ..Insert::default() };
let rows = Insert { block: "MP".into(), insert: Vec3::new(0.0, 20.0, 0.0), rows: 3, row_spacing: 5.0, ..Insert::default() };
let text = cadcraft_io::write_dxf(&drawing(&[columns, rows]));
let markers = insert_markers(&text);
assert_eq!(markers.len(), 2, "{markers:?}");
for m in &markers {
assert_eq!(m, &["AcDbEntity", "AcDbMInsertBlock"], "an arrayed reference is an AcDbMInsertBlock");
}
}
#[test]
fn plain_insert_keeps_the_block_reference_marker() {
let plain = Insert { block: "MP".into(), insert: Vec3::new(30.0, 0.0, 0.0), ..Insert::default() };
let text = cadcraft_io::write_dxf(&drawing(&[plain]));
assert_eq!(insert_markers(&text), [["AcDbEntity", "AcDbBlockReference"]]);
}
#[test]
fn minsert_array_reads_back_from_its_own_dxf() {
let refs = [
Insert { block: "MP".into(), cols: 6, col_spacing: 2.5, ..Insert::default() },
Insert { block: "MP".into(), insert: Vec3::new(0.0, 20.0, 0.0), cols: 2, rows: 3, col_spacing: 4.0, row_spacing: 5.0, ..Insert::default() },
Insert { block: "MP".into(), insert: Vec3::new(30.0, 0.0, 0.0), ..Insert::default() },
];
let text = cadcraft_io::write_dxf(&drawing(&refs));
let back = inserts(&cadcraft_io::read_dxf(text.as_bytes()).unwrap());
let got: Vec<_> = back.iter().map(|i| (i.block.as_str(), i.insert, i.cols, i.rows, i.col_spacing, i.row_spacing)).collect();
let want: Vec<_> = refs.iter().map(|i| (i.block.as_str(), i.insert, i.cols, i.rows, i.col_spacing, i.row_spacing)).collect();
assert_eq!(got, want);
}