fix(plot): correct hatch rendering in PDF / plot export

Hatched content — especially fills nested inside a block INSERT, such as a
title-block logo — plotted incorrectly: missing, mis-spaced, or with phantom
bars. Five related fixes make the PDF/plot output match AutoCAD.

1. Export block-internal hatch fills.
   The export collected only hatches owned directly by the layout block, so a
   hatch nested in a block INSERT was dropped and printed as bare monochrome
   outlines. The insert-explosion the viewport already does is extracted into a
   shared `exploded_insert_hatch_models()` and called from both the viewport
   (`synced_hatch_models`) and the export (`paper_canvas_hatches`), so a plot
   draws block-internal hatches identically to the screen.

2. Honour the hatch's own stored pattern-line spacing.
   `hatch_model_from_dxf` re-derived pattern spacing from the name-matched
   catalog entry x pattern_scale, ignoring the resolved line geometry the DWG
   stores on the hatch. When a drawing was authored against a different base
   spacing (imperial 0.125 vs the catalog's metric 3.175 for ANSI31), lines
   came out up to ~25x too coarse and a dense fill collapsed to a few stray
   lines. When the hatch carries its own line geometry it is now used directly
   (identity scale/angle); the catalog path remains the fallback.

3. Fill far-from-origin pattern hatches.
   `pattern_segments` clamped the ABSOLUTE scan-line index to
   +/-MAX_LINES_PER_FAMILY. A fine-spaced hatch far from the pattern origin has
   large-magnitude indices at both ends but a small span, so the clamp inverted
   the range and emitted nothing — silently dropping the fill. Cap the line
   count (span) instead of the absolute index.

4. Skip TEXTBOX boundary paths.
   TEXTBOX boundary paths (flag bit 3) are text bounding-boxes AutoCAD derives
   for island detection; they are never drawn or filled. Treating one as a fill
   boundary painted its rectangle solid — a phantom bar. It is now skipped when
   building the fill boundary.

5. Print white/ACI-7 hatch fills black on paper.
   Hatch fills arrive adapted to the dark screen background, so a white/ACI-7
   fill would vanish white-on-white on the sheet. Mirror the wire pass:
   near-white/near-yellow -> black, near-cyan -> dark blue, matching AutoCAD's
   colour-7-on-white plotting. Wipeouts keep their paper-white mask.

Adds `tests/block_hatch_export.rs` covering: block-internal hatch reaches the
export set, stored-line spacing is honoured, far-from-origin fills are not
dropped, and TEXTBOX paths are not filled.
This commit is contained in:
sLuCHa committed 2026-07-06 12:24:10 +02:00
1 parent b50921923f
commit 1a45f6bd97
5 files changed
+393 -55

No files matched your search

+20 -2
View File
@@ -212,9 +212,14 @@ impl Scene {
let mut m = model.clone();
m.color = self.render_style(entity).0;
if let EntityType::Hatch(dxf) = entity {
// Only re-apply pattern_scale/angle for catalog-derived patterns
// (empty stored lines). A pattern built from the hatch's own
// stored lines is already final (scale 1 / angle 0).
if let model::hatch_model::HatchPattern::Pattern(_) = &m.pattern {
m.angle_offset = dxf.pattern_angle as f32;
m.scale = dxf.pattern_scale as f32;
if dxf.pattern.lines.is_empty() {
m.angle_offset = dxf.pattern_angle as f32;
m.scale = dxf.pattern_scale as f32;
}
}
}
if self.selected.contains(&handle) {
@@ -222,6 +227,19 @@ impl Scene {
}
models.push(m);
}
// Hatch fills nested inside a block INSERT are owned by the block
// record, so the loop above — which only keeps hatches owned by
// `layout_block` — never sees them. Explode the layout's visible
// INSERTs and materialize their fills at world position, exactly as the
// viewport does, so the export carries the block's colours instead of
// bare outlines. (No selection tint on export.)
let hatch_bg = if self.current_layout != "Model" {
self.paper_bg_color
} else {
self.bg_color
};
let exploded = self.exploded_insert_hatch_models(layout_block, hatch_bg, false);
models.extend(exploded);
Arc::new(models)
}