parent
f99337cb9a
commit
3897fb2661
5 changed files with 93 additions and 8 deletions
|
|
@ -695,6 +695,11 @@ impl OpenCADStudio {
|
||||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"ZOOM EXTENTS ALL" | "ZOOM EXTENTS ALL VIEWPORTS" | "ZEA" => {
|
||||||
|
self.tabs[i].scene.fit_all_model_viewports();
|
||||||
|
self.command_line.push_output("Zoom Extents — All Viewports");
|
||||||
|
}
|
||||||
|
|
||||||
"ZOOM EXTENTS" | "ZOOMEXTENTS" | "ZE" => {
|
"ZOOM EXTENTS" | "ZOOMEXTENTS" | "ZE" => {
|
||||||
self.tabs[i].scene.fit_all();
|
self.tabs[i].scene.fit_all();
|
||||||
self.command_line.push_output("Zoom Extents");
|
self.command_line.push_output("Zoom Extents");
|
||||||
|
|
@ -751,10 +756,11 @@ impl OpenCADStudio {
|
||||||
use crate::command::KeywordCommand;
|
use crate::command::KeywordCommand;
|
||||||
let c = KeywordCommand::new(
|
let c = KeywordCommand::new(
|
||||||
"ZOOM",
|
"ZOOM",
|
||||||
"ZOOM [Window / Extents / All / In / Out / Scale]:",
|
"ZOOM [Window / Extents / Extents All / All / In / Out / Scale]:",
|
||||||
vec![
|
vec![
|
||||||
("Window", "WINDOW", None),
|
("Window", "WINDOW", None),
|
||||||
("Extents", "EXTENTS", None),
|
("Extents", "EXTENTS", None),
|
||||||
|
("Extents All", "EXTENTS ALL", None),
|
||||||
("All", "ALL", None),
|
("All", "ALL", None),
|
||||||
("In", "IN", None),
|
("In", "IN", None),
|
||||||
("Out", "OUT", None),
|
("Out", "OUT", None),
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,25 @@ impl Scene {
|
||||||
self.camera_generation += 1;
|
self.camera_generation += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Aspect ratio of the active camera's actual render rectangle. Model
|
||||||
|
/// panes are separate shader widgets, so the full-canvas render aspect is
|
||||||
|
/// wrong whenever a tiled viewport is active.
|
||||||
|
pub(super) fn active_camera_aspect(&self) -> f32 {
|
||||||
|
if self.current_layout == "Model" {
|
||||||
|
let (canvas_w, canvas_h) = self.selection.borrow().vp_size;
|
||||||
|
let tiles = self.model_tiles.borrow();
|
||||||
|
let active = self.active_model_tile.get().min(tiles.len().saturating_sub(1));
|
||||||
|
if let Some(tile) = tiles.get(active) {
|
||||||
|
let width = tile.rect.width * canvas_w;
|
||||||
|
let height = tile.rect.height * canvas_h;
|
||||||
|
if width.is_finite() && height.is_finite() && width > 0.0 && height > 0.0 {
|
||||||
|
return width / height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.last_render_aspect.get().max(0.01)
|
||||||
|
}
|
||||||
|
|
||||||
/// Fit the camera to a world-space bounding box (corners p1, p2).
|
/// Fit the camera to a world-space bounding box (corners p1, p2).
|
||||||
pub fn zoom_to_window(&mut self, p1: glam::Vec3, p2: glam::Vec3) {
|
pub fn zoom_to_window(&mut self, p1: glam::Vec3, p2: glam::Vec3) {
|
||||||
let min = p1.min(p2);
|
let min = p1.min(p2);
|
||||||
|
|
@ -105,9 +124,10 @@ impl Scene {
|
||||||
if min == max {
|
if min == max {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let aspect = self.active_camera_aspect();
|
||||||
self.camera
|
self.camera
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.fit_to_bounds(min, max, self.last_render_aspect.get().max(0.01));
|
.fit_to_bounds(min, max, aspect);
|
||||||
self.camera_generation += 1;
|
self.camera_generation += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -925,9 +945,61 @@ impl Scene {
|
||||||
if min == max {
|
if min == max {
|
||||||
max += glam::Vec3::splat(1.0);
|
max += glam::Vec3::splat(1.0);
|
||||||
}
|
}
|
||||||
self.camera
|
let aspect = self.active_camera_aspect();
|
||||||
.borrow_mut()
|
self.camera.borrow_mut().fit_to_bounds(min, max, aspect);
|
||||||
.fit_to_bounds(min, max, self.last_render_aspect.get().max(0.01));
|
self.camera_generation += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fit every tiled Model viewport independently. The drawing bounds are
|
||||||
|
/// shared, but each tile keeps its own orientation and uses its own screen
|
||||||
|
/// aspect when calculating the required zoom.
|
||||||
|
pub fn fit_all_model_viewports(&mut self) {
|
||||||
|
if self.current_layout != "Model" || self.model_tiles.borrow().len() <= 1 {
|
||||||
|
self.fit_all();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let generation_before = self.camera_generation;
|
||||||
|
self.fit_all();
|
||||||
|
if self.camera_generation == generation_before {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some((min, max)) = self.camera.borrow().fitted_model_bounds() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let (canvas_w, canvas_h) = self.selection.borrow().vp_size;
|
||||||
|
let fallback = self.last_render_aspect.get().max(0.01);
|
||||||
|
let aspects: Vec<f32> = self
|
||||||
|
.model_tiles
|
||||||
|
.borrow()
|
||||||
|
.iter()
|
||||||
|
.map(|tile| {
|
||||||
|
let width = tile.rect.width * canvas_w;
|
||||||
|
let height = tile.rect.height * canvas_h;
|
||||||
|
if width.is_finite() && height.is_finite() && width > 0.0 && height > 0.0 {
|
||||||
|
width / height
|
||||||
|
} else {
|
||||||
|
fallback
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let active = self
|
||||||
|
.active_model_tile
|
||||||
|
.get()
|
||||||
|
.min(aspects.len().saturating_sub(1));
|
||||||
|
let live_camera = self.camera.borrow_mut();
|
||||||
|
let mut tiles = self.model_tiles.borrow_mut();
|
||||||
|
for (index, tile) in tiles.iter_mut().enumerate() {
|
||||||
|
let aspect = aspects.get(index).copied().unwrap_or(fallback);
|
||||||
|
if index == active {
|
||||||
|
tile.camera = live_camera.clone();
|
||||||
|
} else {
|
||||||
|
tile.camera.fit_to_bounds(min, max, aspect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(aspect) = aspects.get(active) {
|
||||||
|
self.last_render_aspect.set(*aspect);
|
||||||
|
}
|
||||||
self.camera_generation += 1;
|
self.camera_generation += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -137,9 +137,8 @@ impl Scene {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.camera
|
let aspect = self.active_camera_aspect();
|
||||||
.borrow_mut()
|
self.camera.borrow_mut().fit_to_bounds(min, max, aspect);
|
||||||
.fit_to_bounds(min, max, self.last_render_aspect.get().max(0.01));
|
|
||||||
self.camera_generation += 1;
|
self.camera_generation += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -464,6 +464,10 @@ impl Camera {
|
||||||
self.depth_half_range = self.depth_extent_in_view(min, max);
|
self.depth_half_range = self.depth_extent_in_view(min, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn fitted_model_bounds(&self) -> Option<(Vec3, Vec3)> {
|
||||||
|
self.model_bounds
|
||||||
|
}
|
||||||
|
|
||||||
// ── ViewCube snap ─────────────────────────────────────────────────────
|
// ── ViewCube snap ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// Snap to a canonical view direction (called by ViewCubeSnap).
|
/// Snap to a canonical view direction (called by ViewCubeSnap).
|
||||||
|
|
|
||||||
|
|
@ -1923,6 +1923,10 @@ impl Scene {
|
||||||
};
|
};
|
||||||
let active = self.active_model_tile.get();
|
let active = self.active_model_tile.get();
|
||||||
let is_active = tile_idx == active;
|
let is_active = tile_idx == active;
|
||||||
|
if is_active && canvas.1 > 0.0 {
|
||||||
|
self.set_render_aspect(canvas.0 / canvas.1);
|
||||||
|
self.set_render_pixel_scale(canvas.0, canvas.1);
|
||||||
|
}
|
||||||
let nav_build_started = iced::time::Instant::now();
|
let nav_build_started = iced::time::Instant::now();
|
||||||
let perf_nav = if is_active {
|
let perf_nav = if is_active {
|
||||||
self.take_nav_perf()
|
self.take_nav_perf()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue