fix(polyline): render width bands for polylines inside blocks (#222)

Wide LwPolyline / Polyline2D fills only drew for entities directly in the
layout — the model-space loop skips block-internal ones, and the
block-explode fill pass handled hatches but not wide polylines. So a
polyline with a width nested in a block drew at zero width (its
centreline came from the wire path, the band fill never materialized).

Extend the block-explode fill walk (exploded_insert_hatch_models) to emit
wide-polyline bands at world position, gated by a new block_has_wide_poly
scan mirroring block_has_hatch, with a shared push_block_wide_fills helper
that resolves block-child colour inheritance and applies XCLIP.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-07-08 15:56:56 +03:00
1 parent 4df41cdf34
commit 910caa6f67
2 files changed
+148 -1

No files matched your search

+104 -1
View File
@@ -618,6 +618,11 @@ impl Scene {
// contain a hatch still explode exactly as before, so output is unchanged.
let mut hatch_block_memo: std::collections::HashMap<String, bool> =
std::collections::HashMap::new();
// Same gate for wide LwPolyline / Polyline2D fills — a block with a wide
// polyline (but no hatch) must still explode so its width band renders
// at world position, like a block-internal hatch. (#222)
let mut wide_block_memo: std::collections::HashMap<String, bool> =
std::collections::HashMap::new();
for entity in self.document.entities() {
let EntityType::Insert(ins) = entity else {
continue;
@@ -632,7 +637,9 @@ impl Scene {
) {
continue;
}
if !self.block_has_hatch(&ins.block_name, &mut hatch_block_memo) {
if !self.block_has_hatch(&ins.block_name, &mut hatch_block_memo)
&& !self.block_has_wide_poly(&ins.block_name, &mut wide_block_memo)
{
continue;
}
let selected = tint_selected && self.selected.contains(&ins.common.handle);
@@ -741,6 +748,45 @@ impl Scene {
models.push(model);
}
}
// Wide LwPolyline / Polyline2D inside a block: materialize its
// width bands at world position, same as a block-internal
// hatch. Without this a wide polyline in a block drew at zero
// width (its centreline came from the wire path, the band
// fill only from the model-space loop that skips blocks). (#222)
EntityType::LwPolyline(pl) => {
if pl.common.invisible || layer_hidden(&pl.common.layer) {
continue;
}
let (origin, fills) =
crate::entities::lwpolyline::wide_fills(&pl);
self.push_block_wide_fills(
&mut models,
&EntityType::LwPolyline(pl),
origin,
fills,
sub_ins_color,
sub_l0,
&clip_poly,
selected,
);
}
EntityType::Polyline2D(pl) => {
if pl.common.invisible || layer_hidden(&pl.common.layer) {
continue;
}
let (origin, fills) =
crate::entities::polyline::wide_fills(&pl);
self.push_block_wide_fills(
&mut models,
&EntityType::Polyline2D(pl),
origin,
fills,
sub_ins_color,
sub_l0,
&clip_poly,
selected,
);
}
_ => {}
}
}
@@ -748,6 +794,63 @@ impl Scene {
models
}
/// Materialize a block-exploded wide polyline's width bands (from
/// `wide_fills`, already in world coords) as solid `HatchModel`s: resolve the
/// block-child colour inheritance (NO background adaptation — a width band is
/// the polyline's own colour, not a hatch) and apply any XCLIP. Shared by the
/// LwPolyline / Polyline2D arms of the block-explode fill walk. (#222)
#[allow(clippy::too_many_arguments)]
fn push_block_wide_fills(
&self,
models: &mut Vec<HatchModel>,
sub_entity: &EntityType,
origin: [f64; 2],
fills: Vec<Vec<[f32; 2]>>,
sub_ins_color: [f32; 4],
sub_l0: crate::scene::view::render::InheritStyle,
clip_poly: &Option<Vec<[f32; 2]>>,
selected: bool,
) {
if fills.is_empty() {
return;
}
let mut color = crate::scene::view::render::render_style_for_block_sub(
&self.document,
sub_entity,
sub_ins_color,
0.0,
[0.0; 8],
0.0,
sub_l0,
)
.0;
if selected {
color = [0.15, 0.55, 1.00, color[3]];
}
for boundary in fills {
let ring = if let Some(poly) = clip_poly {
let clipped = pick::xclip::clip_hatch_boundary(&boundary, origin, poly);
if clipped.is_empty() {
continue;
}
clipped
} else {
boundary
};
models.push(HatchModel {
boundary: Arc::new(ring),
pattern: model::hatch_model::HatchPattern::Solid,
name: "SOLID".into(),
color,
angle_offset: 0.0,
scale: 1.0,
world_origin: origin,
vp_scissor: None,
draw_depth: 0.0,
});
}
}
/// Wipeout fill models — rendered in a separate pass AFTER wires so that
/// wipeouts correctly mask everything below them in the draw order.
pub(crate) fn wipeout_models(&self) -> Vec<HatchModel> {
+44
View File
@@ -2707,6 +2707,50 @@ impl Scene {
result
}
/// Transitively true when a block (or a block it nests) contains a wide
/// LwPolyline / Polyline2D — one carrying a non-zero width. Gate for the
/// block-explode wide-fill pass, mirroring [`Self::block_has_hatch`] so a
/// solid-only block is never exploded just to look for width bands. (#222)
fn block_has_wide_poly(
&self,
block_name: &str,
memo: &mut std::collections::HashMap<String, bool>,
) -> bool {
if let Some(&v) = memo.get(block_name) {
return v;
}
// Seed `false` first so a cyclic block reference terminates.
memo.insert(block_name.to_string(), false);
let result = self
.document
.block_records
.get(block_name)
.map(|br| {
br.entity_handles.iter().any(|&h| match self.document.get_entity(h) {
Some(EntityType::LwPolyline(p)) => {
p.constant_width > 1e-9
|| p.vertices
.iter()
.any(|v| v.start_width > 1e-9 || v.end_width > 1e-9)
}
Some(EntityType::Polyline2D(p)) => {
p.start_width > 1e-9
|| p.end_width > 1e-9
|| p.vertices
.iter()
.any(|v| v.start_width > 1e-9 || v.end_width > 1e-9)
}
Some(EntityType::Insert(ins)) => {
self.block_has_wide_poly(&ins.block_name, memo)
}
_ => false,
})
})
.unwrap_or(false);
memo.insert(block_name.to_string(), result);
result
}
pub fn insert_hatches_for_click(&self) -> Arc<Vec<(Handle, HatchModel)>> {
{
let c = self.insert_hatch_cache.borrow();