forked from 1b-agent/opencadstudio-upstream
Keep DGN 3D line strings in 3D and fill filled complex shapes
A line string, shape or complex chain whose points are not all at one z becomes a 3D polyline instead of a 2D polyline flattened to the first point's elevation; its arcs then come in as points. Chain vertices are de-duplicated on all three coordinates, and only arcs in the drawing plane become bulges. A filled complex shape gets its solid hatch like a filled simple shape, following the outline's arcs. The hatch keeps the outline's elevation and is left out when the outline is not at one z. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ed4dc91670
commit
11ef1efc43
1 file changed
+51
-21
@@ -16,7 +16,7 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
use codec::entities::{
|
use codec::entities::{
|
||||||
Arc, AttachmentPoint, Circle, DrawingDirection, Ellipse, Insert, Line, LineSpacingStyle,
|
Arc, AttachmentPoint, Circle, DrawingDirection, Ellipse, Insert, Line, LineSpacingStyle,
|
||||||
LwPolyline, LwVertex, MText, Point, Spline, Text, TextHorizontalAlignment,
|
LwPolyline, LwVertex, MText, Point, Polyline3D, Spline, Text, TextHorizontalAlignment,
|
||||||
TextVerticalAlignment,
|
TextVerticalAlignment,
|
||||||
};
|
};
|
||||||
use codec::types::{Color, LineWeight, Vector2, Vector3};
|
use codec::types::{Color, LineWeight, Vector2, Vector3};
|
||||||
@@ -211,7 +211,20 @@ fn to_ocs(normal: P3, p: P3) -> Vector3 {
|
|||||||
codec::types::Matrix3::arbitrary_axis(v3(normal)).transpose() * v3(p)
|
codec::types::Matrix3::arbitrary_axis(v3(normal)).transpose() * v3(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// True when every point is at the first one's z.
|
||||||
|
fn flat(points: &[P3]) -> bool {
|
||||||
|
let z = points.first().map_or(0.0, |p| p[2]);
|
||||||
|
points.iter().all(|p| (p[2] - z).abs() <= 1e-9 * z.abs().max(1.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The points as a polyline: at one z a 2D polyline at that elevation
|
||||||
|
/// (with the bulges), else a 3D polyline (no bulges).
|
||||||
fn polyline(points: &[P3], bulges: &[f64]) -> EntityType {
|
fn polyline(points: &[P3], bulges: &[f64]) -> EntityType {
|
||||||
|
if !flat(points) {
|
||||||
|
return EntityType::Polyline3D(Polyline3D::from_points(
|
||||||
|
points.iter().map(|p| v3(*p)).collect(),
|
||||||
|
));
|
||||||
|
}
|
||||||
let mut pl = LwPolyline::new();
|
let mut pl = LwPolyline::new();
|
||||||
pl.plinegen = true;
|
pl.plinegen = true;
|
||||||
pl.elevation = points.first().map_or(0.0, |p| p[2]);
|
pl.elevation = points.first().map_or(0.0, |p| p[2]);
|
||||||
@@ -227,31 +240,48 @@ fn polyline(points: &[P3], bulges: &[f64]) -> EntityType {
|
|||||||
EntityType::LwPolyline(pl)
|
EntityType::LwPolyline(pl)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A solid hatch filling a closed outline, in its plane.
|
/// A solid hatch filling a closed outline (with its bulges) at the
|
||||||
fn solid_hatch(points: &[P3]) -> EntityType {
|
/// outline's elevation; `None` when the outline is not at one z.
|
||||||
|
fn solid_hatch(points: &[P3], bulges: &[f64]) -> Option<EntityType> {
|
||||||
use codec::entities::{BoundaryEdge, BoundaryPath, Hatch, PolylineEdge};
|
use codec::entities::{BoundaryEdge, BoundaryPath, Hatch, PolylineEdge};
|
||||||
let mut ring: Vec<Vector2> = points.iter().map(|p| Vector2::new(p[0], p[1])).collect();
|
if points.len() < 3 || !flat(points) {
|
||||||
if ring.len() > 1 && ring.first() == ring.last() {
|
return None;
|
||||||
ring.pop();
|
}
|
||||||
|
let mut n = points.len();
|
||||||
|
if points[0][0] == points[n - 1][0] && points[0][1] == points[n - 1][1] {
|
||||||
|
n -= 1;
|
||||||
|
}
|
||||||
|
let mut edge = PolylineEdge::new(Vec::new(), true);
|
||||||
|
for (k, p) in points[..n].iter().enumerate() {
|
||||||
|
edge.add_vertex(Vector2::new(p[0], p[1]), bulges.get(k).copied().unwrap_or(0.0));
|
||||||
}
|
}
|
||||||
let mut hatch = Hatch::solid();
|
let mut hatch = Hatch::solid();
|
||||||
let mut boundary = BoundaryPath::external();
|
let mut boundary = BoundaryPath::external();
|
||||||
boundary.flags.set_polyline(true);
|
boundary.flags.set_polyline(true);
|
||||||
boundary.edges.push(BoundaryEdge::Polyline(PolylineEdge::new(ring, true)));
|
boundary.edges.push(BoundaryEdge::Polyline(edge));
|
||||||
hatch.add_path(boundary);
|
hatch.add_path(boundary);
|
||||||
hatch.elevation = points.first().map_or(0.0, |p| p[2]);
|
hatch.elevation = points[0][2];
|
||||||
EntityType::Hatch(Box::new(hatch))
|
Some(EntityType::Hatch(Box::new(hatch)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A complex chain's vertices and bulges: lines and line strings add their
|
/// A complex chain's vertices and bulges: lines and line strings add their
|
||||||
/// points, circular arcs a bulge, other arcs their points.
|
/// points, circular arcs in the drawing plane a bulge, other arcs their
|
||||||
|
/// points. A chain whose vertices are not at one z comes out as points
|
||||||
|
/// only (a 3D polyline has no bulges).
|
||||||
fn chain(parts: &[DgnGeom]) -> (Vec<P3>, Vec<f64>) {
|
fn chain(parts: &[DgnGeom]) -> (Vec<P3>, Vec<f64>) {
|
||||||
|
let (points, bulges) = chain_points(parts, true);
|
||||||
|
if flat(&points) {
|
||||||
|
(points, bulges)
|
||||||
|
} else {
|
||||||
|
chain_points(parts, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn chain_points(parts: &[DgnGeom], use_bulges: bool) -> (Vec<P3>, Vec<f64>) {
|
||||||
let mut points: Vec<P3> = Vec::new();
|
let mut points: Vec<P3> = Vec::new();
|
||||||
let mut bulges: Vec<f64> = Vec::new();
|
let mut bulges: Vec<f64> = Vec::new();
|
||||||
let mut add = |p: P3, bulge_before: Option<f64>| {
|
let mut add = |p: P3, bulge_before: Option<f64>| {
|
||||||
let same = points
|
let same = points.last().is_some_and(|q| (0..3).all(|c| (q[c] - p[c]).abs() < 1e-9));
|
||||||
.last()
|
|
||||||
.is_some_and(|q| (q[0] - p[0]).abs() < 1e-9 && (q[1] - p[1]).abs() < 1e-9);
|
|
||||||
if let (Some(b), Some(last)) = (bulge_before, bulges.last_mut()) {
|
if let (Some(b), Some(last)) = (bulge_before, bulges.last_mut()) {
|
||||||
*last = b;
|
*last = b;
|
||||||
}
|
}
|
||||||
@@ -273,7 +303,7 @@ fn chain(parts: &[DgnGeom]) -> (Vec<P3>, Vec<f64>) {
|
|||||||
v,
|
v,
|
||||||
start,
|
start,
|
||||||
sweep,
|
sweep,
|
||||||
} if circular(*u, *v) => {
|
} if use_bulges && circular(*u, *v) && arc_plane(*u, *v).0[2] > 1.0 - 1e-9 => {
|
||||||
let at = |t: f64| -> P3 {
|
let at = |t: f64| -> P3 {
|
||||||
std::array::from_fn(|c| center[c] + t.cos() * u[c] + t.sin() * v[c])
|
std::array::from_fn(|c| center[c] + t.cos() * u[c] + t.sin() * v[c])
|
||||||
};
|
};
|
||||||
@@ -752,10 +782,10 @@ impl<'a> Converter<'a> {
|
|||||||
}
|
}
|
||||||
DgnGeom::Line(a, b) => vec![EntityType::Line(Line::from_points(v3(*a), v3(*b)))],
|
DgnGeom::Line(a, b) => vec![EntityType::Line(Line::from_points(v3(*a), v3(*b)))],
|
||||||
// A filled shape is its outline and a solid hatch inside it.
|
// A filled shape is its outline and a solid hatch inside it.
|
||||||
DgnGeom::Points(pts) if pts.len() >= 3 && element.filled => {
|
DgnGeom::Points(pts) if pts.len() >= 2 => {
|
||||||
vec![polyline(pts, &[]), solid_hatch(pts)]
|
let fill = element.filled.then(|| solid_hatch(pts, &[])).flatten();
|
||||||
|
std::iter::once(polyline(pts, &[])).chain(fill).collect()
|
||||||
}
|
}
|
||||||
DgnGeom::Points(pts) if pts.len() >= 2 => vec![polyline(pts, &[])],
|
|
||||||
DgnGeom::Points(_) => Vec::new(),
|
DgnGeom::Points(_) => Vec::new(),
|
||||||
DgnGeom::Arc {
|
DgnGeom::Arc {
|
||||||
center,
|
center,
|
||||||
@@ -783,11 +813,11 @@ impl<'a> Converter<'a> {
|
|||||||
}
|
}
|
||||||
DgnGeom::Chain(parts) => {
|
DgnGeom::Chain(parts) => {
|
||||||
let (points, bulges) = chain(parts);
|
let (points, bulges) = chain(parts);
|
||||||
if points.len() >= 2 {
|
if points.len() < 2 {
|
||||||
vec![polyline(&points, &bulges)]
|
return Vec::new();
|
||||||
} else {
|
|
||||||
Vec::new()
|
|
||||||
}
|
}
|
||||||
|
let fill = element.filled.then(|| solid_hatch(&points, &bulges)).flatten();
|
||||||
|
std::iter::once(polyline(&points, &bulges)).chain(fill).collect()
|
||||||
}
|
}
|
||||||
// V7 text is TEXT, V8 text an MTEXT.
|
// V7 text is TEXT, V8 text an MTEXT.
|
||||||
DgnGeom::Text { font: Some(_), .. } => {
|
DgnGeom::Text { font: Some(_), .. } => {
|
||||||
|
|||||||
Reference in new issue
Block a user