forked from 1b-agent/opencadstudio-upstream
feat: render LWPolyline and Polyline2D wide strokes as solid fills
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 <[email protected]>
This commit is contained in:
1 parent
c7a7022293
commit
67022589df
1 file changed
+164
@@ -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<Vec<[f32; 2]>> {
|
||||
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<Vec<[f32; 2]>> {
|
||||
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<Vec<[f32; 2]>> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user