forked from 1b-agent/opencadstudio-upstream
Merge pull request #1235 with kernel-based dimension leaders
This commit is contained in:
commit
64e64c80a1
4 files changed
+213
-24
No files matched your search
Generated
+2
-2
@@ -896,7 +896,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||
[[package]]
|
||||
name = "cadkernel"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=935d90b86a99377dc95cba92e99a9b109180e2f6#935d90b86a99377dc95cba92e99a9b109180e2f6"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=d3830bb5230bd80e9f8abaa78b9f85d15161536a#d3830bb5230bd80e9f8abaa78b9f85d15161536a"
|
||||
dependencies = [
|
||||
"acadrust",
|
||||
"cavalier_contours",
|
||||
@@ -907,7 +907,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "cadkernel-constraints"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=935d90b86a99377dc95cba92e99a9b109180e2f6#935d90b86a99377dc95cba92e99a9b109180e2f6"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=d3830bb5230bd80e9f8abaa78b9f85d15161536a#d3830bb5230bd80e9f8abaa78b9f85d15161536a"
|
||||
dependencies = [
|
||||
"nalgebra",
|
||||
]
|
||||
|
||||
+2
-2
@@ -32,8 +32,8 @@ rfd = "0.17"
|
||||
clap = { version = "4", features = ["derive", "string"] }
|
||||
env_logger = "0.11"
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "6f3711809e39e9ffdf16a63086079b6475d97175", features = ["serde"] }
|
||||
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "935d90b86a99377dc95cba92e99a9b109180e2f6", features = ["acis", "offset"] }
|
||||
cadkernel-constraints = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "935d90b86a99377dc95cba92e99a9b109180e2f6" }
|
||||
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "d3830bb5230bd80e9f8abaa78b9f85d15161536a", features = ["acis", "offset"] }
|
||||
cadkernel-constraints = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "d3830bb5230bd80e9f8abaa78b9f85d15161536a" }
|
||||
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
|
||||
flate2 = "1"
|
||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }
|
||||
|
||||
+131
-20
@@ -3768,9 +3768,9 @@ fn tessellate_dimension_inner(
|
||||
// DIMTMOVE=1 connects the dimension line to rendered text.
|
||||
if let Some(s) = style {
|
||||
if s.dimtmove == 1 {
|
||||
if let Some((anchor, txt)) = dimtmove_leader_endpoints(dim, text_layout.position) {
|
||||
if let Some((anchor, txt, under_text)) = dimtmove_leader_endpoints(dim, text_layout) {
|
||||
let gap = dim_txt as f32 * 0.5;
|
||||
if (txt - anchor).length() > gap * 2.0 {
|
||||
if under_text || (txt - anchor).length() > gap * 2.0 {
|
||||
add_segment_with_text_break(
|
||||
&mut geom.dim_lines,
|
||||
anchor,
|
||||
@@ -4297,28 +4297,51 @@ fn split_ext_lines(points: &[[f32; 3]]) -> (Vec<[f32; 3]>, Vec<[f32; 3]>) {
|
||||
}
|
||||
|
||||
/// Endpoints for the DIMTMOVE=1 leader.
|
||||
fn dimtmove_leader_endpoints(dim: &Dimension, txt: Vec3) -> Option<(Vec3, Vec3)> {
|
||||
fn dimtmove_leader_endpoints(
|
||||
dim: &Dimension,
|
||||
text: DimensionTextLayout,
|
||||
) -> Option<(Vec3, Vec3, bool)> {
|
||||
let lv = |v| vec3_local(v);
|
||||
let anchor = match dim {
|
||||
let txt = text.position;
|
||||
let linear = |first: Vec3, second: Vec3, definition: Vec3, axis: [f64; 2]| {
|
||||
let bounds = text.break_box?;
|
||||
let xy = |point: Vec3| [point.x as f64, point.y as f64];
|
||||
let (leader, under_text) = cadkernel::geom2d::linear_dimension_leader(
|
||||
cadkernel::geom2d::Line { start: xy(first), end: xy(second) },
|
||||
xy(definition),
|
||||
axis,
|
||||
xy(txt),
|
||||
[
|
||||
(bounds.half_width + bounds.padding) as f64,
|
||||
(bounds.half_height + bounds.padding) as f64,
|
||||
],
|
||||
bounds.rotation,
|
||||
)?;
|
||||
let point = |p: [f64; 2]| Vec3::new(p[0] as f32, p[1] as f32, first.z);
|
||||
Some((point(leader.start), point(leader.end), under_text))
|
||||
};
|
||||
match dim {
|
||||
Dimension::Linear(d) => {
|
||||
let perp = Vec3::new(-(d.rotation.sin() as f32), d.rotation.cos() as f32, 0.0);
|
||||
let first = lv(d.first_point);
|
||||
let second = lv(d.second_point);
|
||||
let def = lv(d.definition_point);
|
||||
let off1 = def.dot(perp) - first.dot(perp);
|
||||
let off2 = def.dot(perp) - second.dot(perp);
|
||||
(first + perp * off1 + second + perp * off2) * 0.5
|
||||
return linear(
|
||||
lv(d.first_point),
|
||||
lv(d.second_point),
|
||||
lv(d.definition_point),
|
||||
[d.rotation.cos(), d.rotation.sin()],
|
||||
);
|
||||
}
|
||||
Dimension::Aligned(d) => {
|
||||
let first = lv(d.first_point);
|
||||
let second = lv(d.second_point);
|
||||
let axis = normalized_or(second - first, Vec3::X);
|
||||
let perp = Vec3::new(-axis.y, axis.x, 0.0);
|
||||
let def = lv(d.definition_point);
|
||||
let off1 = def.dot(perp) - first.dot(perp);
|
||||
let off2 = def.dot(perp) - second.dot(perp);
|
||||
(first + perp * off1 + second + perp * off2) * 0.5
|
||||
return linear(
|
||||
first,
|
||||
second,
|
||||
lv(d.definition_point),
|
||||
[d.second_point.x - d.first_point.x, d.second_point.y - d.first_point.y],
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
let anchor = match dim {
|
||||
Dimension::Radius(_) => return None,
|
||||
Dimension::Diameter(d) => {
|
||||
let chord = lv(d.angle_vertex);
|
||||
@@ -4353,7 +4376,7 @@ fn dimtmove_leader_endpoints(dim: &Dimension, txt: Vec3) -> Option<(Vec3, Vec3)>
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
Some((anchor, txt))
|
||||
Some((anchor, txt, false))
|
||||
}
|
||||
|
||||
/// Build a rectangle of filled triangles sitting under the dim text, used
|
||||
@@ -7188,8 +7211,10 @@ pub(crate) fn baked_large_radial_geometry(
|
||||
}
|
||||
}
|
||||
if style.is_some_and(|style| style.dimtmove == 1) {
|
||||
if let Some((anchor, endpoint)) = dimtmove_leader_endpoints(dimension, text.position) {
|
||||
if anchor.distance(endpoint) > text_height as f32 {
|
||||
if let Some((anchor, endpoint, under_text)) =
|
||||
dimtmove_leader_endpoints(dimension, text)
|
||||
{
|
||||
if under_text || anchor.distance(endpoint) > text_height as f32 {
|
||||
add_segment_with_text_break(
|
||||
&mut geometry.dim_lines,
|
||||
anchor,
|
||||
@@ -7527,3 +7552,89 @@ mod arch_format_tests {
|
||||
assert_eq!(format_engineering(-0.001, 2), "0'-0.00\"");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod dimtmove_leader_tests {
|
||||
use super::*;
|
||||
use acadrust::entities::DimensionLinear;
|
||||
use acadrust::types::Vector3;
|
||||
|
||||
fn v(x: f64, y: f64) -> Vector3 {
|
||||
Vector3::new(x, y, 0.0)
|
||||
}
|
||||
|
||||
fn layout(position: Vec3, half_width: f32) -> DimensionTextLayout {
|
||||
DimensionTextLayout {
|
||||
position,
|
||||
width: half_width * 2.0,
|
||||
break_box: Some(TextBreak {
|
||||
center: position,
|
||||
half_width,
|
||||
half_height: 1.0,
|
||||
padding: 0.0,
|
||||
rotation: 0.0,
|
||||
}),
|
||||
horizontal: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Text outside along the axis → the dim line extends underneath the text.
|
||||
#[test]
|
||||
fn text_outside_extends_dim_line_under_text() {
|
||||
let mut d = DimensionLinear::horizontal(v(0.0, 0.0), v(20.0, 0.0));
|
||||
d.definition_point = v(0.0, -5.0); // dimension line at y=-5
|
||||
let dim = Dimension::Linear(d);
|
||||
// text anchor outside on the right: centre x=30, half width 3 → far edge 33
|
||||
let txt = vec3_local(v(30.0, -3.0));
|
||||
let (anchor, end, under) = dimtmove_leader_endpoints(&dim, layout(txt, 3.0)).expect("leader");
|
||||
assert!(under, "outside text extends the dimension line");
|
||||
assert!((anchor.x - 20.0).abs() < 1e-4 && (anchor.y + 5.0).abs() < 1e-4, "near end should be the dim line's right end (20,-5), got {anchor:?}");
|
||||
assert!((end.x - 33.0).abs() < 1e-4 && (end.y + 5.0).abs() < 1e-4, "far end should be the text's far edge (33,-5), got {end:?}");
|
||||
}
|
||||
|
||||
/// Text outside on the left → extends leftwards.
|
||||
#[test]
|
||||
fn text_outside_left_extends_leftwards() {
|
||||
let mut d = DimensionLinear::horizontal(v(0.0, 0.0), v(20.0, 0.0));
|
||||
d.definition_point = v(0.0, -5.0);
|
||||
let dim = Dimension::Linear(d);
|
||||
let txt = vec3_local(v(-12.0, -3.0));
|
||||
let (anchor, end, under) = dimtmove_leader_endpoints(&dim, layout(txt, 2.0)).expect("leader");
|
||||
assert!(under);
|
||||
assert!((anchor.x - 0.0).abs() < 1e-4, "near end should be the dim line's left end, got {anchor:?}");
|
||||
assert!((end.x + 14.0).abs() < 1e-4, "far end should be the text's far edge at -14, got {end:?}");
|
||||
}
|
||||
|
||||
/// Text still inside (only moved up/down) → leader from the dim line to the text.
|
||||
#[test]
|
||||
fn text_inside_connects_to_text() {
|
||||
let mut d = DimensionLinear::horizontal(v(0.0, 0.0), v(20.0, 0.0));
|
||||
d.definition_point = v(0.0, -5.0);
|
||||
let dim = Dimension::Linear(d);
|
||||
let txt = vec3_local(v(10.0, 2.0));
|
||||
let (anchor, end, under) = dimtmove_leader_endpoints(&dim, layout(txt, 3.0)).expect("leader");
|
||||
assert!(!under, "inside → keep the connect-to-text leader (text break allowed)");
|
||||
assert!((anchor.x - 10.0).abs() < 1e-4 && (anchor.y + 5.0).abs() < 1e-4, "anchor should be the dim line point facing the text, got {anchor:?}");
|
||||
assert!((end.y - 2.0).abs() < 1e-4, "end should be the text position, got {end:?}");
|
||||
}
|
||||
|
||||
/// Radius dimensions draw no leader (unchanged).
|
||||
#[test]
|
||||
fn radius_has_no_leader() {
|
||||
let r = acadrust::entities::DimensionRadius::new(v(0.0, 0.0), v(0.0, 5.0));
|
||||
assert!(dimtmove_leader_endpoints(&Dimension::Radius(r), layout(vec3_local(v(0.0, 8.0)), 2.0)).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_does_not_draw_through_text() {
|
||||
let mut d = DimensionLinear::horizontal(v(0.0, 0.0), v(20.0, 0.0));
|
||||
d.definition_point = v(0.0, -5.0);
|
||||
let text = layout(Vec3::new(30.0, -5.0, 0.0), 3.0);
|
||||
let (start, end, outside) = dimtmove_leader_endpoints(&Dimension::Linear(d), text).unwrap();
|
||||
assert!(outside);
|
||||
let mut points = Vec::new();
|
||||
add_segment_with_text_break(&mut points, start, end, text.break_box);
|
||||
let finite: Vec<_> = points.into_iter().filter(|point| point[0].is_finite()).collect();
|
||||
assert_eq!(finite, vec![[20.0, -5.0, 0.0], [27.0, -5.0, 0.0]]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
//! Check DIMTMOVE through the scene's actual dimension tessellation.
|
||||
|
||||
use acadrust::entities::{Dimension, DimensionAligned, DimensionLinear};
|
||||
use acadrust::types::Vector3;
|
||||
use acadrust::xdata::XDataValue;
|
||||
use acadrust::EntityType;
|
||||
use OpenCADStudio::entities::dim_override as ov;
|
||||
use OpenCADStudio::scene::Scene;
|
||||
|
||||
#[test]
|
||||
fn dim_leader_extends_only_when_requested() {
|
||||
for aligned in [false, true] {
|
||||
for movement in [0, 1, 2] {
|
||||
for (text_x, text_y) in [
|
||||
(-18.0, 89.0),
|
||||
(98.0, 89.0),
|
||||
(19.9, 89.0),
|
||||
(80.1, 89.0),
|
||||
(-18.0, 86.0),
|
||||
(98.0, 86.0),
|
||||
] {
|
||||
let mut scene = Scene::new();
|
||||
let first = Vector3::new(20.0, 100.0, 0.0);
|
||||
let second = Vector3::new(80.0, 100.0, 0.0);
|
||||
let definition = Vector3::new(20.0, 86.0, 0.0);
|
||||
let mut dim = if aligned {
|
||||
let mut d = DimensionAligned::new(first, second);
|
||||
d.definition_point = definition;
|
||||
Dimension::Aligned(d)
|
||||
} else {
|
||||
let mut d = DimensionLinear::horizontal(first, second);
|
||||
d.definition_point = definition;
|
||||
Dimension::Linear(d)
|
||||
};
|
||||
let base = dim.base_mut();
|
||||
base.definition_point = definition;
|
||||
base.text_middle_point = Vector3::new(text_x, text_y, 0.0);
|
||||
base.insertion_point = base.text_middle_point;
|
||||
base.text_user_positioned = true;
|
||||
base.style_name = "Standard".into();
|
||||
let handle = scene.add_entity(EntityType::Dimension(dim));
|
||||
for (code, value) in [
|
||||
(ov::DIMTMOVE, XDataValue::Integer16(movement)),
|
||||
(ov::DIMTXT, XDataValue::Real(3.5)),
|
||||
(ov::DIMASZ, XDataValue::Real(3.5)),
|
||||
(ov::DIMGAP, XDataValue::Real(1.0)),
|
||||
] {
|
||||
ov::set(&mut scene.document, handle, code, Some(value));
|
||||
}
|
||||
scene.bump_entities(&[(handle, OpenCADStudio::scene::ChangeKind::Modified)]);
|
||||
let wires = scene.entity_wires();
|
||||
let extension = wires.iter().any(|wire| {
|
||||
(0..wire.points.len().saturating_sub(1)).any(|index| {
|
||||
let a = wire.point_world(index, 1.0);
|
||||
let b = wire.point_world(index + 1, 1.0);
|
||||
if !a.is_finite() || !b.is_finite() {
|
||||
return false;
|
||||
}
|
||||
let near = if text_x > 80.0 { 80.0 } else { 20.0 };
|
||||
(a.x - near).abs() < 1e-4
|
||||
&& (a.y - 86.0).abs() < 1e-4
|
||||
&& (b.y - 86.0).abs() < 1e-4
|
||||
&& if text_x > 80.0 {
|
||||
b.x > text_x
|
||||
} else {
|
||||
b.x < text_x
|
||||
}
|
||||
})
|
||||
});
|
||||
assert_eq!(
|
||||
extension,
|
||||
movement == 1 && text_y == 89.0,
|
||||
"aligned={aligned}, DIMTMOVE={movement}, text=({text_x},{text_y})"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user