diff --git a/src/app/commands/dgn_import.rs b/src/app/commands/dgn_import.rs index bac56cb5f..0645105ad 100644 --- a/src/app/commands/dgn_import.rs +++ b/src/app/commands/dgn_import.rs @@ -458,7 +458,8 @@ impl OpenCADStudio { .file_stem() .map(|s| s.to_string_lossy().into_owned()) .unwrap_or_default(); - Naming::Prefix(dgn_import::symbol_name(&format!("{stem} - {}$0$", request.model))) + let prefix = format!("{stem} - {}$0$", request.model); + Naming::Prefix(dgn_import::symbol_name(&prefix)) } else { Naming::Keep }; diff --git a/src/modules/insert/dgn_import.rs b/src/modules/insert/dgn_import.rs index 23aa84971..f1cd2f4dc 100644 --- a/src/modules/insert/dgn_import.rs +++ b/src/modules/insert/dgn_import.rs @@ -12,7 +12,7 @@ // B-spline curves, complex chains and shapes (polylines with arcs), text, // text nodes and shared cells (blocks) are converted. -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use codec::entities::{ Arc, AttachmentPoint, Circle, DrawingDirection, Ellipse, Insert, Line, LineSpacingStyle, @@ -114,7 +114,7 @@ pub fn unit_display(short: &str) -> String { pub fn symbol_name(name: &str) -> String { name.chars() .map(|c| { - if c.is_control() || "<>/\\\":;?*|,=`".contains(c) { + if c.is_control() || "<>/\\\":;?*|=`".contains(c) { '_' } else { c @@ -221,9 +221,8 @@ fn flat(points: &[P3]) -> bool { /// (with the bulges), else a 3D polyline (no bulges). fn polyline(points: &[P3], bulges: &[f64]) -> EntityType { if !flat(points) { - return EntityType::Polyline3D(Polyline3D::from_points( - points.iter().map(|p| v3(*p)).collect(), - )); + let points: Vec = points.iter().map(|p| v3(*p)).collect(); + return EntityType::Polyline3D(Polyline3D::from_points(points)); } let mut pl = LwPolyline::new(); pl.plinegen = true; @@ -253,7 +252,8 @@ fn solid_hatch(points: &[P3], bulges: &[f64]) -> Option { } let mut edge = PolylineEdge::new(Vec::new(), true); for (k, p) in points[..n].iter().enumerate() { - edge.add_vertex(Vector2::new(p[0], p[1]), bulges.get(k).copied().unwrap_or(0.0)); + let bulge = bulges.get(k).copied().unwrap_or(0.0); + edge.add_vertex(Vector2::new(p[0], p[1]), bulge); } let mut hatch = Hatch::solid(); let mut boundary = BoundaryPath::external(); @@ -281,7 +281,9 @@ fn chain_points(parts: &[DgnGeom], use_bulges: bool) -> (Vec, Vec) { let mut points: Vec = Vec::new(); let mut bulges: Vec = Vec::new(); let mut add = |p: P3, bulge_before: Option| { - let same = points.last().is_some_and(|q| (0..3).all(|c| (q[c] - p[c]).abs() < 1e-9)); + let same = points + .last() + .is_some_and(|q| (0..3).all(|c| (q[c] - p[c]).abs() < 1e-9)); if let (Some(b), Some(last)) = (bulge_before, bulges.last_mut()) { *last = b; } @@ -379,6 +381,14 @@ fn attachment(just: u8) -> AttachmentPoint { } } +/// Text as MTEXT contents: backslashes and braces stay characters instead +/// of becoming formatting codes. +fn mtext_escape(text: &str) -> String { + text.replace('\\', "\\\\") + .replace('{', "\\{") + .replace('}', "\\}") +} + /// A number with six significant digits, trailing zeros dropped. fn sig6(x: f64) -> String { let digits = if x == 0.0 { @@ -510,7 +520,7 @@ fn mtext_of_text(geom: &DgnGeom, color: u16, style: &str) -> Option } else { String::new() }; - m.value = format!("{{{width}\\C{color};{text}}}"); + m.value = format!("{{{width}\\C{color};{}}}", mtext_escape(text)); if *just == 2 { let up = -0.33 * height; m.insertion_point = Vector3::new( @@ -549,7 +559,11 @@ fn mtext_of_node( color: Option, ) -> Option { let (_, first, height, rotation, _) = lines.iter().find_map(text_line)?; - let texts: Vec<&str> = lines.iter().filter_map(text_line).map(|l| l.0).collect(); + let texts: Vec = lines + .iter() + .filter_map(text_line) + .map(|l| mtext_escape(l.0)) + .collect(); let mut m = MText::new(); let joined = texts.join("\\P"); m.value = match color { @@ -783,8 +797,11 @@ impl<'a> Converter<'a> { DgnGeom::Line(a, b) => vec![EntityType::Line(Line::from_points(v3(*a), v3(*b)))], // A filled shape is its outline and a solid hatch inside it. DgnGeom::Points(pts) if pts.len() >= 2 => { - let fill = element.filled.then(|| solid_hatch(pts, &[])).flatten(); - std::iter::once(polyline(pts, &[])).chain(fill).collect() + let mut out = vec![polyline(pts, &[])]; + if element.filled { + out.extend(solid_hatch(pts, &[])); + } + out } DgnGeom::Points(_) => Vec::new(), DgnGeom::Arc { @@ -816,8 +833,11 @@ impl<'a> Converter<'a> { if points.len() < 2 { return Vec::new(); } - let fill = element.filled.then(|| solid_hatch(&points, &bulges)).flatten(); - std::iter::once(polyline(&points, &bulges)).chain(fill).collect() + let mut out = vec![polyline(&points, &bulges)]; + if element.filled { + out.extend(solid_hatch(&points, &bulges)); + } + out } // V7 text is TEXT, V8 text an MTEXT. DgnGeom::Text { font: Some(_), .. } => { @@ -980,8 +1000,8 @@ pub fn convert(import: &DgnImport, sub_units: bool, explode_text_nodes: bool) -> c.out.entities = entities; // A cell left without a block (the point display cell, an empty or // missing definition) places nothing: its instances are dropped. - let defined: std::collections::HashSet = - c.out.blocks.iter().map(|b| b.0.to_ascii_uppercase()).collect(); + let blocks = &c.out.blocks; + let defined: HashSet = blocks.iter().map(|b| b.0.to_ascii_uppercase()).collect(); let placed = |e: &EntityType| match e { EntityType::Insert(i) => defined.contains(&i.block_name.to_ascii_uppercase()), _ => true, diff --git a/src/scene/model/underlay_vector/dgn7.rs b/src/scene/model/underlay_vector/dgn7.rs index d5b8612d3..e39d0ef06 100644 --- a/src/scene/model/underlay_vector/dgn7.rs +++ b/src/scene/model/underlay_vector/dgn7.rs @@ -293,7 +293,8 @@ fn read_run(run: &[&[u8]], colors: &[[u8; 3]], d3: bool, depth: usize) -> Raw { // covers; they are already placed, so the cell keeps them as // they are. Nesting past `MAX_CELL_DEPTH` is left out. 2 => { - let mut left = (38 + uint16(e, 36).unwrap_or(0) as usize * 2).saturating_sub(e.len()); + let total = 38 + uint16(e, 36).unwrap_or(0) as usize * 2; + let mut left = total.saturating_sub(e.len()); let start = index; while left > 0 && index < run.len() && run[index][0] & 0x80 != 0 { left = left.saturating_sub(run[index].len()); diff --git a/src/scene/model/underlay_vector/dgn8.rs b/src/scene/model/underlay_vector/dgn8.rs index fd03152f4..32a66c759 100644 --- a/src/scene/model/underlay_vector/dgn8.rs +++ b/src/scene/model/underlay_vector/dgn8.rs @@ -284,7 +284,6 @@ fn f64_at(e: &[u8], at: usize) -> Option { Some(f64::from_le_bytes(e.get(at..at + 8)?.try_into().ok()?)) } -/// A string stored after the "ff fe 01 00" marker, when the element has one. /// The text style a text element uses: the id in its text style linkage /// (the four bytes after `04 20 e6 04 00 00`). fn text_style_id(e: &[u8]) -> Option { @@ -313,6 +312,7 @@ fn text_style_table(nm: &[&[u8]]) -> Vec<(u32, String)> { out } +/// A string stored after the "ff fe 01 00" marker, when the element has one. fn marked_string(e: &[u8], from: usize) -> Option { let i = e .get(from..)? @@ -446,8 +446,7 @@ impl Styles { /// A B-spline curve from its order and poles, with uniform knots. A closed /// curve (its last pole repeating the first) is periodic: its distinct /// poles wrap around with knots (i + 1) / (count + 1), and the curve is -/// clamped at both ends of its domain by knot insertion, as the reference -/// converts it. +/// clamped at both ends of its domain by knot insertion. fn bspline_geom(order: usize, poles: Vec<[f64; 3]>, closed: bool) -> DgnGeom { let degree = order - 1; let distinct = poles.len() - usize::from(closed && poles.first() == poles.last()); @@ -914,8 +913,14 @@ fn read_run(run: &[&[u8]], styles: &Styles, xf: &Xf) -> Raw { // The byte count at 110 (with the 4-byte marker) // ends the characters. { - let count = u16::from_le_bytes(e.get(110..112)?.try_into().ok()?) as usize; - marked_string(e, 200)?.chars().take(count.saturating_sub(4)).collect() + let count = + u16::from_le_bytes(e.get(110..112)?.try_into().ok()?) as usize; + let mark = e + .get(200..)? + .windows(4) + .position(|w| w == [0xff, 0xfe, 0x01, 0x00])? + + 200; + marked_string(&e[..(mark + count.max(4)).min(e.len())], mark)? }, )) })() @@ -1405,9 +1410,6 @@ fn unit_name(metres: f64) -> String { .unwrap_or_default() } -/// The named model element by element (DGNIMPORT): in master units with -/// the global origin at 0,0, shared cell definitions in master units, and -/// every level by id with its own name and colour. /// A design model (not a sheet): the model type byte of its header (4105) /// is 2 for a sheet. fn is_design(header: &[u8]) -> bool { @@ -1503,6 +1505,9 @@ fn references(control: &[u8]) -> Vec { .collect() } +/// The named model element by element (DGNIMPORT): in master units with +/// the global origin at 0,0, shared cell definitions in master units, and +/// every level by id with its own name and colour. pub fn import(bytes: &[u8], name: &str) -> Option { let cfb = Cfb::open(bytes)?; let all = models(&cfb); @@ -1546,8 +1551,8 @@ pub fn import(bytes: &[u8], name: &str) -> Option { while j < nm.len() && is_component(nm[j]) { j += 1; } - // The cell that draws points is the writer's own; the reference - // leaves it out of the drawing. + // The cell that draws points belongs to the writing program, + // not to the drawing: it is left out. if !cell.eq_ignore_ascii_case("POINT_DISPLAY") { let def = read_run(&nm[i + 1..j], &styles, &IDENTITY).elements; cells.push((cell, scaled_elements(&def, s, [0.0, 0.0])));