From 064da8829e759a1b95e736bc1fdc73126cd1e696 Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Tue, 13 Oct 2026 09:44:49 +0300 Subject: [PATCH] View backgrounds: image colours as stored, viewport style name, adjust automation - The background image texture is not an sRGB view: the image is drawn with its own colours, as the solid and gradient colours are. - An entered paper-space viewport shows its named visual style (Realistic, ...) in the render-mode picker, and a mode picked there is recorded as the viewport's visual style. - The view_background automation action takes offset=x,y and scale=x,y. Co-Authored-By: Claude Opus 5.5 --- src/app/commands/pdf_dialogs.rs | 2 ++ src/app/commands/view_presets.rs | 2 ++ src/app/control/actions.rs | 17 +++++++++++++++- src/app/update/viewport.rs | 3 +++ src/app/view/mod.rs | 33 +++++++++++++++++++------------- src/scene/camera_ops.rs | 17 +++++++++++++++- src/scene/pipeline/mod.rs | 22 +++++++++++++++++---- src/ui/window/pdf_dialogs.rs | 3 +++ 8 files changed, 80 insertions(+), 19 deletions(-) diff --git a/src/app/commands/pdf_dialogs.rs b/src/app/commands/pdf_dialogs.rs index 2c14389c8..b7a5b1887 100644 --- a/src/app/commands/pdf_dialogs.rs +++ b/src/app/commands/pdf_dialogs.rs @@ -582,6 +582,8 @@ impl OpenCADStudio { | PdfDialogMsg::BgBrowse | PdfDialogMsg::BgPosition(_) | PdfDialogMsg::BgAspect(_) + | PdfDialogMsg::BgOffset(_) + | PdfDialogMsg::BgScale(_) | PdfDialogMsg::BgApply) => return self.update_view_background(message), message @ (PdfDialogMsg::SecPerimeter(_) | PdfDialogMsg::SecMaxPoints(_) diff --git a/src/app/commands/view_presets.rs b/src/app/commands/view_presets.rs index 98dc13566..7622f0f17 100644 --- a/src/app/commands/view_presets.rs +++ b/src/app/commands/view_presets.rs @@ -507,6 +507,8 @@ impl OpenCADStudio { M::BgImage(value) => state.image = value, M::BgPosition(value) => state.position = value, M::BgAspect(on) => state.aspect = on, + M::BgOffset(value) => state.offset = value, + M::BgScale(value) => state.scale = value, M::BgColor(slot, rgb) => match slot { 0 => state.solid = rgb, 1 => state.top = rgb, diff --git a/src/app/control/actions.rs b/src/app/control/actions.rs index feaba9171..bc9ad7ed4 100644 --- a/src/app/control/actions.rs +++ b/src/app/control/actions.rs @@ -1798,7 +1798,7 @@ impl OpenCADStudio { "ribbon_dropdown" => Message::ToggleRibbonDropdown(string(req, "value")?.into()), // The -VIEW Background dialog, one field per call: "type=gradient", // "color|top|middle|bottom=r,g,b", "three=1", "rotation=30", - // "image=", "position=center|stretch|tile", "aspect=1", + // "image=", "position=center|stretch|tile", "aspect=1", "offset=x,y" (pixels), "scale=x,y" (non-zero), // "apply" or "cancel". "view_background" => { use crate::ui::window::pdf_dialogs::{BgKind, BgPosition, PdfDialogMsg as P}; @@ -1840,6 +1840,21 @@ impl OpenCADStudio { _ => return Err(bad()), })), "aspect" => Message::PdfDialog(P::BgAspect(value == "1")), + "offset" | "scale" => { + let pair: Vec = value + .split(',') + .map(|part| part.trim().parse::()) + .collect::>() + .map_err(|_| bad())?; + let pair = <[f64; 2]>::try_from(pair).map_err(|_| bad())?; + if key == "offset" { + Message::PdfDialog(P::BgOffset(pair)) + } else if pair.iter().all(|v| v.is_finite() && *v != 0.0) { + Message::PdfDialog(P::BgScale(pair)) + } else { + return Err(bad()); + } + } _ => return Err(bad()), } } diff --git a/src/app/update/viewport.rs b/src/app/update/viewport.rs index 57e627446..0307b2e22 100644 --- a/src/app/update/viewport.rs +++ b/src/app/update/viewport.rs @@ -1084,6 +1084,9 @@ impl OpenCADStudio { // viewport, the picker drives that viewport entity's own // render mode; the model-layout tab style is untouched. if self.tabs[i].scene.set_active_viewport_render_mode(mode) { + self.tabs[i] + .scene + .set_active_visual_style_named(crate::modules::view::visual_style::style_name_for(mode)); self.tabs[i].scene.bump_geometry_no_blocks(); self.tabs[i].dirty = true; self.command_line diff --git a/src/app/view/mod.rs b/src/app/view/mod.rs index 0d8b7a9e8..62df6afcf 100644 --- a/src/app/view/mod.rs +++ b/src/app/view/mod.rs @@ -182,6 +182,24 @@ impl std::fmt::Display for RenderModeChoice { } } +/// A named style (Realistic, …) the active viewport draws as, by name. +fn named_visual_style( + scene: &crate::scene::Scene, + mode: codec::entities::ViewportRenderMode, +) -> Option { + match scene.document.objects.get(&scene.active_visual_style()) { + Some(codec::objects::ObjectType::VisualStyle(style)) => { + match crate::modules::view::visual_style::mode_for_style_name(&style.description) { + Some((named_mode, true)) if named_mode == mode => { + Some(crate::t!(style.description.as_str()).into_owned()) + } + _ => None, + } + } + _ => None, + } +} + impl OpenCADStudio { #[cfg(not(target_arch = "wasm32"))] pub fn view(&self, window_id: window::Id) -> Element<'_, Message> { @@ -1297,18 +1315,7 @@ bg={bg_ms:.1}ms n={view_count}" let rect = tab.scene.active_model_tile_bounds(vw, vh); // Unified control chip: split buttons + render-mode picker + // grid / grid-snap toggles, for the active Model tile. - // A named style (Realistic, …) the tile draws as shows by name. - let style_name = match tab.scene.document.objects.get(&tab.scene.active_visual_style()) { - Some(codec::objects::ObjectType::VisualStyle(style)) => { - match crate::modules::view::visual_style::mode_for_style_name(&style.description) { - Some((mode, true)) if mode == tab.render_mode => { - Some(crate::t!(style.description.as_str()).into_owned()) - } - _ => None, - } - } - _ => None, - }; + let style_name = named_visual_style(&tab.scene, tab.render_mode); let bar = viewport_controls( tab.render_mode, self.show_grid, @@ -1410,7 +1417,7 @@ bg={bg_ms:.1}ms n={view_count}" 0, self.render_mode_menu_open, self.render_mode_preview, - None, + named_visual_style(&tab.scene, vp_mode), ); let adaptive: Element<'_, Message> = DensitySwap::new(vec![ iced::widget::opaque(bar), diff --git a/src/scene/camera_ops.rs b/src/scene/camera_ops.rs index e9dcbcc6b..bcfcddc0e 100644 --- a/src/scene/camera_ops.rs +++ b/src/scene/camera_ops.rs @@ -336,6 +336,12 @@ impl Scene { /// The visual style of the active Model tile's viewport record. pub fn active_visual_style(&self) -> Handle { + if self.current_layout != "Model" { + return match self.active_viewport.and_then(|handle| self.document.get_entity(handle)) { + Some(EntityType::Viewport(value)) => value.visual_style_handle, + _ => Handle::NULL, + }; + } self.active_tile_vport_handle() .and_then(|handle| self.document.vports.iter().find(|value| value.handle == handle)) .map_or(Handle::NULL, |value| value.visual_style_handle) @@ -348,7 +354,16 @@ impl Scene { codec::objects::ObjectType::VisualStyle(style) if style.description == name => Some(*handle), _ => None, }); - let (Some(style), Some(handle)) = (style, self.active_tile_vport_handle()) else { return }; + let Some(style) = style else { return }; + if self.current_layout != "Model" { + if let Some(handle) = self.active_viewport { + if let Some(EntityType::Viewport(value)) = self.document.get_entity_mut(handle) { + value.visual_style_handle = style; + } + } + return; + } + let Some(handle) = self.active_tile_vport_handle() else { return }; if let Some(value) = self.document.vports.iter_mut().find(|value| value.handle == handle) { value.visual_style_handle = style; } diff --git a/src/scene/pipeline/mod.rs b/src/scene/pipeline/mod.rs index 66278bcfd..6cad467e8 100644 --- a/src/scene/pipeline/mod.rs +++ b/src/scene/pipeline/mod.rs @@ -3996,7 +3996,8 @@ analytic={:.1} regular={:.1} blocks={:.1}", } let upload = |label: &'static str, image: Option<&crate::scene::model::image_model::DecodedImage>, - fallback: [u8; 4]| { + fallback: [u8; 4], + format: wgpu::TextureFormat| { let (width, height, pixels) = image .map(|image| (image.width, image.height, image.pixels.as_slice())) .unwrap_or((1, 1, fallback.as_slice())); @@ -4010,7 +4011,7 @@ analytic={:.1} regular={:.1} blocks={:.1}", mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba8UnormSrgb, + format, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, view_formats: &[], }); @@ -4030,8 +4031,21 @@ analytic={:.1} regular={:.1} blocks={:.1}", ); texture }; - self.background_texture = upload("background.texture", background, [0, 0, 0, 255]); - self.environment_texture = upload("environment.texture", environment, [128, 128, 128, 255]); + // The background image is drawn as it is, like the solid and gradient + // colours (an sRGB view would darken it a second time); the + // environment map stays sRGB for lighting. + self.background_texture = upload( + "background.texture", + background, + [0, 0, 0, 255], + wgpu::TextureFormat::Rgba8Unorm, + ); + self.environment_texture = upload( + "environment.texture", + environment, + [128, 128, 128, 255], + wgpu::TextureFormat::Rgba8UnormSrgb, + ); self.rebuild_frame_bind_group(device); self.background_source_id = background_id; self.environment_source_id = environment_id; diff --git a/src/ui/window/pdf_dialogs.rs b/src/ui/window/pdf_dialogs.rs index cdf7cf97a..c27edfc5d 100644 --- a/src/ui/window/pdf_dialogs.rs +++ b/src/ui/window/pdf_dialogs.rs @@ -122,6 +122,9 @@ pub enum PdfDialogMsg { BgBrowse, BgPosition(BgPosition), BgAspect(bool), + /// An image background's offset (pixels) and scale. + BgOffset([f64; 2]), + BgScale([f64; 2]), BgApply, }