molgfx-core 0.3.4

The semantic scene graph: columnar tables, GPU record layouts, the borrowed coordinate seam.
Documentation
use crate::{
    Annotation, AnnotationAnchor, AtomSelection, EntityKind, EntityRef, Measurement, Scene, fixture,
};
use molgfx_math::Vec3;

fn scene() -> Scene {
    Scene::from_structure(&fixture::structure()).unwrap_or_else(|error| panic!("{error}"))
}

fn owner(scene: &Scene) -> crate::StructureHandle {
    scene
        .structures()
        .next()
        .map_or_else(|| panic!("fixture owner exists"), |(handle, _)| handle)
}

fn anchor(position: Vec3) -> AnnotationAnchor {
    AnnotationAnchor::world(position).unwrap_or_else(|error| panic!("{error}"))
}

#[test]
fn annotations_and_measurements_share_stable_pick_rows_without_type_confusion() {
    let mut scene = scene();
    let owner = owner(&scene);
    let note = Annotation::note(owner, anchor(Vec3::ZERO), "active-site note")
        .unwrap_or_else(|error| panic!("{error}"));
    let note = scene
        .add_annotation(note)
        .unwrap_or_else(|error| panic!("{error}"));
    let distance = Measurement::distance(
        owner,
        [anchor(Vec3::ZERO), anchor(Vec3::X)],
        1.0,
        "caller:test",
    )
    .unwrap_or_else(|error| panic!("{error}"));
    let distance = scene
        .add_measurement(distance)
        .unwrap_or_else(|error| panic!("{error}"));
    let note_entity = EntityRef {
        structure: owner,
        kind: EntityKind::Label,
        index: Scene::annotation_row(note),
    };
    let measurement_entity = EntityRef {
        structure: owner,
        kind: EntityKind::Measurement,
        index: Scene::measurement_row(distance),
    };
    assert_eq!(
        scene
            .annotation_for_entity(note_entity)
            .map(|(handle, value)| (handle, value.text())),
        Some((note, "active-site note"))
    );
    assert!(scene.measurement_for_entity(note_entity).is_none());
    assert_eq!(
        scene
            .measurement_for_entity(measurement_entity)
            .map(|(handle, value)| (handle, value.label())),
        Some((distance, "1.00 Å"))
    );
    assert!(scene.annotation_for_entity(measurement_entity).is_none());
}

#[test]
fn label_edits_and_removals_advance_one_independent_revision() {
    let mut scene = scene();
    let owner = owner(&scene);
    let selection = scene.add_selection(AtomSelection::All);
    let region =
        Annotation::region(owner, selection, "protein").unwrap_or_else(|error| panic!("{error}"));
    let before = scene.label_revision();
    let handle = scene
        .add_annotation(region)
        .unwrap_or_else(|error| panic!("{error}"));
    assert!(scene.label_revision() > before);
    let after_add = scene.label_revision();
    scene
        .annotation_mut(handle)
        .unwrap_or_else(|| panic!("annotation resolves"))
        .set_visible(false);
    assert!(scene.label_revision() > after_add);
    assert_eq!(scene.annotations().count(), 1);
    assert_eq!(scene.measurements().count(), 0);
    assert!(scene.remove_annotation(handle).is_some());
    assert!(scene.annotation(handle).is_none());
}

#[test]
fn absent_owners_regions_and_source_structures_are_stale_errors() {
    let mut scene = scene();
    let owner = owner(&scene);
    let stale_selection = crate::SelectionHandle(crate::handle::RawHandle::new_for_test(999, 0));
    let region = Annotation::region(owner, stale_selection, "gone")
        .unwrap_or_else(|error| panic!("{error}"));
    assert!(matches!(
        scene.add_annotation(region),
        Err(crate::CoreError::StaleHandle)
    ));
    let stale_entity = EntityRef {
        structure: crate::StructureHandle(crate::handle::RawHandle::new_for_test(999, 0)),
        kind: EntityKind::Atom,
        index: 0,
    };
    let anchored = AnnotationAnchor::entity(Vec3::ZERO, stale_entity)
        .unwrap_or_else(|error| panic!("{error}"));
    let note = Annotation::note(owner, anchored, "stale").unwrap_or_else(|error| panic!("{error}"));
    assert!(matches!(
        scene.add_annotation(note),
        Err(crate::CoreError::StaleHandle)
    ));
}