forked from 1b-agent/opencadstudio-upstream
fix(polyline): render fill mode boundaries
Use kernel-produced band outlines when FILLMODE is off. Preserve centerline linetype stations through inserts, clipping, projection, and PDF export.
This commit is contained in:
commit
4209c35ba3
60 files changed
+1620
-575
No files matched your search
@@ -1,6 +1,8 @@
|
||||
use std::cell::Cell;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::scene::model::object::{GripDef, GripShape, PropValue, Property};
|
||||
use crate::scene::model::wire_model::PatternStationPiece;
|
||||
|
||||
/// Linear / angular unit format pulled from the document header so the
|
||||
/// per-thread properties pipeline can format values consistently without
|
||||
@@ -789,6 +791,158 @@ pub(crate) fn tapered_band_points(
|
||||
(pts, widths)
|
||||
}
|
||||
|
||||
pub(crate) struct WideBandOutline {
|
||||
pub points: Vec<[f64; 3]>,
|
||||
pub stations: Vec<f32>,
|
||||
pub point_segments: Vec<i32>,
|
||||
pub station_pieces: Vec<PatternStationPiece>,
|
||||
pub source_length: f32,
|
||||
}
|
||||
|
||||
/// Builds the boundary of a variable-width band through the kernel.
|
||||
pub(crate) fn wide_band_outline(
|
||||
verts: &[([f64; 2], f64, f64, f64)],
|
||||
is_closed: bool,
|
||||
restart_per_segment: bool,
|
||||
to_wcs: &dyn Fn(f64, f64) -> (f64, f64, f64),
|
||||
) -> WideBandOutline {
|
||||
let source = cadkernel::geom2d::Polyline {
|
||||
vertices: verts
|
||||
.iter()
|
||||
.map(|(position, bulge, _, _)| cadkernel::geom2d::PolylineVertex {
|
||||
position: *position,
|
||||
bulge: *bulge,
|
||||
})
|
||||
.collect(),
|
||||
closed: is_closed,
|
||||
};
|
||||
let segment_count = if is_closed {
|
||||
verts.len()
|
||||
} else {
|
||||
verts.len().saturating_sub(1)
|
||||
};
|
||||
let widths: Vec<[f64; 2]> = verts
|
||||
.iter()
|
||||
.take(segment_count)
|
||||
.map(|(_, _, start, end)| [*start, *end])
|
||||
.collect();
|
||||
let boundary = cadkernel::geom2d::polyline_band_boundary(
|
||||
&source,
|
||||
&widths,
|
||||
cadkernel::tessellation::DEFAULT_ANGLE,
|
||||
);
|
||||
let mut points = Vec::new();
|
||||
let mut stations = Vec::new();
|
||||
let mut point_segments = Vec::new();
|
||||
for edge in boundary.edges {
|
||||
if !points.is_empty() {
|
||||
points.push([f64::NAN; 3]);
|
||||
stations.push(0.0);
|
||||
point_segments.push(-1);
|
||||
}
|
||||
points.extend(edge.points.into_iter().map(|point| {
|
||||
let (x, y, z) = to_wcs(point[0], point[1]);
|
||||
[x, y, z]
|
||||
}));
|
||||
stations.extend(
|
||||
if restart_per_segment {
|
||||
edge.segment_distances
|
||||
} else {
|
||||
edge.source_distances
|
||||
}
|
||||
.map(|distance| distance as f32),
|
||||
);
|
||||
point_segments.extend([edge.source_segment as i32; 2]);
|
||||
}
|
||||
let station_pieces = boundary
|
||||
.station_pieces
|
||||
.into_iter()
|
||||
.map(|piece| {
|
||||
let start = to_wcs(piece.points[0][0], piece.points[0][1]);
|
||||
let end = to_wcs(piece.points[1][0], piece.points[1][1]);
|
||||
PatternStationPiece {
|
||||
source_segment: piece.source_segment as u32,
|
||||
source_distances: piece.source_distances.map(|distance| distance as f32),
|
||||
segment_distances: piece.segment_distances.map(|distance| distance as f32),
|
||||
vector: [
|
||||
(end.0 - start.0) as f32,
|
||||
(end.1 - start.1) as f32,
|
||||
(end.2 - start.2) as f32,
|
||||
],
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
WideBandOutline {
|
||||
points,
|
||||
stations,
|
||||
point_segments,
|
||||
station_pieces,
|
||||
source_length: boundary.source_length as f32,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn extrude_wide_band_outline(
|
||||
outline: WideBandOutline,
|
||||
extrusion: [f64; 3],
|
||||
) -> WideBandOutline {
|
||||
let base_points = outline.points;
|
||||
let base_stations = outline.stations;
|
||||
let base_segments = outline.point_segments;
|
||||
let mut points = base_points.clone();
|
||||
let mut stations = base_stations.clone();
|
||||
let mut point_segments = base_segments.clone();
|
||||
points.push([f64::NAN; 3]);
|
||||
stations.push(0.0);
|
||||
point_segments.push(-1);
|
||||
points.extend(base_points.iter().map(|point| {
|
||||
if point[0].is_finite() {
|
||||
[
|
||||
point[0] + extrusion[0],
|
||||
point[1] + extrusion[1],
|
||||
point[2] + extrusion[2],
|
||||
]
|
||||
} else {
|
||||
*point
|
||||
}
|
||||
}));
|
||||
stations.extend_from_slice(&base_stations);
|
||||
point_segments.extend_from_slice(&base_segments);
|
||||
|
||||
let mut emitted = HashSet::new();
|
||||
for ((&point, &station), &segment) in base_points
|
||||
.iter()
|
||||
.zip(&base_stations)
|
||||
.zip(&base_segments)
|
||||
{
|
||||
if !point[0].is_finite() {
|
||||
continue;
|
||||
}
|
||||
let key = (point.map(f64::to_bits), station.to_bits(), segment);
|
||||
if !emitted.insert(key) {
|
||||
continue;
|
||||
}
|
||||
points.push([f64::NAN; 3]);
|
||||
stations.push(0.0);
|
||||
point_segments.push(-1);
|
||||
points.push(point);
|
||||
points.push([
|
||||
point[0] + extrusion[0],
|
||||
point[1] + extrusion[1],
|
||||
point[2] + extrusion[2],
|
||||
]);
|
||||
stations.push(station);
|
||||
stations.push(station);
|
||||
point_segments.extend([segment; 2]);
|
||||
}
|
||||
WideBandOutline {
|
||||
points,
|
||||
stations,
|
||||
point_segments,
|
||||
station_pieces: outline.station_pieces,
|
||||
source_length: outline.source_length,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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).
|
||||
|
||||
@@ -2068,6 +2068,7 @@ fn tessellate_dimension_inner(
|
||||
wires.push(WireModel {
|
||||
point_marker: None,
|
||||
taper_widths: Vec::new(),
|
||||
pattern_stations: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
@@ -2102,6 +2103,7 @@ fn tessellate_dimension_inner(
|
||||
wires.push(WireModel {
|
||||
point_marker: None,
|
||||
taper_widths: Vec::new(),
|
||||
pattern_stations: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
@@ -2136,6 +2138,7 @@ fn tessellate_dimension_inner(
|
||||
wires.push(WireModel {
|
||||
point_marker: None,
|
||||
taper_widths: Vec::new(),
|
||||
pattern_stations: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
@@ -2171,6 +2174,7 @@ fn tessellate_dimension_inner(
|
||||
wires.push(WireModel {
|
||||
point_marker: None,
|
||||
taper_widths: Vec::new(),
|
||||
pattern_stations: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
@@ -2222,6 +2226,7 @@ fn tessellate_dimension_inner(
|
||||
wires.push(WireModel {
|
||||
point_marker: None,
|
||||
taper_widths: Vec::new(),
|
||||
pattern_stations: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
|
||||
@@ -676,6 +676,7 @@ impl LeaderTess for Leader {
|
||||
return WireModel {
|
||||
point_marker: None,
|
||||
taper_widths: Vec::new(),
|
||||
pattern_stations: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
@@ -855,6 +856,7 @@ impl LeaderTess for Leader {
|
||||
WireModel {
|
||||
point_marker: None,
|
||||
taper_widths: Vec::new(),
|
||||
pattern_stations: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
|
||||
+120
-96
@@ -16,6 +16,15 @@ use crate::scene::model::wire_model::TangentGeom;
|
||||
const TAU: f64 = std::f64::consts::TAU;
|
||||
const REVCLOUD_BULGE: f64 = 0.5;
|
||||
const MAX_REVCLOUD_VERTICES: usize = 100_000;
|
||||
const WIDTH_EPSILON: f64 = 1.0e-9;
|
||||
|
||||
fn effective_width(width: f64, constant_width: f64) -> f64 {
|
||||
if width > WIDTH_EPSILON {
|
||||
width
|
||||
} else {
|
||||
constant_width
|
||||
}
|
||||
}
|
||||
|
||||
/// Midpoint position on an arc segment defined by its bulge.
|
||||
fn arc_midpoint(p0: [f64; 2], p1: [f64; 2], bulge: f64) -> [f64; 2] {
|
||||
@@ -251,7 +260,7 @@ fn thick_wide_band(
|
||||
}
|
||||
}
|
||||
|
||||
fn to_render(pline: &LwPolyline) -> RenderEntity {
|
||||
fn to_render(pline: &LwPolyline, fill_mode: bool) -> RenderEntity {
|
||||
let verts = &pline.vertices;
|
||||
if verts.is_empty() {
|
||||
return RenderEntity {
|
||||
@@ -268,9 +277,6 @@ fn to_render(pline: &LwPolyline) -> RenderEntity {
|
||||
let normal = (pline.normal.x, pline.normal.y, pline.normal.z);
|
||||
let count = verts.len();
|
||||
let seg_count = if pline.is_closed { count } else { count - 1 };
|
||||
let mut tangents: Vec<TangentGeom> = Vec::new();
|
||||
let mut key_verts: Vec<[f64; 3]> = Vec::new();
|
||||
|
||||
// Convert OCS (x, y, elevation) to a WCS point.
|
||||
let to_wcs = |x: f64, y: f64| -> (f64, f64, f64) {
|
||||
crate::scene::view::transform::ocs_point_to_wcs((x, y, elev), normal)
|
||||
@@ -280,6 +286,44 @@ fn to_render(pline: &LwPolyline) -> RenderEntity {
|
||||
[wx, wy, wz]
|
||||
};
|
||||
|
||||
let band_verts = band_verts(pline);
|
||||
if !fill_mode {
|
||||
let mut boundary = crate::entities::common::wide_band_outline(
|
||||
&band_verts,
|
||||
pline.is_closed,
|
||||
!pline.plinegen,
|
||||
&to_wcs,
|
||||
);
|
||||
if !boundary.points.is_empty() {
|
||||
if pline.thickness.abs() > 1e-10 {
|
||||
boundary = crate::entities::common::extrude_wide_band_outline(
|
||||
boundary,
|
||||
[
|
||||
pline.thickness * normal.0,
|
||||
pline.thickness * normal.1,
|
||||
pline.thickness * normal.2,
|
||||
],
|
||||
);
|
||||
}
|
||||
let (tangent_geoms, key_vertices) = centerline_metadata(pline, &to_wcs);
|
||||
return RenderEntity {
|
||||
pick_tris: Vec::new(),
|
||||
object: RenderObject::BoundaryLines {
|
||||
points: boundary.points,
|
||||
stations: boundary.stations,
|
||||
point_segments: boundary.point_segments,
|
||||
station_pieces: boundary.station_pieces,
|
||||
source_length: boundary.source_length,
|
||||
plinegen: pline.plinegen,
|
||||
},
|
||||
snap_pts: vec![],
|
||||
tangent_geoms,
|
||||
key_vertices,
|
||||
fill_tris: vec![],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if pline.thickness.abs() > 1e-10 {
|
||||
let mut path: Vec<[f64; 3]> = Vec::new();
|
||||
let mut kv: Vec<[f64; 3]> = Vec::new();
|
||||
@@ -341,7 +385,7 @@ fn to_render(pline: &LwPolyline) -> RenderEntity {
|
||||
// A wide polyline whose per-vertex widths VARY renders a smooth taper —
|
||||
// handled here, before the PLINEGEN split, so both cases get it. A
|
||||
// uniform-width polyline falls through to the constant-band paths below.
|
||||
if let Some(band_verts) = tapered_band_verts(pline) {
|
||||
if tapered_band_verts(&band_verts).is_some() {
|
||||
let mut kv: Vec<[f64; 3]> = Vec::new();
|
||||
let mut tgs: Vec<TangentGeom> = Vec::new();
|
||||
for i in 0..seg_count {
|
||||
@@ -370,8 +414,11 @@ fn to_render(pline: &LwPolyline) -> RenderEntity {
|
||||
});
|
||||
}
|
||||
}
|
||||
let (pts, widths) =
|
||||
crate::entities::common::tapered_band_points(&band_verts, pline.is_closed, &to_wcs);
|
||||
let (pts, widths) = crate::entities::common::tapered_band_points(
|
||||
&band_verts,
|
||||
pline.is_closed,
|
||||
&to_wcs,
|
||||
);
|
||||
let (fill_origin, fills) = wide_fills(pline);
|
||||
return RenderEntity {
|
||||
pick_tris: crate::entities::common::wide_band_tris(fill_origin, &fills),
|
||||
@@ -444,36 +491,7 @@ fn to_render(pline: &LwPolyline) -> RenderEntity {
|
||||
};
|
||||
}
|
||||
|
||||
for i in 0..seg_count {
|
||||
let v0 = &verts[i];
|
||||
let v1 = &verts[(i + 1) % count];
|
||||
let p0 = to_pt(v0);
|
||||
let p1 = to_pt(v1);
|
||||
let bulge = v0.bulge;
|
||||
|
||||
if bulge.abs() < 1e-9 {
|
||||
tangents.push(TangentGeom::Line {
|
||||
p1: [p0[0] as f32, p0[1] as f32, p0[2] as f32],
|
||||
p2: [p1[0] as f32, p1[1] as f32, p1[2] as f32],
|
||||
});
|
||||
} else if let Some(arc) = crate::entities::common::BulgeArc::from_bulge(
|
||||
[v0.location.x, v0.location.y],
|
||||
[v1.location.x, v1.location.y],
|
||||
bulge as f64,
|
||||
) {
|
||||
let (wcx, wcy, wcz) = to_wcs(arc.center[0], arc.center[1]);
|
||||
tangents.push(TangentGeom::Circle {
|
||||
center: [wcx as f32, wcy as f32, wcz as f32],
|
||||
radius: arc.radius as f32,
|
||||
});
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
key_verts.push([p0[0], p0[1], p0[2]]);
|
||||
}
|
||||
key_verts.push([p1[0], p1[1], p1[2]]);
|
||||
}
|
||||
|
||||
let (tangents, key_verts) = centerline_metadata(pline, &to_wcs);
|
||||
let (fill_origin, fills) = wide_fills(pline);
|
||||
RenderEntity {
|
||||
pick_tris: crate::entities::common::wide_band_tris(fill_origin, &fills),
|
||||
@@ -491,20 +509,23 @@ fn to_render(pline: &LwPolyline) -> RenderEntity {
|
||||
}
|
||||
}
|
||||
|
||||
/// The per-vertex `(location, bulge, start_width, end_width)` band description
|
||||
/// for a wide LwPolyline whose width VARIES — `None` when the width is uniform
|
||||
/// (a constant band) so the caller keeps the cheaper constant-width path.
|
||||
fn tapered_band_verts(pline: &LwPolyline) -> Option<Vec<([f64; 2], f64, f64, f64)>> {
|
||||
/// Effective segment widths for an LwPolyline band.
|
||||
fn band_verts(pline: &LwPolyline) -> Vec<([f64; 2], f64, f64, f64)> {
|
||||
let c = pline.constant_width;
|
||||
let band: Vec<([f64; 2], f64, f64, f64)> = pline
|
||||
pline
|
||||
.vertices
|
||||
.iter()
|
||||
.map(|v| {
|
||||
let sw = if v.start_width > 1e-9 { v.start_width } else { c };
|
||||
let ew = if v.end_width > 1e-9 { v.end_width } else { c };
|
||||
let sw = effective_width(v.start_width, c);
|
||||
let ew = effective_width(v.end_width, c);
|
||||
([v.location.x, v.location.y], v.bulge, sw, ew)
|
||||
})
|
||||
.collect();
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn tapered_band_verts(
|
||||
band: &[([f64; 2], f64, f64, f64)],
|
||||
) -> Option<&[([f64; 2], f64, f64, f64)]> {
|
||||
let w0 = band.first().map_or(0.0, |v| v.2);
|
||||
let varies = band
|
||||
.iter()
|
||||
@@ -517,6 +538,47 @@ fn tapered_band_verts(pline: &LwPolyline) -> Option<Vec<([f64; 2], f64, f64, f64
|
||||
}
|
||||
}
|
||||
|
||||
fn centerline_metadata(
|
||||
pline: &LwPolyline,
|
||||
to_wcs: &dyn Fn(f64, f64) -> (f64, f64, f64),
|
||||
) -> (Vec<TangentGeom>, Vec<[f64; 3]>) {
|
||||
let count = pline.vertices.len();
|
||||
let segment_count = if pline.is_closed {
|
||||
count
|
||||
} else {
|
||||
count.saturating_sub(1)
|
||||
};
|
||||
let mut tangents = Vec::with_capacity(segment_count);
|
||||
let mut key_vertices = Vec::with_capacity(segment_count + 1);
|
||||
for index in 0..segment_count {
|
||||
let start = &pline.vertices[index];
|
||||
let end = &pline.vertices[(index + 1) % count];
|
||||
let p0 = to_wcs(start.location.x, start.location.y);
|
||||
let p1 = to_wcs(end.location.x, end.location.y);
|
||||
if start.bulge.abs() < 1e-9 {
|
||||
tangents.push(TangentGeom::Line {
|
||||
p1: [p0.0 as f32, p0.1 as f32, p0.2 as f32],
|
||||
p2: [p1.0 as f32, p1.1 as f32, p1.2 as f32],
|
||||
});
|
||||
} else if let Some(arc) = crate::entities::common::BulgeArc::from_bulge(
|
||||
[start.location.x, start.location.y],
|
||||
[end.location.x, end.location.y],
|
||||
start.bulge,
|
||||
) {
|
||||
let center = to_wcs(arc.center[0], arc.center[1]);
|
||||
tangents.push(TangentGeom::Circle {
|
||||
center: [center.0 as f32, center.1 as f32, center.2 as f32],
|
||||
radius: arc.radius as f32,
|
||||
});
|
||||
}
|
||||
if index == 0 {
|
||||
key_vertices.push([p0.0, p0.1, p0.2]);
|
||||
}
|
||||
key_vertices.push([p1.0, p1.1, p1.2]);
|
||||
}
|
||||
(tangents, key_vertices)
|
||||
}
|
||||
|
||||
/// Split at vertex `idx`: a closed polyline re-opens there (one piece); an
|
||||
/// open one splits into two (interior vertices only). `None` when invalid.
|
||||
pub(crate) fn break_at_vertex(
|
||||
@@ -852,8 +914,12 @@ fn properties(pline: &LwPolyline) -> Vec<PropSection> {
|
||||
let v = pline.vertices.get(vi);
|
||||
let vx = v.map_or(0.0, |v| v.location.x);
|
||||
let vy = v.map_or(0.0, |v| v.location.y);
|
||||
let start_w = v.map_or(0.0, |v| v.start_width);
|
||||
let end_w = v.map_or(0.0, |v| v.end_width);
|
||||
let start_w = v.map_or(0.0, |v| {
|
||||
effective_width(v.start_width, pline.constant_width)
|
||||
});
|
||||
let end_w = v.map_or(0.0, |v| {
|
||||
effective_width(v.end_width, pline.constant_width)
|
||||
});
|
||||
let mp = <LwPolyline as crate::entities::traits::MassPropsCalc>::mass_props(pline);
|
||||
let cloud_arc_length = revision_cloud_arc_length(pline);
|
||||
let vertex_label = if n == 0 {
|
||||
@@ -964,16 +1030,7 @@ fn apply_geom_prop(pline: &mut LwPolyline, field: &str, value: &str) {
|
||||
};
|
||||
match field {
|
||||
"elevation" => pline.elevation = v,
|
||||
"global_width" if v.is_finite() && v >= 0.0 => {
|
||||
pline.constant_width = v;
|
||||
// Global width replaces every segment override. Keeping old
|
||||
// start/end values would make this editable row appear to work
|
||||
// while the renderer continued using the stale per-segment data.
|
||||
for vertex in &mut pline.vertices {
|
||||
vertex.start_width = 0.0;
|
||||
vertex.end_width = 0.0;
|
||||
}
|
||||
}
|
||||
"global_width" if v.is_finite() && v >= 0.0 => pline.constant_width = v,
|
||||
"vertex_x" => {
|
||||
if let Some(vtx) = pline.vertices.get_mut(vi) {
|
||||
vtx.location.x = v;
|
||||
@@ -985,27 +1042,11 @@ fn apply_geom_prop(pline: &mut LwPolyline, field: &str, value: &str) {
|
||||
}
|
||||
}
|
||||
"start_width" if v.is_finite() && v >= 0.0 => {
|
||||
if pline.constant_width > 0.0 {
|
||||
let width = pline.constant_width;
|
||||
for vertex in &mut pline.vertices {
|
||||
vertex.start_width = width;
|
||||
vertex.end_width = width;
|
||||
}
|
||||
pline.constant_width = 0.0;
|
||||
}
|
||||
if let Some(vtx) = pline.vertices.get_mut(vi) {
|
||||
vtx.start_width = v;
|
||||
}
|
||||
}
|
||||
"end_width" if v.is_finite() && v >= 0.0 => {
|
||||
if pline.constant_width > 0.0 {
|
||||
let width = pline.constant_width;
|
||||
for vertex in &mut pline.vertices {
|
||||
vertex.start_width = width;
|
||||
vertex.end_width = width;
|
||||
}
|
||||
pline.constant_width = 0.0;
|
||||
}
|
||||
if let Some(vtx) = pline.vertices.get_mut(vi) {
|
||||
vtx.end_width = v;
|
||||
}
|
||||
@@ -1104,8 +1145,8 @@ fn apply_transform(pline: &mut LwPolyline, t: &EntityTransform) {
|
||||
}
|
||||
|
||||
impl RenderConvertible for LwPolyline {
|
||||
fn to_render(&self, _document: &acadrust::CadDocument) -> Option<RenderEntity> {
|
||||
Some(to_render(self))
|
||||
fn to_render(&self, document: &acadrust::CadDocument) -> Option<RenderEntity> {
|
||||
Some(to_render(self, document.header.fill_mode))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1226,16 +1267,8 @@ impl crate::entities::traits::Grippable for LwPolyline {
|
||||
new_v.location.x = midpoint[0];
|
||||
new_v.location.y = midpoint[1];
|
||||
new_v.vertex_id = 0;
|
||||
let effective_start = if v0.start_width > 1e-9 {
|
||||
v0.start_width
|
||||
} else {
|
||||
self.constant_width
|
||||
};
|
||||
let effective_end = if v0.end_width > 1e-9 {
|
||||
v0.end_width
|
||||
} else {
|
||||
self.constant_width
|
||||
};
|
||||
let effective_start = effective_width(v0.start_width, self.constant_width);
|
||||
let effective_end = effective_width(v0.end_width, self.constant_width);
|
||||
let middle_width = (effective_start + effective_end) * 0.5;
|
||||
self.vertices[i0].end_width = middle_width;
|
||||
new_v.start_width = middle_width;
|
||||
@@ -1296,7 +1329,6 @@ pub(crate) fn wide_fills(pl: &acadrust::entities::LwPolyline) -> ([f64; 2], Vec<
|
||||
// Feeding the stored width in whole draws every wide polyline twice as wide
|
||||
// as the file asks for, which on a donut (a closed 2-vertex bulge-1 polyline)
|
||||
// shows up as a disc 1.5× its real radius.
|
||||
let hw_const = pl.constant_width as f32 * 0.5;
|
||||
let verts = &pl.vertices;
|
||||
let n = verts.len();
|
||||
if n < 2 {
|
||||
@@ -1308,16 +1340,8 @@ pub(crate) fn wide_fills(pl: &acadrust::entities::LwPolyline) -> ([f64; 2], Vec<
|
||||
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 * 0.5
|
||||
} else {
|
||||
hw_const
|
||||
};
|
||||
let hw1 = if v0.end_width > 1e-9 {
|
||||
v0.end_width as f32 * 0.5
|
||||
} else {
|
||||
hw_const
|
||||
};
|
||||
let hw0 = effective_width(v0.start_width, pl.constant_width) as f32 * 0.5;
|
||||
let hw1 = effective_width(v0.end_width, pl.constant_width) as f32 * 0.5;
|
||||
if hw0 < 1e-6 && hw1 < 1e-6 {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1505,6 +1505,7 @@ impl MultiLeaderTess for MultiLeader {
|
||||
wires.push(WireModel {
|
||||
point_marker: None,
|
||||
taper_widths: Vec::new(),
|
||||
pattern_stations: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
@@ -1845,6 +1846,7 @@ impl MultiLeaderTess for MultiLeader {
|
||||
wires.push(WireModel {
|
||||
point_marker: None,
|
||||
taper_widths: Vec::new(),
|
||||
pattern_stations: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
@@ -1918,6 +1920,7 @@ impl MultiLeaderTess for MultiLeader {
|
||||
wires.push(WireModel {
|
||||
point_marker: None,
|
||||
taper_widths: Vec::new(),
|
||||
pattern_stations: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
@@ -2006,6 +2009,7 @@ impl MultiLeaderTess for MultiLeader {
|
||||
wires.push(WireModel {
|
||||
point_marker: None,
|
||||
taper_widths: Vec::new(),
|
||||
pattern_stations: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
@@ -2049,6 +2053,7 @@ impl MultiLeaderTess for MultiLeader {
|
||||
wires.push(WireModel {
|
||||
point_marker: None,
|
||||
taper_widths: Vec::new(),
|
||||
pattern_stations: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
|
||||
+99
-11
@@ -195,7 +195,7 @@ pub fn drawn_vertices2d(
|
||||
(kept.len() >= 2).then_some(kept)
|
||||
}
|
||||
|
||||
fn tessellate_polyline2d(pl: &Polyline2D) -> RenderEntity {
|
||||
fn tessellate_polyline2d(pl: &Polyline2D, fill_mode: bool) -> RenderEntity {
|
||||
let filtered = drawn_vertices2d(pl);
|
||||
let verts: &[acadrust::entities::Vertex2D] = filtered.as_deref().unwrap_or(&pl.vertices);
|
||||
if verts.is_empty() {
|
||||
@@ -224,6 +224,47 @@ fn tessellate_polyline2d(pl: &Polyline2D) -> RenderEntity {
|
||||
[wx, wy, wz]
|
||||
};
|
||||
|
||||
if !fill_mode {
|
||||
let continuous = pl.flags.bits()
|
||||
& acadrust::entities::PolylineFlags::LINETYPE_CONTINUOUS.bits()
|
||||
!= 0;
|
||||
let mut boundary = crate::entities::common::wide_band_outline(
|
||||
&band_verts_2d(pl),
|
||||
pl.is_closed(),
|
||||
!continuous,
|
||||
&to_wcs,
|
||||
);
|
||||
if !boundary.points.is_empty() {
|
||||
if pl.thickness.abs() > 1e-10 {
|
||||
boundary = crate::entities::common::extrude_wide_band_outline(
|
||||
boundary,
|
||||
[
|
||||
pl.thickness * normal.0,
|
||||
pl.thickness * normal.1,
|
||||
pl.thickness * normal.2,
|
||||
],
|
||||
);
|
||||
}
|
||||
let (tangent_geoms, key_vertices) =
|
||||
centerline_metadata_2d(verts, pl.is_closed(), &to_wcs);
|
||||
return RenderEntity {
|
||||
pick_tris: Vec::new(),
|
||||
object: RenderObject::BoundaryLines {
|
||||
points: boundary.points,
|
||||
stations: boundary.stations,
|
||||
point_segments: boundary.point_segments,
|
||||
station_pieces: boundary.station_pieces,
|
||||
source_length: boundary.source_length,
|
||||
plinegen: continuous,
|
||||
},
|
||||
snap_pts: vec![],
|
||||
tangent_geoms,
|
||||
key_vertices,
|
||||
fill_tris: vec![],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if pl.thickness.abs() > 1e-10 {
|
||||
let (nx, ny, nz) = normal;
|
||||
let t = pl.thickness;
|
||||
@@ -348,13 +389,14 @@ fn tessellate_polyline2d(pl: &Polyline2D) -> RenderEntity {
|
||||
key_verts.push([p1[0], p1[1], p1[2]]);
|
||||
}
|
||||
|
||||
let band_verts = band_verts_2d(pl);
|
||||
let (fill_origin, fills) = wide_fills(pl);
|
||||
// A wide Polyline2D whose per-vertex widths VARY renders a smooth taper; a
|
||||
// uniform-width one keeps the constant-band Contour.
|
||||
let object = match tapered_band_verts_2d(pl) {
|
||||
let object = match tapered_band_verts_2d(&band_verts) {
|
||||
Some(band_verts) => {
|
||||
let (pts, widths) = crate::entities::common::tapered_band_points(
|
||||
&band_verts,
|
||||
band_verts,
|
||||
pl.is_closed(),
|
||||
&to_wcs,
|
||||
);
|
||||
@@ -376,16 +418,15 @@ fn tessellate_polyline2d(pl: &Polyline2D) -> RenderEntity {
|
||||
}
|
||||
}
|
||||
|
||||
/// Per-vertex `(location, bulge, start_width, end_width)` band description for a
|
||||
/// wide Polyline2D whose width VARIES — `None` when the width is uniform.
|
||||
fn tapered_band_verts_2d(
|
||||
/// Effective segment widths for a Polyline2D band.
|
||||
fn band_verts_2d(
|
||||
pl: &acadrust::entities::Polyline2D,
|
||||
) -> Option<Vec<([f64; 2], f64, f64, f64)>> {
|
||||
) -> Vec<([f64; 2], f64, f64, f64)> {
|
||||
let default_start = pl.start_width;
|
||||
let default_end = pl.end_width;
|
||||
let filtered = drawn_vertices2d(pl);
|
||||
let verts: &[acadrust::entities::Vertex2D] = filtered.as_deref().unwrap_or(&pl.vertices);
|
||||
let band: Vec<([f64; 2], f64, f64, f64)> = verts
|
||||
verts
|
||||
.iter()
|
||||
.map(|v| {
|
||||
let sw = if v.start_width > 1e-9 {
|
||||
@@ -400,7 +441,12 @@ fn tapered_band_verts_2d(
|
||||
};
|
||||
([v.location.x, v.location.y], v.bulge, sw, ew)
|
||||
})
|
||||
.collect();
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn tapered_band_verts_2d(
|
||||
band: &[([f64; 2], f64, f64, f64)],
|
||||
) -> Option<&[([f64; 2], f64, f64, f64)]> {
|
||||
let w0 = band.first().map_or(0.0, |v| v.2);
|
||||
let varies = band
|
||||
.iter()
|
||||
@@ -412,9 +458,51 @@ fn tapered_band_verts_2d(
|
||||
}
|
||||
}
|
||||
|
||||
fn centerline_metadata_2d(
|
||||
verts: &[acadrust::entities::Vertex2D],
|
||||
closed: bool,
|
||||
to_wcs: &dyn Fn(f64, f64) -> (f64, f64, f64),
|
||||
) -> (Vec<TangentGeom>, Vec<[f64; 3]>) {
|
||||
let count = verts.len();
|
||||
let segment_count = if closed {
|
||||
count
|
||||
} else {
|
||||
count.saturating_sub(1)
|
||||
};
|
||||
let mut tangents = Vec::with_capacity(segment_count);
|
||||
let mut key_vertices = Vec::with_capacity(segment_count + 1);
|
||||
for index in 0..segment_count {
|
||||
let start = &verts[index];
|
||||
let end = &verts[(index + 1) % count];
|
||||
let p0 = to_wcs(start.location.x, start.location.y);
|
||||
let p1 = to_wcs(end.location.x, end.location.y);
|
||||
if start.bulge.abs() < 1e-9 {
|
||||
tangents.push(TangentGeom::Line {
|
||||
p1: [p0.0 as f32, p0.1 as f32, p0.2 as f32],
|
||||
p2: [p1.0 as f32, p1.1 as f32, p1.2 as f32],
|
||||
});
|
||||
} else if let Some(arc) = crate::entities::common::BulgeArc::from_bulge(
|
||||
[start.location.x, start.location.y],
|
||||
[end.location.x, end.location.y],
|
||||
start.bulge,
|
||||
) {
|
||||
let center = to_wcs(arc.center[0], arc.center[1]);
|
||||
tangents.push(TangentGeom::Circle {
|
||||
center: [center.0 as f32, center.1 as f32, center.2 as f32],
|
||||
radius: arc.radius as f32,
|
||||
});
|
||||
}
|
||||
if index == 0 {
|
||||
key_vertices.push([p0.0, p0.1, p0.2]);
|
||||
}
|
||||
key_vertices.push([p1.0, p1.1, p1.2]);
|
||||
}
|
||||
(tangents, key_vertices)
|
||||
}
|
||||
|
||||
impl RenderConvertible for Polyline2D {
|
||||
fn to_render(&self, _document: &acadrust::CadDocument) -> Option<RenderEntity> {
|
||||
Some(tessellate_polyline2d(self))
|
||||
fn to_render(&self, document: &acadrust::CadDocument) -> Option<RenderEntity> {
|
||||
Some(tessellate_polyline2d(self, document.header.fill_mode))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1669,6 +1669,7 @@ pub fn tessellate_table(
|
||||
WireModel {
|
||||
point_marker: None,
|
||||
taper_widths: Vec::new(),
|
||||
pattern_stations: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
|
||||
Reference in new issue
Block a user