diff --git a/examples/inspect_block.rs b/examples/inspect_block.rs new file mode 100644 index 000000000..1ce6a277b --- /dev/null +++ b/examples/inspect_block.rs @@ -0,0 +1,128 @@ +// List entities inside a named block, showing their layer + resolved linetype. +// +// cargo run --release --example inspect_block -- + +use acadrust::entities::EntityType; +use acadrust::io::dwg::DwgReader; + +fn main() -> Result<(), Box> { + let path = std::env::args().nth(1).expect("usage: inspect_block [block]"); + let block_name = std::env::args().nth(2); + let doc = DwgReader::from_file(&path)?.read()?; + + if block_name.is_none() { + let mut blocks: Vec<_> = doc + .block_records + .iter() + .map(|br| { + let n = doc + .entities() + .filter(|e| e.common().owner_handle == br.handle) + .count(); + (br.name.clone(), n) + }) + .collect(); + blocks.sort_by_key(|(_, n)| std::cmp::Reverse(*n)); + println!("─── top 30 blocks by entity count ───────"); + for (name, n) in blocks.iter().take(30) { + println!(" {:>5} {}", n, name); + } + return Ok(()); + } + let block_name = block_name.unwrap(); + + // Find the block record by name. + let br = doc + .block_records + .iter() + .find(|br| br.name.eq_ignore_ascii_case(&block_name)) + .ok_or_else(|| format!("block '{}' not found", block_name))?; + + println!("block: \"{}\" handle={:?}", br.name, br.handle); + println!(); + + let mut counts: std::collections::BTreeMap<&'static str, u32> = Default::default(); + let mut sample_by_kind: std::collections::BTreeMap<&'static str, Vec> = + Default::default(); + + for e in doc.entities() { + if e.common().owner_handle != br.handle { + continue; + } + let kind: &'static str = match e { + EntityType::Line(_) => "Line", + EntityType::LwPolyline(_) => "LwPolyline", + EntityType::Polyline(_) => "Polyline", + EntityType::Polyline2D(_) => "Polyline2D", + EntityType::Arc(_) => "Arc", + EntityType::Circle(_) => "Circle", + EntityType::Spline(_) => "Spline", + EntityType::Ellipse(_) => "Ellipse", + EntityType::Text(_) => "Text", + EntityType::MText(_) => "MText", + EntityType::Insert(_) => "Insert", + EntityType::Hatch(_) => "Hatch", + EntityType::Solid(_) => "Solid", + _ => "Other", + }; + *counts.entry(kind).or_default() += 1; + + let c = e.common(); + let extra = match e { + EntityType::Insert(i) => format!(" → block=\"{}\"", i.block_name), + _ => String::new(), + }; + let line = format!( + " [{:?}] layer=\"{}\" lt=\"{}\" lt_scale={:.4}{}", + e.common().handle, c.layer, c.linetype, c.linetype_scale, extra + ); + let bucket = sample_by_kind.entry(kind).or_default(); + if bucket.len() < 4 { + bucket.push(line); + } + } + + println!("─── entity counts in block ──────────────"); + for (k, v) in &counts { + println!(" {:<12} {}", k, v); + } + println!(); + for (k, samples) in &sample_by_kind { + println!("─── sample {} (first {}) ────────", k, samples.len()); + for s in samples { + println!("{}", s); + } + println!(); + } + + println!("─── relevant layers ─────────────────────"); + let mut seen: std::collections::BTreeSet = Default::default(); + for e in doc.entities() { + if e.common().owner_handle == br.handle { + seen.insert(e.common().layer.clone()); + } + } + for layer_name in &seen { + if let Some(l) = doc.layers.get(layer_name) { + println!( + " \"{}\" lt=\"{}\" off={} frozen={}", + l.name, l.line_type, l.flags.off, l.flags.frozen + ); + } + } + + println!(); + println!("─── linetype table (dashed only) ────────"); + for lt in doc.line_types.iter() { + if lt.elements.is_empty() || lt.is_continuous() { + continue; + } + let elems: Vec = lt.elements.iter().map(|el| el.length).collect(); + println!( + " {:<24} pattern_len={:.4} elements={:?}", + lt.name, lt.pattern_length, elems + ); + } + + Ok(()) +} diff --git a/examples/inspect_lt.rs b/examples/inspect_lt.rs new file mode 100644 index 000000000..f87c1444c --- /dev/null +++ b/examples/inspect_lt.rs @@ -0,0 +1,116 @@ +// Summarise linetype usage in a DWG/DXF: which layers use which linetype, +// and which entities carry an explicit (non-bylayer) dashed linetype. +// +// cargo run --release --example inspect_lt -- + +use acadrust::entities::EntityType; +use acadrust::io::dwg::DwgReader; +use std::collections::BTreeMap; + +fn main() -> Result<(), Box> { + let path = std::env::args().nth(1).expect("usage: inspect_lt "); + let doc = DwgReader::from_file(&path)?.read()?; + + println!("─── layers (linetype) ───────────────────"); + let dashed_lts: std::collections::BTreeSet = doc + .line_types + .iter() + .filter(|lt| !lt.elements.is_empty() && !lt.is_continuous()) + .map(|lt| lt.name.clone()) + .collect(); + println!(" dashed linetypes in table: {:?}", dashed_lts); + println!(); + + let mut dashed_layers: BTreeMap = BTreeMap::new(); + for l in doc.layers.iter() { + if dashed_lts.contains(&l.line_type) { + dashed_layers.insert(l.name.clone(), l.line_type.clone()); + } + } + println!(" layers using a dashed linetype:"); + if dashed_layers.is_empty() { + println!(" (none)"); + } + for (n, lt) in &dashed_layers { + println!(" \"{}\" → \"{}\"", n, lt); + } + println!(); + + // Per-entity explicit dashed override (lt field non-empty and non-byLayer/byBlock/Continuous). + let mut explicit_dashed: u32 = 0; + let mut by_layer_in_dashed_layer: u32 = 0; + let mut by_layer_in_continuous: u32 = 0; + let mut by_kind: BTreeMap<&'static str, u32> = BTreeMap::new(); + let mut sample_explicit: Vec = vec![]; + let mut sample_layer_inh: Vec = vec![]; + + for e in doc.entities() { + let c = e.common(); + let kind: &'static str = match e { + EntityType::Line(_) => "Line", + EntityType::LwPolyline(_) => "LwPolyline", + EntityType::Polyline(_) => "Polyline", + EntityType::Polyline2D(_) => "Polyline2D", + EntityType::Arc(_) => "Arc", + EntityType::Circle(_) => "Circle", + EntityType::Spline(_) => "Spline", + EntityType::Ellipse(_) => "Ellipse", + EntityType::Insert(_) => "Insert", + _ => "Other", + }; + let lt = c.linetype.trim(); + let lt_norm = lt.to_ascii_lowercase(); + let is_bylayer = lt.is_empty() || lt_norm == "bylayer"; + let is_explicit_dashed = !is_bylayer + && lt_norm != "byblock" + && lt_norm != "continuous" + && dashed_lts.iter().any(|d| d.eq_ignore_ascii_case(lt)); + if is_explicit_dashed { + explicit_dashed += 1; + *by_kind.entry(kind).or_default() += 1; + if sample_explicit.len() < 6 { + sample_explicit.push(format!( + " [{:?}] {} layer=\"{}\" lt=\"{}\" lt_scale={:.4}", + c.handle, kind, c.layer, lt, c.linetype_scale + )); + } + } else if is_bylayer && dashed_layers.contains_key(&c.layer) { + by_layer_in_dashed_layer += 1; + if sample_layer_inh.len() < 6 { + sample_layer_inh.push(format!( + " [{:?}] {} layer=\"{}\" (inherits \"{}\") lt_scale={:.4}", + c.handle, + kind, + c.layer, + dashed_layers.get(&c.layer).unwrap(), + c.linetype_scale + )); + } + } else if is_bylayer { + by_layer_in_continuous += 1; + } + } + + println!("─── entities w/ explicit dashed lt ──────"); + println!(" total: {}", explicit_dashed); + for (k, v) in &by_kind { + println!(" {}: {}", k, v); + } + for s in &sample_explicit { + println!("{}", s); + } + println!(); + + println!("─── entities inheriting dashed via layer:"); + println!(" total: {}", by_layer_in_dashed_layer); + for s in &sample_layer_inh { + println!("{}", s); + } + println!(); + println!( + "─── entities bylayer/continuous: {}", + by_layer_in_continuous + ); + + Ok(()) +} diff --git a/src/scene/mod.rs b/src/scene/mod.rs index f49d75408..26848bea8 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -1987,6 +1987,12 @@ impl Scene { out.points = clipped; out.color = [r * 0.80, g * 0.80, b * 0.80, a * 0.85]; out.line_weight_px = wire.line_weight_px; + // Wire's pattern was sized for model-space coords during + // tessellation; we just projected points into paper coords + // (× scale), so rescale the dash pattern by the same factor + // to keep dimensional consistency in the GPU shader. + out.pattern_length = wire.pattern_length * scale; + out.pattern = wire.pattern.map(|v| v * scale); out.vp_scissor = Some([vp_x0, vp_y0, vp_x1, vp_y1]); projected.push(out); }