fix(leader): derive hookline for DWG leaders; size it from the dim style

DWG stores no "hookline exists" flag (only the direction bit), so every
DWG-loaded leader kept hookline_enabled=false and drew no hook. Derive
it: a text-annotated leader hooks when its final segment leaves the
horizontal by more than 15°, shared by the render and snap/grip paths.

Hook length comes from the dim style like the arrowhead (DIMASZ ×
DIMSCALE), and with DIMTAD "text above" it extends under the annotation
to the far edge of the MTEXT's laid-out extents — newly exposed by
acadrust (lock bumped); the old text-height heuristic stays as the
no-style fallback.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Fable 5 committed 2026-07-22 23:36:25 +03:00
1 parent 48e8988528
commit bf06a8e330
2 files changed
+75 -8

No files matched your search

Generated
+2 -2
View File
@@ -74,7 +74,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
[[package]]
name = "acadrust"
version = "0.4.0"
source = "git+https://github.com/OpenAEC-Foundation/acadifc?branch=main#a87435fc9eb8e47788176eab444a49a64876ca91"
source = "git+https://github.com/OpenAEC-Foundation/acadifc?branch=main#3832a9b6f598405f057e6e4e6d49db65f4d68834"
dependencies = [
"ahash 0.8.12",
"anyhow",
@@ -3180,7 +3180,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b577e2d69827c4740cba2b52efaad1c4cc7c73042860b199710b3575c68438d"
dependencies = [
"bytecount",
"memchr 1.0.2",
"memchr 2.8.3",
"nom 8.0.0",
]
+73 -6
View File
@@ -13,6 +13,36 @@ use crate::scene::model::wire_model::TangentGeom;
// ── TruckConvertible (used for snap/grip key-vertices) ─────────────────────
/// Whether a leader draws a horizontal hookline. DWG stores no "hookline
/// exists" flag (only the direction bit), so the stored flag alone would drop
/// every hookline on DWG-loaded leaders: a text-annotated leader gets a
/// horizontal hook whenever its final segment leaves the horizontal by more
/// than 15°.
fn draws_hookline(leader: &Leader) -> bool {
if leader.hookline_enabled {
return true;
}
let verts = &leader.vertices;
if leader.creation_type != LeaderCreationType::WithText
|| leader.annotation_handle.is_null()
|| verts.len() < 2
{
return false;
}
let last = &verts[verts.len() - 1];
let prev = &verts[verts.len() - 2];
let (dx, dy) = (last.x - prev.x, last.y - prev.y);
let len = (dx * dx + dy * dy).sqrt();
let h = leader.horizontal_direction;
let hl = (h.x * h.x + h.y * h.y).sqrt();
let cos = if len > 1e-9 && hl > 1e-9 {
((dx * h.x + dy * h.y) / (len * hl)).abs()
} else {
1.0
};
cos < (15.0f64).to_radians().cos()
}
fn to_truck(leader: &Leader) -> TruckEntity {
let verts = &leader.vertices;
let nan = [f64::NAN; 3];
@@ -64,7 +94,7 @@ fn to_truck(leader: &Leader) -> TruckEntity {
}
// Landing line at last vertex
if leader.hookline_enabled && verts.len() >= 2 {
if draws_hookline(leader) && verts.len() >= 2 {
let last = verts.last().unwrap();
let prev = &verts[verts.len() - 2];
// Landing runs along the leader's horizontal direction (UCS X for
@@ -683,19 +713,56 @@ impl LeaderTess for Leader {
fill_tris.extend(arrow_geom.arrow_fill);
}
if self.hookline_enabled {
if draws_hookline(self) {
let last = verts.last().unwrap();
let prev = &verts[verts.len() - 2];
let sign = if (last.x - prev.x) >= 0.0 {
1.0_f32
1.0_f64
} else {
-1.0_f32
-1.0_f64
};
let land_len = self.text_height as f32 * 1.5 * anno_scale;
let style = document.dim_styles.iter().find(|s| {
s.name.eq_ignore_ascii_case(&self.dimension_style)
|| (self.dimension_style.trim().is_empty()
&& s.name.eq_ignore_ascii_case("Standard"))
});
let dim_scale = dov::real(xd, dov::DIMSCALE)
.filter(|v| *v > 1e-6)
.or_else(|| style.map(|s| s.dimscale).filter(|v| *v > 1e-6))
.unwrap_or(anno_scale as f64);
// Hook length: DIMASZ, like the arrowhead, when a style resolves;
// the legacy text-height heuristic otherwise.
let mut land_len = match dov::real(xd, dov::DIMASZ)
.or_else(|| style.map(|s| s.dimasz))
{
Some(a) => a * dim_scale,
None => self.text_height * 1.5 * anno_scale as f64,
};
// With DIMTAD "text above", the annotation sits on top of the hook
// and the hook underlines it: extend to the far edge of the MTEXT's
// laid-out extents.
let dimtad = dov::int(xd, dov::DIMTAD)
.or_else(|| style.map(|s| s.dimtad))
.unwrap_or(0);
if dimtad != 0 {
if let Some(acadrust::entities::EntityType::MText(mt)) =
document.get_entity(self.annotation_handle)
{
if mt.extents_width > 0.0 {
let to_far_edge =
sign * (mt.insertion_point.x - last.x) + mt.extents_width;
land_len = land_len.max(to_far_edge);
}
}
}
let last_f = p3(last);
points.push(nan);
points.push(last_f);
points.push([last_f[0] + sign * land_len, last_f[1], last_f[2]]);
points.push([
last_f[0] + (sign * land_len) as f32,
last_f[1],
last_f[2],
]);
}
WireModel {