Layouts: first opening follows the page setup; viewports picked by their frame

- A layout opens for the first time when its page setup was never
  initialized (no paper size, no media name) or it was created in this
  session; its page setup is initialized then, and with
  LAYOUTCREATEVIEWPORT it gets its first viewport. A layout never opened
  is saved with an uninitialized page setup and no viewports, so it gets
  its viewport when the file is opened again, here or in the reference.
- Select Viewport for view: a viewport is selected when the pick box
  (PICKBOX pixels at the sheet's zoom) touches its frame; a pick well
  inside or outside selects nothing and the prompt repeats.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
ramox81andClaude Opus 5.5 committed 2026-10-13 12:02:26 +03:00
1 parent 006be8909e
commit c55dbb886e
7 files changed
+88 -6

No files matched your search

+6
View File
@@ -80,6 +80,12 @@ impl OpenCADStudio {
visual_styles: visual_styles.into_iter().map(|(name, _)| name).collect(),
live_sections: live_sections.into_iter().map(|(name, _)| name).collect(),
sheet_viewports,
// PICKBOX pixels in sheet units, at the sheet camera's zoom.
sheet_pick_tolerance: {
let height = scene.camera.borrow().ortho_size() as f64 * 2.0;
let (_, pixels) = scene.selection.borrow().view.vp_size;
f64::from(self.pick_box) * height / f64::from(pixels).max(1.0)
},
}
}
+1
View File
@@ -6538,6 +6538,7 @@ properties={:.1}ms picked={}",
.as_ref()
.map(|style| style.name.clone())
.unwrap_or_default();
self.tabs[i].scene.new_layout_plot_style = plot_style.clone();
for obj in self.tabs[i].scene.document.objects.values_mut() {
if let codec::objects::ObjectType::Layout(l) = obj {
if l.name == new_name {
+10 -2
View File
@@ -71,6 +71,9 @@ pub struct ViewNames {
/// (handle, [min x, min y, max x, max y]), one of which takes a
/// model-space view being restored.
pub sheet_viewports: Option<Vec<(u64, [f64; 4])>>,
/// The pick box (PICKBOX pixels) in sheet units: a viewport is selected
/// by its frame, within this distance of it.
pub sheet_pick_tolerance: f64,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
@@ -513,9 +516,14 @@ impl CadCommand for DashViewCommand {
name, first.x, first.y, pt.x, pt.y
)),
Step::RestoreViewport(name) => {
// The topmost viewport under the pick.
// The topmost viewport whose frame the pick box touches; a
// pick well inside a viewport selects nothing, as in the
// reference.
let tol = self.names.sheet_pick_tolerance.max(0.0);
let hit = self.names.sheet_viewports.iter().flatten().rev().find(|(_, [x0, y0, x1, y1])| {
pt.x >= *x0 && pt.x <= *x1 && pt.y >= *y0 && pt.y <= *y1
let outside = pt.x < x0 - tol || pt.x > x1 + tol || pt.y < y0 - tol || pt.y > y1 + tol;
let inside = pt.x > x0 + tol && pt.x < x1 - tol && pt.y > y0 + tol && pt.y < y1 - tol;
!outside && !inside
});
match hit {
Some((handle, _)) => CmdResult::Dispatch(format!("-VIEW >RESTOREVP {name}\u{1f}{handle:X}")),
+9 -4
View File
@@ -1378,10 +1378,15 @@ impl Scene {
// Every paper layout has a full-screen sheet viewport that holds
// its view; create one if a loaded file lacks it.
let layout = self.current_layout.clone();
// A layout opening for the first time: one whose sheet had to be
// made, or one created in this session.
let created = self.ensure_sheet_viewport(&layout);
if self.unopened_layouts.remove(&layout) || created {
// A layout opening for the first time, as the reference tells
// it: its page setup was never initialized (a file's layout
// with no paper), or it was created in this session and not
// opened yet. Its page setup is initialized and, with
// LAYOUTCREATEVIEWPORT, it gets its first viewport.
let unopened = self.unopened_layouts.remove(&layout);
let initialized = self.initialize_layout_page_setup(&layout);
self.ensure_sheet_viewport(&layout);
if unopened || initialized {
self.add_layout_default_viewport(&layout);
}
self.apply_sheet_viewport_camera()
+1
View File
@@ -3098,6 +3098,7 @@ impl Scene {
if let codec::objects::ObjectType::Layout(l) = obj {
if l.name != "Model" {
crate::scene::apply_default_page_setup(l, "");
self.unopened_layouts.insert(l.name.clone());
}
}
}
+58
View File
@@ -2360,6 +2360,9 @@ pub struct Scene {
/// Layouts created in this session and not opened yet: the first time
/// one opens it gets the LAYOUTCREATEVIEWPORT viewport.
pub(crate) unopened_layouts: std::collections::HashSet<String>,
/// The plot style table a layout's page setup takes when it is
/// initialized on first opening.
pub(crate) new_layout_plot_style: String,
layout_names_cache: RefCell<Option<(u64, std::sync::Arc<Vec<String>>)>>,
/// Bump on any annotation-scale list mutation.
scale_epoch: u64,
@@ -2773,6 +2776,7 @@ impl Scene {
layout_type_names_cache: RefCell::new(None),
layout_epoch: 0,
unopened_layouts: Default::default(),
new_layout_plot_style: String::new(),
layout_names_cache: RefCell::new(None),
scale_epoch: 0,
scale_picker_cache: RefCell::new(None),
@@ -4737,6 +4741,26 @@ impl Scene {
true
}
/// Gives a layout whose page setup was never initialized (no paper size
/// and no media name, as the reference leaves a layout until it is first
/// opened) the default page setup; true when it did.
pub fn initialize_layout_page_setup(&mut self, layout_name: &str) -> bool {
let plot_style = self.new_layout_plot_style.clone();
for object in self.document.objects.values_mut() {
if let ObjectType::Layout(layout) = object {
if layout.name == layout_name {
if !page_setup_uninitialized(layout) {
return false;
}
crate::scene::apply_default_page_setup(layout, &plot_style);
self.bump_layout_epoch();
return true;
}
}
}
false
}
/// The viewport a layout gets when it first opens (LAYOUTCREATEVIEWPORT),
/// as the reference makes it: centred on the printable area, 0.8 of its
/// width and height, showing the model's extents.
@@ -12732,6 +12756,40 @@ vis_index={:.1} visible_probe={:.1}",
}
}
/// A page setup never initialized: no paper size and no media name (the
/// reference writes a layout so until it is first opened).
pub fn page_setup_uninitialized(layout: &codec::objects::Layout) -> bool {
layout.paper_size.trim().is_empty() && layout.paper_width <= 0.0 && layout.paper_height <= 0.0
}
/// A layout written as never opened: the page setup fields back to the
/// uninitialized state and no viewports, as the reference stores a layout
/// it has not opened yet.
pub(crate) fn write_layout_unopened(document: &mut codec::CadDocument, layout_name: &str) {
let fresh = codec::objects::Layout::new(layout_name);
let mut viewports = Vec::new();
for object in document.objects.values_mut() {
if let ObjectType::Layout(layout) = object {
if layout.name == layout_name {
layout.paper_size = fresh.paper_size.clone();
layout.paper_width = fresh.paper_width;
layout.paper_height = fresh.paper_height;
layout.plot_rotation = fresh.plot_rotation;
layout.plot_paper_units = fresh.plot_paper_units;
layout.min_limits = fresh.min_limits;
layout.max_limits = fresh.max_limits;
layout.min_extents = fresh.min_extents;
layout.max_extents = fresh.max_extents;
viewports = std::mem::take(&mut layout.viewports);
layout.viewport = Handle::NULL;
}
}
}
for handle in viewports {
document.remove_entity(handle);
}
}
/// LAYOUTCREATEVIEWPORT: a layout opened for the first time gets a viewport.
static LAYOUT_CREATE_VIEWPORT: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
+3
View File
@@ -2535,6 +2535,9 @@ impl super::Scene {
}
crate::io::xref::strip_resolved_xref_content(&mut document);
crate::entities::field::stamp_save_dates(&mut document);
for name in &self.unopened_layouts {
crate::scene::write_layout_unopened(&mut document, name);
}
document
}