forked from 1b-agent/opencadstudio-upstream
Complete Viewport: layout-tab canvas oracle, unique ids, derived scale
The shared driver now activates the layout that owns a viewport and requires a drawn frame. The host assigns a unique id of at least 2 and derives custom_scale as height / view_height on every scripted create or edit, as MVIEW does, so custom_scale is read-only. A control showed the default id 0 is not invisible, so the id matters for uniqueness. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
1 parent
5e6a830782
commit
6e2e073e29
6 files changed
+106
-12
No files matched your search
+95
-5
@@ -158,6 +158,26 @@ impl<'a> HostSession<'a> {
|
||||
if let EntityType::Dimension(dimension) = entity {
|
||||
crate::entities::dimension::normalize_scripted_dimension(dimension);
|
||||
}
|
||||
if let EntityType::Viewport(viewport) = entity {
|
||||
// A viewport needs an id unique in its layout and at least 2 (id 1
|
||||
// is the never-drawn sheet viewport, and a lone id 0 can be taken for
|
||||
// it), and its scale follows its paper height and model view height;
|
||||
// both are what the MVIEW command sets.
|
||||
if old.is_none() {
|
||||
let owner = viewport.common.owner_handle;
|
||||
let highest = self
|
||||
.document()
|
||||
.entities()
|
||||
.filter_map(|e| match e {
|
||||
EntityType::Viewport(other) if other.common.owner_handle == owner => Some(other.id),
|
||||
_ => None,
|
||||
})
|
||||
.max()
|
||||
.unwrap_or(1);
|
||||
viewport.id = (highest + 1).max(2);
|
||||
}
|
||||
viewport.custom_scale = viewport.height / viewport.view_height;
|
||||
}
|
||||
if let EntityType::SectionSymbol(symbol) = entity {
|
||||
// `points` is the canonical geometry: the point counts follow it
|
||||
// (they equal the point count in every verified export) and the
|
||||
@@ -3682,12 +3702,18 @@ mod tests {
|
||||
.unwrap_or_else(|| panic!("Python did not create the mesh: {}", last_output(&host)));
|
||||
let created = host.document().get_entity(handle).unwrap().clone();
|
||||
assert_eq!((case.digest)(&created), case.expect_created);
|
||||
// Viewports draw only on their paper-space layout tab, which this
|
||||
// driver does not activate, so they have no model-tab wire oracle.
|
||||
let has_wires = !matches!(created, EntityType::Viewport(_));
|
||||
if has_wires {
|
||||
assert!(!host.app.tabs[0].scene.wire_models_for(&[handle]).is_empty(), "no canvas geometry");
|
||||
// A paper-space viewport draws only on its layout tab, so the canvas
|
||||
// oracle first activates the layout that owns it.
|
||||
if matches!(created, EntityType::Viewport(_)) {
|
||||
let owner = created.common().owner_handle;
|
||||
let layout = host.document().objects.values().find_map(|object| match object {
|
||||
acadrust::objects::ObjectType::Layout(l) if l.block_record == owner => Some(l.name.clone()),
|
||||
_ => None,
|
||||
}).expect("the viewport's owner is a layout block");
|
||||
host.app.tabs[0].scene.set_current_layout(layout);
|
||||
}
|
||||
let has_wires = true;
|
||||
assert!(!host.app.tabs[0].scene.wire_models_for(&[handle]).is_empty(), "no canvas geometry");
|
||||
|
||||
run_script(&mut host, "edit", &case.edit.replace("HANDLE", &handle.value().to_string()).replace("TMPDIR", &tmp).replace("LAYER0", &layer0.unwrap_or_default().to_string()));
|
||||
let expected = host.document().get_entity(handle).unwrap().clone();
|
||||
@@ -5777,6 +5803,70 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn audit_python_viewport_ids_and_scale_over_real_ipc() {
|
||||
let Some(plugin_path) = std::env::var_os("OCS_TEST_PYTHON_PLUGIN") else {
|
||||
return;
|
||||
};
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
app.tabs[0].is_start = false;
|
||||
let mut host = HostSession::new(&mut app, 0);
|
||||
let paper = host.document().block_records.iter().find(|r| r.is_paper_space()).map(|r| r.handle).unwrap();
|
||||
let layout = host.document().objects.values().find_map(|o| match o {
|
||||
acadrust::objects::ObjectType::Layout(l) if l.block_record == paper => Some(l.name.clone()), _ => None }).unwrap();
|
||||
host.app.tabs[0].scene.set_current_layout(layout);
|
||||
let process = ocs_plugin_api::process::PluginProcess::spawn(
|
||||
std::path::Path::new(&plugin_path), &mut host, crate::plugin::v4_support::notification_handler(),
|
||||
).unwrap();
|
||||
let dispatch = |host: &mut HostSession<'_>, command: &str| {
|
||||
assert!(process.dispatch(host, command, &mut |_| {}).expect("Python dispatch"));
|
||||
};
|
||||
let last = |host: &HostSession<'_>| host.app.command_line.history.last().unwrap().text.clone();
|
||||
let viewports = |host: &HostSession<'_>| {
|
||||
let mut list: Vec<_> = host.document().entities().filter_map(|e| match e {
|
||||
EntityType::Viewport(v) => Some((v.common.handle, v.id)), _ => None }).collect();
|
||||
list.sort_by_key(|(h, _)| h.value());
|
||||
list
|
||||
};
|
||||
let create = |width: f64, height: f64, view_height: f64| format!(
|
||||
"PY_EVAL ocs.active_document.create_entity('Viewport', owner_handle={}, center=(50, 50, 0), width={width}, height={height}, view_height={view_height}).handle", paper.value());
|
||||
dispatch(&mut host, &create(80.0, 60.0, 50.0));
|
||||
dispatch(&mut host, &create(40.0, 30.0, 10.0));
|
||||
dispatch(&mut host, &create(20.0, 20.0, 20.0));
|
||||
let list = viewports(&host);
|
||||
assert_eq!(list.len(), 3, "{}", last(&host));
|
||||
let ids: Vec<i16> = list.iter().map(|(_, id)| *id).collect();
|
||||
assert_eq!(ids, vec![2, 3, 4], "scripted viewports get unique ids of at least 2: {ids:?}");
|
||||
for (handle, expected) in [(list[0].0, 60.0 / 50.0), (list[1].0, 30.0 / 10.0), (list[2].0, 1.0)] {
|
||||
let Some(EntityType::Viewport(v)) = host.document().get_entity(handle) else { unreachable!() };
|
||||
assert!((v.custom_scale - expected).abs() < 1e-12, "custom_scale {} vs {expected}", v.custom_scale);
|
||||
assert!(!host.app.tabs[0].scene.wire_models_for(&[handle]).is_empty(), "viewport {handle:?} draws a frame");
|
||||
}
|
||||
// The scale follows the geometry; it cannot be set directly.
|
||||
let second = list[0].0;
|
||||
dispatch(&mut host, &format!("PY_EVAL ocs.update_many('Zoom', [{{'handle':{}, 'view_height':25.0}}])", second.value()));
|
||||
let Some(EntityType::Viewport(zoomed)) = host.document().get_entity(second) else { unreachable!() };
|
||||
assert!((zoomed.custom_scale - 60.0 / 25.0).abs() < 1e-12, "{}", last(&host));
|
||||
assert_eq!(zoomed.id, 2, "an edit keeps the id");
|
||||
dispatch(&mut host, &format!("PY_EVAL ocs.update_many('Resize', [{{'handle':{}, 'height':90.0}}])", second.value()));
|
||||
let Some(EntityType::Viewport(resized)) = host.document().get_entity(second) else { unreachable!() };
|
||||
assert!((resized.custom_scale - 90.0 / 25.0).abs() < 1e-12);
|
||||
let before = host.document().get_entity(second).unwrap().clone();
|
||||
dispatch(&mut host, &format!("PY_EVAL ocs.update_many('Scale', [{{'handle':{}, 'custom_scale':3.0}}])", second.value()));
|
||||
assert!(last(&host).contains("read-only"), "{}", last(&host));
|
||||
assert_eq!(host.document().get_entity(second), Some(&before));
|
||||
// Control: the engine's default id 0 is not invisible here (there is no
|
||||
// sheet viewport to confuse it with), so the derived id matters for
|
||||
// uniqueness, not for visibility.
|
||||
let mut bare = acadrust::entities::Viewport::new();
|
||||
bare.common.owner_handle = paper;
|
||||
bare.width = 40.0;
|
||||
bare.height = 30.0;
|
||||
let bare = host.document_mut().add_entity(EntityType::Viewport(bare)).unwrap();
|
||||
assert_eq!(host.document().get_entity(bare).map(|e| match e { EntityType::Viewport(v) => v.id, _ => -1 }), Some(0));
|
||||
assert!(!host.app.tabs[0].scene.wire_models_for(&[bare]).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn staged_python_point_pick_and_cancel_over_real_ipc() {
|
||||
let Some(plugin_path) = std::env::var_os("OCS_TEST_PYTHON_PLUGIN") else {
|
||||
|
||||
Reference in new issue
Block a user