feat(viewport): Ctrl+click opens an entity PE_URL hyperlink

Entities that carry the standard PE_URL XDATA record (what HYPERLINK writes)
can now be followed from the canvas: Ctrl+click opens the link in the system
browser and consumes the click, so no box/lasso selection starts.

- scene::selection::pe_url_of() reads the record with the same rule as the
  Properties panel hyperlink field; unit-tested.
- The ViewCube/compass hit region wins over a hyperlink drawn beneath it.
- Unchanged behaviour when Ctrl is not held or the entity has no link.
This commit is contained in:
ysdirector committed 2026-09-13 23:53:50 +08:00
1 parent 05d95e2e72
commit 171fc48987
3 files changed
+116

No files matched your search

+1
View File
@@ -46,6 +46,7 @@ mod preview;
mod project;
mod scene_markers;
mod selection;
pub use selection::pe_url_of;
pub mod sketch_constraints;
mod sketch_persist;
mod sketch_solve;
+54
View File
@@ -1,5 +1,22 @@
use super::*;
/// The entity's standard `PE_URL` hyperlink (the record AutoCAD's HYPERLINK
/// command writes).
///
/// Returns the first non-empty string value of the record; `None` when the
/// entity or record is missing, the record holds no string, or it is empty.
/// Matches how the Properties panel reads its `"hyperlink"` field.
pub fn pe_url_of(doc: &CadDocument, handle: Handle) -> Option<String> {
doc.get_entity(handle)
.and_then(|entity| entity.common().extended_data.get_record("PE_URL"))
.and_then(|record| {
record.values.iter().find_map(|value| match value {
acadrust::xdata::XDataValue::String(text) if !text.is_empty() => Some(text.clone()),
_ => None,
})
})
}
impl Scene {
// ── Selection ─────────────────────────────────────────────────────────
/// Treat a classic LEADER and its attached annotation as one logical object.
@@ -1116,6 +1133,43 @@ impl Scene {
#[cfg(test)]
mod tests {
#[test]
fn pe_url_of_reads_standard_hyperlink_xdata() {
use acadrust::entities::Point;
use acadrust::xdata::{ExtendedDataRecord, XDataValue};
let mut doc = CadDocument::new();
// An entity carrying a PE_URL hyperlink resolves to that link.
let mut linked = Point::new();
let mut rec = ExtendedDataRecord::new("PE_URL");
rec.add_value(XDataValue::String("https://example.com/gui".to_string()));
linked.common.extended_data.add_record(rec);
let linked_handle = doc
.add_entity(EntityType::Point(linked))
.expect("linked entity added");
assert_eq!(
pe_url_of(&doc, linked_handle).as_deref(),
Some("https://example.com/gui")
);
// No PE_URL record -> None.
let plain_handle = doc
.add_entity(EntityType::Point(Point::new()))
.expect("plain entity added");
assert_eq!(pe_url_of(&doc, plain_handle), None);
// Empty string in the record -> None.
let mut empty = Point::new();
let mut empty_rec = ExtendedDataRecord::new("PE_URL");
empty_rec.add_value(XDataValue::String(String::new()));
empty.common.extended_data.add_record(empty_rec);
let empty_handle = doc
.add_entity(EntityType::Point(empty))
.expect("empty-link entity added");
assert_eq!(pe_url_of(&doc, empty_handle), None);
}
use super::*;
#[test]