From 67022589df5b8fb55f6abf1260ee26b7e57ba074 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Mon, 27 Apr 2026 02:20:28 +0300 Subject: [PATCH] feat: render LWPolyline and Polyline2D wide strokes as solid fills MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wide polyline segments (constant_width, per-vertex start/end widths) now appear as solid-filled quads or arc bands via HatchModel, generated inline in synced_hatch_models(). Straight segments → trapezoid, arc segments → arc-band polygon (inner/outer arc). MINSERT confirmed working via acadrust explode_from_document → array_transforms(). Co-Authored-By: Claude Sonnet 4.6 --- src/scene/mod.rs | 164 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) diff --git a/src/scene/mod.rs b/src/scene/mod.rs index ee247a1e1..1b8cd9344 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -1441,6 +1441,37 @@ impl Scene { } } + // Wide LWPolyline and Polyline2D fills + for entity in self.document.entities() { + let (common, fills) = match entity { + EntityType::LwPolyline(pl) => { + (&pl.common, wide_lwpolyline_fills(pl)) + } + EntityType::Polyline2D(pl) => { + (&pl.common, wide_polyline2d_fills(pl)) + } + _ => continue, + }; + if fills.is_empty() { continue; } + if common.invisible || layer_hidden(&common.layer) { continue; } + if !self.belongs_to_visible_block(common.handle, common.owner_handle, layout_block) { + continue; + } + let base_color = self.render_style(entity).0; + let selected = self.selected.contains(&common.handle); + let color = if selected { [0.15, 0.55, 1.00, 1.0] } else { base_color }; + for boundary in fills { + models.push(HatchModel { + boundary, + pattern: hatch_model::HatchPattern::Solid, + name: "SOLID".into(), + color, + angle_offset: 0.0, + scale: 1.0, + }); + } + } + models } @@ -2704,3 +2735,136 @@ fn entity_aabb(e: &acadrust::EntityType) -> [f32; 4] { [min_x, min_y, max_x, max_y] } +/// Generate solid-fill boundary polygons for each wide segment of an LWPolyline. +/// Returns one polygon per segment that has non-zero width; empty if the polyline +/// has zero `constant_width` and all vertex widths are zero. +fn wide_lwpolyline_fills( + pl: &acadrust::entities::LwPolyline, +) -> Vec> { + let hw_const = (pl.constant_width / 2.0) as f32; + let verts = &pl.vertices; + let n = verts.len(); + if n < 2 { + return vec![]; + } + let seg_count = if pl.is_closed { n } else { n - 1 }; + let mut out = Vec::new(); + for i in 0..seg_count { + let v0 = &verts[i]; + let v1 = &verts[(i + 1) % n]; + let hw0 = if v0.start_width > 1e-9 { v0.start_width as f32 / 2.0 } else { hw_const }; + let hw1 = if v0.end_width > 1e-9 { v0.end_width as f32 / 2.0 } else { hw_const }; + if hw0 < 1e-6 && hw1 < 1e-6 { + continue; + } + let p0 = [v0.location.x as f32, v0.location.y as f32]; + let p1 = [v1.location.x as f32, v1.location.y as f32]; + if let Some(poly) = polyline_segment_fill(p0, p1, hw0, hw1, v0.bulge as f32) { + out.push(poly); + } + } + out +} + +/// Generate solid-fill boundary polygons for each wide segment of a Polyline2D. +fn wide_polyline2d_fills( + pl: &acadrust::entities::Polyline2D, +) -> Vec> { + let hw_default = (pl.start_width.max(pl.end_width) / 2.0) as f32; + let verts = &pl.vertices; + let n = verts.len(); + if n < 2 { + return vec![]; + } + let seg_count = if pl.is_closed() { n } else { n - 1 }; + let mut out = Vec::new(); + for i in 0..seg_count { + let v0 = &verts[i]; + let v1 = &verts[(i + 1) % n]; + let hw0 = if v0.start_width > 1e-9 { v0.start_width as f32 / 2.0 } else { hw_default }; + let hw1 = if v0.end_width > 1e-9 { v0.end_width as f32 / 2.0 } else { hw_default }; + if hw0 < 1e-6 && hw1 < 1e-6 { + continue; + } + let p0 = [v0.location.x as f32, v0.location.y as f32]; + let p1 = [v1.location.x as f32, v1.location.y as f32]; + if let Some(poly) = polyline_segment_fill(p0, p1, hw0, hw1, v0.bulge as f32) { + out.push(poly); + } + } + out +} + +/// Compute the filled boundary polygon for one polyline segment. +/// For straight segments: a rectangle/trapezoid. +/// For arc segments: an arc band (outer arc + reversed inner arc). +/// Returns `None` if the segment is degenerate. +fn polyline_segment_fill( + p0: [f32; 2], + p1: [f32; 2], + hw0: f32, + hw1: f32, + bulge: f32, +) -> Option> { + if bulge.abs() < 1e-9 { + // Straight segment — rectangle or trapezoid + let dx = p1[0] - p0[0]; + let dy = p1[1] - p0[1]; + let len = (dx * dx + dy * dy).sqrt(); + if len < 1e-9 { + return None; + } + let nx = -dy / len; + let ny = dx / len; + Some(vec![ + [p0[0] + hw0 * nx, p0[1] + hw0 * ny], + [p1[0] + hw1 * nx, p1[1] + hw1 * ny], + [p1[0] - hw1 * nx, p1[1] - hw1 * ny], + [p0[0] - hw0 * nx, p0[1] - hw0 * ny], + ]) + } else { + // Arc segment — arc band polygon + let angle = 4.0 * (bulge as f64).atan(); + let dx = (p1[0] - p0[0]) as f64; + let dy = (p1[1] - p0[1]) as f64; + let d = (dx * dx + dy * dy).sqrt(); + if d < 1e-9 { + return None; + } + let r = (d / 2.0) / (angle / 2.0).sin().abs(); + let mx = ((p0[0] + p1[0]) * 0.5) as f64; + let my = ((p0[1] + p1[1]) * 0.5) as f64; + let px = -dy / d; + let py = dx / d; + let sign = if bulge > 0.0 { 1.0_f64 } else { -1.0_f64 }; + let h = r - (r * r - d * d / 4.0).max(0.0).sqrt(); + let cx = (mx - sign * px * (r - h)) as f32; + let cy = (my - sign * py * (r - h)) as f32; + let a0 = ((p0[1] - cy) as f32).atan2((p0[0] - cx) as f32); + let a1 = ((p1[1] - cy) as f32).atan2((p1[0] - cx) as f32); + let (sa, mut ea) = if bulge > 0.0 { (a0, a1) } else { (a1, a0) }; + if ea < sa { + ea += std::f32::consts::TAU; + } + let span = ea - sa; + let segs = ((span.abs() / std::f32::consts::TAU) * 12.0) + .ceil() + .max(4.0) as u32; + let r = r as f32; + let hw = (hw0 + hw1) * 0.5; + let r_outer = r + hw; + let r_inner = (r - hw).max(0.0); + let mut boundary = Vec::with_capacity((segs as usize + 1) * 2); + for j in 0..=segs { + let t = sa + span * (j as f32 / segs as f32); + boundary.push([cx + r_outer * t.cos(), cy + r_outer * t.sin()]); + } + for j in (0..=segs).rev() { + let t = sa + span * (j as f32 / segs as f32); + boundary.push([cx + r_inner * t.cos(), cy + r_inner * t.sin()]); + } + boundary.truncate(64); + Some(boundary) + } +} +